@almadar/ui 5.47.0 → 5.49.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 (45) hide show
  1. package/dist/avl/index.cjs +2998 -443
  2. package/dist/avl/index.css +504 -0
  3. package/dist/avl/index.js +2998 -444
  4. package/dist/components/core/templates/index.d.ts +3 -0
  5. package/dist/components/game/atoms/ActionButton.d.ts +4 -1
  6. package/dist/components/game/atoms/ComboCounter.d.ts +4 -1
  7. package/dist/components/game/atoms/ControlButton.d.ts +4 -2
  8. package/dist/components/game/atoms/DamageNumber.d.ts +4 -1
  9. package/dist/components/game/atoms/ResourceCounter.d.ts +4 -1
  10. package/dist/components/game/atoms/ScoreDisplay.d.ts +4 -1
  11. package/dist/components/game/atoms/StateIndicator.d.ts +4 -7
  12. package/dist/components/game/atoms/StatusEffect.d.ts +4 -1
  13. package/dist/components/game/atoms/TurnIndicator.d.ts +4 -1
  14. package/dist/components/game/atoms/WaypointMarker.d.ts +4 -1
  15. package/dist/components/game/molecules/EnemyPlate.d.ts +4 -1
  16. package/dist/components/game/molecules/StatBadge.d.ts +4 -1
  17. package/dist/components/game/molecules/three/index.cjs +1428 -346
  18. package/dist/components/game/molecules/three/index.css +0 -5
  19. package/dist/components/game/molecules/three/index.js +1429 -347
  20. package/dist/components/game/organisms/CastleBoard.d.ts +10 -1
  21. package/dist/components/game/organisms/GameBoard3D.d.ts +62 -0
  22. package/dist/components/game/organisms/PlatformerBoard.d.ts +51 -0
  23. package/dist/components/game/organisms/RoguelikeBoard.d.ts +59 -0
  24. package/dist/components/game/organisms/TowerDefenseBoard.d.ts +64 -0
  25. package/dist/components/game/organisms/index.d.ts +4 -0
  26. package/dist/components/game/organisms/puzzles/event-handler/EventHandlerBoard.d.ts +9 -1
  27. package/dist/components/game/organisms/puzzles/state-architect/StateArchitectBoard.d.ts +14 -1
  28. package/dist/components/game/templates/GameCanvas3DBattleTemplate.d.ts +22 -35
  29. package/dist/components/game/templates/GameCanvas3DCastleTemplate.d.ts +23 -23
  30. package/dist/components/game/templates/GameCanvas3DWorldMapTemplate.d.ts +27 -29
  31. package/dist/components/game/templates/PlatformerTemplate.d.ts +26 -0
  32. package/dist/components/game/templates/RoguelikeTemplate.d.ts +23 -0
  33. package/dist/components/game/templates/TowerDefenseTemplate.d.ts +43 -0
  34. package/dist/components/index.cjs +2991 -422
  35. package/dist/components/index.css +504 -0
  36. package/dist/components/index.js +2985 -424
  37. package/dist/docs/index.css +504 -0
  38. package/dist/providers/index.cjs +2981 -426
  39. package/dist/providers/index.css +504 -0
  40. package/dist/providers/index.js +2981 -427
  41. package/dist/runtime/index.cjs +2982 -427
  42. package/dist/runtime/index.css +504 -0
  43. package/dist/runtime/index.js +2982 -428
  44. package/package.json +1 -1
  45. package/tailwind-preset.cjs +1 -0
@@ -41,6 +41,12 @@ var core$1 = require('@dnd-kit/core');
41
41
  var sortable = require('@dnd-kit/sortable');
42
42
  var utilities = require('@dnd-kit/utilities');
43
43
  var react = require('@xyflow/react');
44
+ var THREE = require('three');
45
+ var GLTFLoader_js = require('three/examples/jsm/loaders/GLTFLoader.js');
46
+ var OBJLoader_js = require('three/examples/jsm/loaders/OBJLoader.js');
47
+ var GLTFLoader = require('three/examples/jsm/loaders/GLTFLoader');
48
+ var fiber = require('@react-three/fiber');
49
+ var drei = require('@react-three/drei');
44
50
  var patterns = require('@almadar/patterns');
45
51
 
46
52
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
@@ -88,6 +94,7 @@ var ReactMarkdown__default = /*#__PURE__*/_interopDefault(ReactMarkdown);
88
94
  var remarkGfm__default = /*#__PURE__*/_interopDefault(remarkGfm);
89
95
  var remarkMath__default = /*#__PURE__*/_interopDefault(remarkMath);
90
96
  var rehypeKatex__default = /*#__PURE__*/_interopDefault(rehypeKatex);
97
+ var THREE__namespace = /*#__PURE__*/_interopNamespace(THREE);
91
98
 
92
99
  var __defProp = Object.defineProperty;
93
100
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -719,7 +726,7 @@ var init_Box = __esm({
719
726
  position,
720
727
  className,
721
728
  children,
722
- as: Component = "div",
729
+ as: Component2 = "div",
723
730
  action,
724
731
  actionPayload,
725
732
  hoverEvent,
@@ -751,7 +758,7 @@ var init_Box = __esm({
751
758
  }, [hoverEvent, eventBus, onMouseLeave]);
752
759
  const isClickable = action || onClick;
753
760
  return React83__namespace.default.createElement(
754
- Component,
761
+ Component2,
755
762
  {
756
763
  ref,
757
764
  className: cn(
@@ -889,9 +896,9 @@ function resolvePhosphor(name, weight, family) {
889
896
  (lib) => {
890
897
  const PhosphorComp = lib[target];
891
898
  if (!PhosphorComp || typeof PhosphorComp !== "object") return null;
892
- const Component = PhosphorComp;
899
+ const Component2 = PhosphorComp;
893
900
  const Adapter = (props) => /* @__PURE__ */ jsxRuntime.jsx(
894
- Component,
901
+ Component2,
895
902
  {
896
903
  weight,
897
904
  className: props.className,
@@ -915,9 +922,9 @@ function resolveTabler(name, family) {
915
922
  (lib) => {
916
923
  const TablerComp = lib[target];
917
924
  if (!TablerComp || typeof TablerComp !== "object") return null;
918
- const Component = TablerComp;
925
+ const Component2 = TablerComp;
919
926
  const Adapter = (props) => /* @__PURE__ */ jsxRuntime.jsx(
920
- Component,
927
+ Component2,
921
928
  {
922
929
  stroke: props.strokeWidth ?? 1.5,
923
930
  className: props.className,
@@ -941,9 +948,9 @@ function resolveFa(name, family) {
941
948
  (lib) => {
942
949
  const FaComp = lib[target];
943
950
  if (!FaComp || typeof FaComp !== "function") return null;
944
- const Component = FaComp;
951
+ const Component2 = FaComp;
945
952
  const Adapter = (props) => /* @__PURE__ */ jsxRuntime.jsx(
946
- Component,
953
+ Component2,
947
954
  {
948
955
  className: props.className,
949
956
  style: props.style,
@@ -1898,8 +1905,8 @@ var init_Typography = __esm({
1898
1905
  children
1899
1906
  }) => {
1900
1907
  const variant = variantProp ?? (level ? `h${level}` : "body1");
1901
- const Component = as || defaultElements[variant];
1902
- const Comp = Component;
1908
+ const Component2 = as || defaultElements[variant];
1909
+ const Comp = Component2;
1903
1910
  return /* @__PURE__ */ jsxRuntime.jsx(
1904
1911
  Comp,
1905
1912
  {
@@ -4454,10 +4461,10 @@ var init_Center = __esm({
4454
4461
  className,
4455
4462
  style,
4456
4463
  children,
4457
- as: Component = "div"
4464
+ as: Component2 = "div"
4458
4465
  }) => {
4459
4466
  const mergedStyle = minHeight ? { minHeight, ...style } : style;
4460
- const Comp = Component;
4467
+ const Comp = Component2;
4461
4468
  return /* @__PURE__ */ jsxRuntime.jsx(
4462
4469
  Comp,
4463
4470
  {
@@ -5088,7 +5095,7 @@ var init_Stack = __esm({
5088
5095
  className,
5089
5096
  style,
5090
5097
  children,
5091
- as: Component = "div",
5098
+ as: Component2 = "div",
5092
5099
  onClick,
5093
5100
  onKeyDown,
5094
5101
  role,
@@ -5106,7 +5113,7 @@ var init_Stack = __esm({
5106
5113
  };
5107
5114
  const isHorizontal = direction === "horizontal";
5108
5115
  const directionClass = responsive && isHorizontal ? reverse ? "flex-col-reverse md:flex-row-reverse" : "flex-col md:flex-row" : isHorizontal ? reverse ? "flex-row-reverse" : "flex-row" : reverse ? "flex-col-reverse" : "flex-col";
5109
- const Comp = Component;
5116
+ const Comp = Component2;
5110
5117
  return /* @__PURE__ */ jsxRuntime.jsx(
5111
5118
  Comp,
5112
5119
  {
@@ -7162,6 +7169,7 @@ var init_HealthBar = __esm({
7162
7169
  }
7163
7170
  });
7164
7171
  function ScoreDisplay({
7172
+ assetUrl = DEFAULT_ASSET_URL,
7165
7173
  value,
7166
7174
  label,
7167
7175
  icon,
@@ -7208,14 +7216,24 @@ function ScoreDisplay({
7208
7216
  className
7209
7217
  ),
7210
7218
  children: [
7211
- icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-shrink-0", children: typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: icon }) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon }) }),
7219
+ assetUrl ? /* @__PURE__ */ jsxRuntime.jsx(
7220
+ "img",
7221
+ {
7222
+ src: assetUrl,
7223
+ alt: "",
7224
+ width: 20,
7225
+ height: 20,
7226
+ style: { imageRendering: "pixelated", objectFit: "contain" },
7227
+ className: "flex-shrink-0"
7228
+ }
7229
+ ) : icon ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-shrink-0", children: typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: icon }) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon }) }) : null,
7212
7230
  label && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: label }),
7213
7231
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "tabular-nums", children: formattedValue })
7214
7232
  ]
7215
7233
  }
7216
7234
  );
7217
7235
  }
7218
- var sizeMap2;
7236
+ var sizeMap2, DEFAULT_ASSET_URL;
7219
7237
  var init_ScoreDisplay = __esm({
7220
7238
  "components/game/atoms/ScoreDisplay.tsx"() {
7221
7239
  "use client";
@@ -7227,10 +7245,12 @@ var init_ScoreDisplay = __esm({
7227
7245
  lg: "text-2xl",
7228
7246
  xl: "text-4xl"
7229
7247
  };
7248
+ DEFAULT_ASSET_URL = "https://almadar-kflow-assets.web.app/shared/effects/particles/star_01.png";
7230
7249
  ScoreDisplay.displayName = "ScoreDisplay";
7231
7250
  }
7232
7251
  });
7233
7252
  function ControlButton({
7253
+ assetUrl = DEFAULT_ASSET_URL2,
7234
7254
  label,
7235
7255
  icon,
7236
7256
  size = "md",
@@ -7277,7 +7297,7 @@ function ControlButton({
7277
7297
  },
7278
7298
  [isPressed, releaseEvent, eventBus, onRelease]
7279
7299
  );
7280
- return /* @__PURE__ */ jsxRuntime.jsxs(
7300
+ return /* @__PURE__ */ jsxRuntime.jsx(
7281
7301
  "button",
7282
7302
  {
7283
7303
  type: "button",
@@ -7298,20 +7318,27 @@ function ControlButton({
7298
7318
  disabled && "opacity-50 cursor-not-allowed",
7299
7319
  className
7300
7320
  ),
7301
- children: [
7302
- icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-2xl", children: typeof icon === "string" ? /^[a-zA-Z0-9-]+$/.test(icon) ? (() => {
7303
- const I = resolveIcon(icon);
7304
- return I ? /* @__PURE__ */ jsxRuntime.jsx(I, { className: "w-6 h-6" }) : null;
7305
- })() : icon : /* @__PURE__ */ (() => {
7306
- const I = icon;
7307
- return /* @__PURE__ */ jsxRuntime.jsx(I, { className: "w-6 h-6" });
7308
- })() }),
7309
- label && !icon && /* @__PURE__ */ jsxRuntime.jsx("span", { children: label })
7310
- ]
7321
+ children: assetUrl ? /* @__PURE__ */ jsxRuntime.jsx(
7322
+ "img",
7323
+ {
7324
+ src: assetUrl,
7325
+ alt: "",
7326
+ width: 24,
7327
+ height: 24,
7328
+ style: { imageRendering: "pixelated", objectFit: "contain" },
7329
+ className: "flex-shrink-0"
7330
+ }
7331
+ ) : icon ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-2xl", children: typeof icon === "string" ? /^[a-zA-Z0-9-]+$/.test(icon) ? (() => {
7332
+ const I = resolveIcon(icon);
7333
+ return I ? /* @__PURE__ */ jsxRuntime.jsx(I, { className: "w-6 h-6" }) : null;
7334
+ })() : icon : /* @__PURE__ */ (() => {
7335
+ const I = icon;
7336
+ return /* @__PURE__ */ jsxRuntime.jsx(I, { className: "w-6 h-6" });
7337
+ })() }) : label ? /* @__PURE__ */ jsxRuntime.jsx("span", { children: label }) : null
7311
7338
  }
7312
7339
  );
7313
7340
  }
7314
- var sizeMap3, shapeMap, variantMap;
7341
+ var sizeMap3, shapeMap, variantMap, DEFAULT_ASSET_URL2;
7315
7342
  var init_ControlButton = __esm({
7316
7343
  "components/game/atoms/ControlButton.tsx"() {
7317
7344
  "use client";
@@ -7334,6 +7361,7 @@ var init_ControlButton = __esm({
7334
7361
  secondary: "bg-secondary text-secondary-foreground border-border hover:bg-secondary-hover",
7335
7362
  ghost: "bg-transparent text-foreground border-border hover:bg-muted"
7336
7363
  };
7364
+ DEFAULT_ASSET_URL2 = "https://almadar-kflow-assets.web.app/shared/effects/particles/circle_01.png";
7337
7365
  ControlButton.displayName = "ControlButton";
7338
7366
  }
7339
7367
  });
@@ -7413,6 +7441,7 @@ var init_Sprite = __esm({
7413
7441
  }
7414
7442
  });
7415
7443
  function StateIndicator({
7444
+ assetUrl = DEFAULT_ASSET_URL3,
7416
7445
  state = "idle",
7417
7446
  label,
7418
7447
  size = "md",
@@ -7435,18 +7464,29 @@ function StateIndicator({
7435
7464
  className
7436
7465
  ),
7437
7466
  children: [
7438
- /* @__PURE__ */ jsxRuntime.jsx(Box, { as: "span", children: typeof config.icon === "string" ? /^[a-zA-Z0-9-]+$/.test(config.icon) ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: config.icon }) : config.icon : /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: config.icon }) }),
7467
+ /* @__PURE__ */ jsxRuntime.jsx(Box, { as: "span", children: assetUrl ? /* @__PURE__ */ jsxRuntime.jsx(
7468
+ "img",
7469
+ {
7470
+ src: assetUrl,
7471
+ alt: displayLabel,
7472
+ width: 16,
7473
+ height: 16,
7474
+ style: { imageRendering: "pixelated", objectFit: "contain" },
7475
+ className: "flex-shrink-0"
7476
+ }
7477
+ ) : typeof config.icon === "string" ? /^[a-zA-Z0-9-]+$/.test(config.icon) ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: config.icon }) : config.icon : /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: config.icon }) }),
7439
7478
  /* @__PURE__ */ jsxRuntime.jsx(Box, { as: "span", children: displayLabel })
7440
7479
  ]
7441
7480
  }
7442
7481
  );
7443
7482
  }
7444
- var DEFAULT_STATE_STYLES, DEFAULT_STYLE, SIZE_CLASSES;
7483
+ var DEFAULT_ASSET_URL3, DEFAULT_STATE_STYLES, DEFAULT_STYLE, SIZE_CLASSES;
7445
7484
  var init_StateIndicator = __esm({
7446
7485
  "components/game/atoms/StateIndicator.tsx"() {
7447
7486
  init_Box();
7448
7487
  init_Icon();
7449
7488
  init_cn();
7489
+ DEFAULT_ASSET_URL3 = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
7450
7490
  DEFAULT_STATE_STYLES = {
7451
7491
  idle: { icon: "\u23F8", bgClass: "bg-muted" },
7452
7492
  active: { icon: "\u25B6", bgClass: "bg-success" },
@@ -7521,6 +7561,7 @@ var init_TimerDisplay = __esm({
7521
7561
  }
7522
7562
  });
7523
7563
  function ResourceCounter({
7564
+ assetUrl = DEFAULT_ASSET_URL4,
7524
7565
  icon,
7525
7566
  label = "Gold",
7526
7567
  value = 250,
@@ -7540,7 +7581,17 @@ function ResourceCounter({
7540
7581
  className
7541
7582
  ),
7542
7583
  children: [
7543
- icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("flex-shrink-0", sizes.icon), children: typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: icon }) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon }) }),
7584
+ assetUrl ? /* @__PURE__ */ jsxRuntime.jsx(
7585
+ "img",
7586
+ {
7587
+ src: assetUrl,
7588
+ alt: label,
7589
+ width: sizes.img,
7590
+ height: sizes.img,
7591
+ style: { imageRendering: "pixelated", objectFit: "contain" },
7592
+ className: "flex-shrink-0"
7593
+ }
7594
+ ) : icon ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("flex-shrink-0", sizes.icon), children: typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: icon }) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon }) }) : null,
7544
7595
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: label }),
7545
7596
  /* @__PURE__ */ jsxRuntime.jsxs("span", { className: cn("font-bold tabular-nums", color && (color in colorTokenClasses2 ? colorTokenClasses2[color] : color)), children: [
7546
7597
  value,
@@ -7553,7 +7604,7 @@ function ResourceCounter({
7553
7604
  }
7554
7605
  );
7555
7606
  }
7556
- var colorTokenClasses2, sizeMap5;
7607
+ var colorTokenClasses2, DEFAULT_ASSET_URL4, sizeMap5;
7557
7608
  var init_ResourceCounter = __esm({
7558
7609
  "components/game/atoms/ResourceCounter.tsx"() {
7559
7610
  init_cn();
@@ -7566,16 +7617,17 @@ var init_ResourceCounter = __esm({
7566
7617
  error: "text-error",
7567
7618
  muted: "text-muted-foreground"
7568
7619
  };
7620
+ DEFAULT_ASSET_URL4 = "https://almadar-kflow-assets.web.app/shared/world-map/gold_mine.png";
7569
7621
  sizeMap5 = {
7570
- sm: { wrapper: "text-xs gap-1 px-1.5 py-0.5", icon: "text-sm" },
7571
- md: { wrapper: "text-sm gap-1.5 px-2 py-1", icon: "text-base" },
7572
- lg: { wrapper: "text-base gap-2 px-3 py-1.5", icon: "text-lg" }
7622
+ sm: { wrapper: "text-xs gap-1 px-1.5 py-0.5", icon: "text-sm", img: 16 },
7623
+ md: { wrapper: "text-sm gap-1.5 px-2 py-1", icon: "text-base", img: 20 },
7624
+ lg: { wrapper: "text-base gap-2 px-3 py-1.5", icon: "text-lg", img: 28 }
7573
7625
  };
7574
7626
  ResourceCounter.displayName = "ResourceCounter";
7575
7627
  }
7576
7628
  });
7577
7629
  function ItemSlot({
7578
- assetUrl = DEFAULT_ASSET_URL,
7630
+ assetUrl = DEFAULT_ASSET_URL5,
7579
7631
  icon = "sword",
7580
7632
  label = "Iron Sword",
7581
7633
  quantity,
@@ -7634,7 +7686,7 @@ function ItemSlot({
7634
7686
  }
7635
7687
  );
7636
7688
  }
7637
- var sizeMap6, rarityBorderMap, rarityGlowMap, DEFAULT_ASSET_URL, assetSizeMap;
7689
+ var sizeMap6, rarityBorderMap, rarityGlowMap, DEFAULT_ASSET_URL5, assetSizeMap;
7638
7690
  var init_ItemSlot = __esm({
7639
7691
  "components/game/atoms/ItemSlot.tsx"() {
7640
7692
  "use client";
@@ -7659,7 +7711,7 @@ var init_ItemSlot = __esm({
7659
7711
  epic: "shadow-lg",
7660
7712
  legendary: "shadow-lg"
7661
7713
  };
7662
- DEFAULT_ASSET_URL = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
7714
+ DEFAULT_ASSET_URL5 = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
7663
7715
  assetSizeMap = {
7664
7716
  sm: 28,
7665
7717
  md: 40,
@@ -7669,6 +7721,7 @@ var init_ItemSlot = __esm({
7669
7721
  }
7670
7722
  });
7671
7723
  function TurnIndicator({
7724
+ assetUrl = DEFAULT_ASSET_URL6,
7672
7725
  currentTurn = 1,
7673
7726
  maxTurns,
7674
7727
  activeTeam,
@@ -7701,14 +7754,24 @@ function TurnIndicator({
7701
7754
  ] }),
7702
7755
  activeTeam && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
7703
7756
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: "|" }),
7704
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("rounded-full bg-success/20", sizes.dot) }),
7757
+ assetUrl ? /* @__PURE__ */ jsxRuntime.jsx(
7758
+ "img",
7759
+ {
7760
+ src: assetUrl,
7761
+ alt: "",
7762
+ width: 12,
7763
+ height: 12,
7764
+ style: { imageRendering: "pixelated", objectFit: "contain" },
7765
+ className: "flex-shrink-0"
7766
+ }
7767
+ ) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("rounded-full bg-success/20", sizes.dot) }),
7705
7768
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-success", children: activeTeam })
7706
7769
  ] })
7707
7770
  ]
7708
7771
  }
7709
7772
  );
7710
7773
  }
7711
- var sizeMap7;
7774
+ var sizeMap7, DEFAULT_ASSET_URL6;
7712
7775
  var init_TurnIndicator = __esm({
7713
7776
  "components/game/atoms/TurnIndicator.tsx"() {
7714
7777
  init_cn();
@@ -7717,6 +7780,7 @@ var init_TurnIndicator = __esm({
7717
7780
  md: { wrapper: "text-sm gap-2 px-3 py-1", dot: "w-2 h-2" },
7718
7781
  lg: { wrapper: "text-base gap-2.5 px-4 py-1.5", dot: "w-2.5 h-2.5" }
7719
7782
  };
7783
+ DEFAULT_ASSET_URL6 = "https://almadar-kflow-assets.web.app/shared/effects/particles/symbol_01.png";
7720
7784
  TurnIndicator.displayName = "TurnIndicator";
7721
7785
  }
7722
7786
  });
@@ -7732,6 +7796,7 @@ function getComboScale(combo) {
7732
7796
  return "";
7733
7797
  }
7734
7798
  function ComboCounter({
7799
+ assetUrl = DEFAULT_ASSET_URL7,
7735
7800
  combo = 5,
7736
7801
  multiplier,
7737
7802
  streak,
@@ -7751,6 +7816,17 @@ function ComboCounter({
7751
7816
  className
7752
7817
  ),
7753
7818
  children: [
7819
+ assetUrl && /* @__PURE__ */ jsxRuntime.jsx(
7820
+ "img",
7821
+ {
7822
+ src: assetUrl,
7823
+ alt: "combo",
7824
+ width: 24,
7825
+ height: 24,
7826
+ style: { imageRendering: "pixelated", objectFit: "contain" },
7827
+ className: "flex-shrink-0 mb-0.5"
7828
+ }
7829
+ ),
7754
7830
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("font-black tabular-nums leading-none", sizes.combo, getComboIntensity(combo)), children: combo }),
7755
7831
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("font-bold uppercase tracking-wider text-muted-foreground", sizes.label), children: "combo" }),
7756
7832
  multiplier != null && multiplier > 1 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: cn("font-bold text-warning tabular-nums", sizes.multiplier), children: [
@@ -7765,10 +7841,11 @@ function ComboCounter({
7765
7841
  }
7766
7842
  );
7767
7843
  }
7768
- var sizeMap8;
7844
+ var DEFAULT_ASSET_URL7, sizeMap8;
7769
7845
  var init_ComboCounter = __esm({
7770
7846
  "components/game/atoms/ComboCounter.tsx"() {
7771
7847
  init_cn();
7848
+ DEFAULT_ASSET_URL7 = "https://almadar-kflow-assets.web.app/shared/effects/flash/flash00.png";
7772
7849
  sizeMap8 = {
7773
7850
  sm: { combo: "text-lg", label: "text-xs", multiplier: "text-xs" },
7774
7851
  md: { combo: "text-2xl", label: "text-xs", multiplier: "text-sm" },
@@ -7862,6 +7939,7 @@ var init_XPBar = __esm({
7862
7939
  }
7863
7940
  });
7864
7941
  function WaypointMarker({
7942
+ assetUrl = DEFAULT_ASSET_URL8,
7865
7943
  label,
7866
7944
  icon,
7867
7945
  active = true,
@@ -7900,7 +7978,17 @@ function WaypointMarker({
7900
7978
  active && !completed && "bg-info text-foreground",
7901
7979
  !active && !completed && "bg-muted"
7902
7980
  ),
7903
- children: completed ? checkIcon : icon && (typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: icon }) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon }))
7981
+ children: completed ? checkIcon : assetUrl ? /* @__PURE__ */ jsxRuntime.jsx(
7982
+ "img",
7983
+ {
7984
+ src: assetUrl,
7985
+ alt: label,
7986
+ width: sizes.img,
7987
+ height: sizes.img,
7988
+ style: { imageRendering: "pixelated", objectFit: "contain" },
7989
+ className: "flex-shrink-0"
7990
+ }
7991
+ ) : icon ? typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: icon }) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon }) : null
7904
7992
  }
7905
7993
  )
7906
7994
  ] }),
@@ -7917,15 +8005,16 @@ function WaypointMarker({
7917
8005
  )
7918
8006
  ] });
7919
8007
  }
7920
- var sizeMap10, checkIcon;
8008
+ var DEFAULT_ASSET_URL8, sizeMap10, checkIcon;
7921
8009
  var init_WaypointMarker = __esm({
7922
8010
  "components/game/atoms/WaypointMarker.tsx"() {
7923
8011
  init_cn();
7924
8012
  init_Icon();
8013
+ DEFAULT_ASSET_URL8 = "https://almadar-kflow-assets.web.app/shared/world-map/battle_marker.png";
7925
8014
  sizeMap10 = {
7926
- sm: { dot: "w-4 h-4", ring: "w-6 h-6", label: "text-xs mt-1" },
7927
- md: { dot: "w-6 h-6", ring: "w-8 h-8", label: "text-sm mt-1.5" },
7928
- lg: { dot: "w-8 h-8", ring: "w-10 h-10", label: "text-base mt-2" }
8015
+ sm: { dot: "w-4 h-4", ring: "w-6 h-6", label: "text-xs mt-1", img: 12 },
8016
+ md: { dot: "w-6 h-6", ring: "w-8 h-8", label: "text-sm mt-1.5", img: 18 },
8017
+ lg: { dot: "w-8 h-8", ring: "w-10 h-10", label: "text-base mt-2", img: 24 }
7929
8018
  };
7930
8019
  checkIcon = /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 3, className: "w-3/5 h-3/5", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M5 13l4 4L19 7", strokeLinecap: "round", strokeLinejoin: "round" }) });
7931
8020
  WaypointMarker.displayName = "WaypointMarker";
@@ -7938,6 +8027,7 @@ function formatDuration(seconds) {
7938
8027
  return `${Math.round(seconds)}s`;
7939
8028
  }
7940
8029
  function StatusEffect({
8030
+ assetUrl = DEFAULT_ASSET_URL9,
7941
8031
  icon = "shield",
7942
8032
  label = "Shield",
7943
8033
  duration = 30,
@@ -7958,7 +8048,17 @@ function StatusEffect({
7958
8048
  ),
7959
8049
  title: label,
7960
8050
  children: [
7961
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("flex items-center justify-center", sizes.icon), children: typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: icon, size: "sm" }) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon, size: "sm" }) }),
8051
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("flex items-center justify-center", sizes.icon), children: assetUrl ? /* @__PURE__ */ jsxRuntime.jsx(
8052
+ "img",
8053
+ {
8054
+ src: assetUrl,
8055
+ alt: label,
8056
+ width: sizes.img,
8057
+ height: sizes.img,
8058
+ style: { imageRendering: "pixelated", objectFit: "contain" },
8059
+ className: "flex-shrink-0"
8060
+ }
8061
+ ) : typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: icon, size: "sm" }) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon, size: "sm" }) }),
7962
8062
  duration !== void 0 && /* @__PURE__ */ jsxRuntime.jsx(
7963
8063
  "span",
7964
8064
  {
@@ -7985,15 +8085,16 @@ function StatusEffect({
7985
8085
  label && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground mt-0.5 text-center whitespace-nowrap", children: label })
7986
8086
  ] });
7987
8087
  }
7988
- var sizeMap11, variantStyles7;
8088
+ var DEFAULT_ASSET_URL9, sizeMap11, variantStyles7;
7989
8089
  var init_StatusEffect = __esm({
7990
8090
  "components/game/atoms/StatusEffect.tsx"() {
7991
8091
  init_cn();
7992
8092
  init_Icon();
8093
+ DEFAULT_ASSET_URL9 = "https://almadar-kflow-assets.web.app/shared/effects/particles/flame_01.png";
7993
8094
  sizeMap11 = {
7994
- sm: { container: "w-8 h-8", icon: "text-sm", badge: "text-xs -top-1 -right-1 w-4 h-4", timer: "text-[9px]" },
7995
- md: { container: "w-10 h-10", icon: "text-base", badge: "text-xs -top-1 -right-1 w-5 h-5", timer: "text-xs" },
7996
- lg: { container: "w-12 h-12", icon: "text-lg", badge: "text-sm -top-1.5 -right-1.5 w-6 h-6", timer: "text-xs" }
8095
+ sm: { container: "w-8 h-8", icon: "text-sm", badge: "text-xs -top-1 -right-1 w-4 h-4", timer: "text-[9px]", img: 20 },
8096
+ md: { container: "w-10 h-10", icon: "text-base", badge: "text-xs -top-1 -right-1 w-5 h-5", timer: "text-xs", img: 28 },
8097
+ lg: { container: "w-12 h-12", icon: "text-lg", badge: "text-sm -top-1.5 -right-1.5 w-6 h-6", timer: "text-xs", img: 36 }
7997
8098
  };
7998
8099
  variantStyles7 = {
7999
8100
  buff: "border-success bg-success/20",
@@ -8004,6 +8105,7 @@ var init_StatusEffect = __esm({
8004
8105
  }
8005
8106
  });
8006
8107
  function DamageNumber({
8108
+ assetUrl = DEFAULT_ASSET_URL10,
8007
8109
  value = 42,
8008
8110
  type = "damage",
8009
8111
  size = "md",
@@ -8012,22 +8114,35 @@ function DamageNumber({
8012
8114
  const displayText = type === "miss" ? "MISS" : type === "heal" ? `+${value}` : `${value}`;
8013
8115
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
8014
8116
  /* @__PURE__ */ jsxRuntime.jsx("style", { children: floatKeyframes }),
8015
- /* @__PURE__ */ jsxRuntime.jsx(
8117
+ /* @__PURE__ */ jsxRuntime.jsxs(
8016
8118
  "span",
8017
8119
  {
8018
8120
  className: cn(
8019
- "inline-block font-mono select-none pointer-events-none",
8121
+ "inline-flex items-center gap-0.5 font-mono select-none pointer-events-none",
8020
8122
  sizeMap12[size],
8021
8123
  typeStyles[type],
8022
8124
  className
8023
8125
  ),
8024
8126
  style: { animation: "damageFloat 1s ease-out forwards" },
8025
- children: displayText
8127
+ children: [
8128
+ assetUrl && /* @__PURE__ */ jsxRuntime.jsx(
8129
+ "img",
8130
+ {
8131
+ src: assetUrl,
8132
+ alt: "",
8133
+ width: 14,
8134
+ height: 14,
8135
+ style: { imageRendering: "pixelated", objectFit: "contain" },
8136
+ className: "flex-shrink-0"
8137
+ }
8138
+ ),
8139
+ displayText
8140
+ ]
8026
8141
  }
8027
8142
  )
8028
8143
  ] });
8029
8144
  }
8030
- var sizeMap12, typeStyles, floatKeyframes;
8145
+ var sizeMap12, typeStyles, floatKeyframes, DEFAULT_ASSET_URL10;
8031
8146
  var init_DamageNumber = __esm({
8032
8147
  "components/game/atoms/DamageNumber.tsx"() {
8033
8148
  init_cn();
@@ -8049,6 +8164,7 @@ var init_DamageNumber = __esm({
8049
8164
  100% { opacity: 0; transform: translateY(-32px) scale(0.8); }
8050
8165
  }
8051
8166
  `;
8167
+ DEFAULT_ASSET_URL10 = "https://almadar-kflow-assets.web.app/shared/effects/particles/spark_01.png";
8052
8168
  DamageNumber.displayName = "DamageNumber";
8053
8169
  }
8054
8170
  });
@@ -8137,6 +8253,7 @@ var init_ChoiceButton = __esm({
8137
8253
  }
8138
8254
  });
8139
8255
  function ActionButton({
8256
+ assetUrl = DEFAULT_ASSET_URL11,
8140
8257
  label = "Attack",
8141
8258
  icon,
8142
8259
  cooldown = 0,
@@ -8176,13 +8293,23 @@ function ActionButton({
8176
8293
  }
8177
8294
  }
8178
8295
  ),
8179
- icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("flex-shrink-0", sizes.icon), children: typeof icon === "string" ? (() => {
8296
+ assetUrl ? /* @__PURE__ */ jsxRuntime.jsx(
8297
+ "img",
8298
+ {
8299
+ src: assetUrl,
8300
+ alt: "",
8301
+ width: 16,
8302
+ height: 16,
8303
+ style: { imageRendering: "pixelated", objectFit: "contain" },
8304
+ className: cn("flex-shrink-0", sizes.icon)
8305
+ }
8306
+ ) : icon ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("flex-shrink-0", sizes.icon), children: typeof icon === "string" ? (() => {
8180
8307
  const I = resolveIcon(icon);
8181
8308
  return I ? /* @__PURE__ */ jsxRuntime.jsx(I, { className: "w-4 h-4" }) : null;
8182
8309
  })() : /* @__PURE__ */ (() => {
8183
8310
  const I = icon;
8184
8311
  return /* @__PURE__ */ jsxRuntime.jsx(I, { className: "w-4 h-4" });
8185
- })() }),
8312
+ })() }) : null,
8186
8313
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "relative z-10", children: label }),
8187
8314
  hotkey && /* @__PURE__ */ jsxRuntime.jsx(
8188
8315
  "span",
@@ -8198,7 +8325,7 @@ function ActionButton({
8198
8325
  }
8199
8326
  );
8200
8327
  }
8201
- var sizeMap13, variantStyles8;
8328
+ var sizeMap13, variantStyles8, DEFAULT_ASSET_URL11;
8202
8329
  var init_ActionButton = __esm({
8203
8330
  "components/game/atoms/ActionButton.tsx"() {
8204
8331
  init_cn();
@@ -8213,6 +8340,7 @@ var init_ActionButton = __esm({
8213
8340
  secondary: "bg-secondary text-secondary-foreground hover:bg-secondary-hover border-border",
8214
8341
  danger: "bg-error text-error-foreground hover:bg-error/90 border-error"
8215
8342
  };
8343
+ DEFAULT_ASSET_URL11 = "https://almadar-kflow-assets.web.app/shared/effects/particles/slash_01.png";
8216
8344
  ActionButton.displayName = "ActionButton";
8217
8345
  }
8218
8346
  });
@@ -9081,10 +9209,10 @@ var init_Container = __esm({
9081
9209
  center = true,
9082
9210
  className,
9083
9211
  children,
9084
- as: Component = "div"
9212
+ as: Component2 = "div"
9085
9213
  }) => {
9086
9214
  const resolvedSize = maxWidth ?? size ?? "lg";
9087
- const Comp = Component;
9215
+ const Comp = Component2;
9088
9216
  return /* @__PURE__ */ jsxRuntime.jsx(
9089
9217
  Comp,
9090
9218
  {
@@ -11906,15 +12034,15 @@ function BattleBoard({
11906
12034
  const board = boardEntity(entity) ?? {};
11907
12035
  const tiles = propTiles ?? (Array.isArray(board.tiles) ? board.tiles : []);
11908
12036
  const features = propFeatures ?? (Array.isArray(board.features) ? board.features : []);
11909
- const boardWidth = num(board.boardWidth, 8);
11910
- const boardHeight = num(board.boardHeight, 6);
12037
+ const boardWidth = num(board.gridWidth ?? board.boardWidth, 8);
12038
+ const boardHeight = num(board.gridHeight ?? board.boardHeight, 6);
11911
12039
  const assetManifest = propAssetManifest ?? board.assetManifest;
11912
12040
  const backgroundImage = board.backgroundImage;
11913
12041
  const units = rows(board.units);
11914
12042
  const selectedUnitId = board.selectedUnitId ?? null;
11915
12043
  const currentPhase = str(board.phase) || "observation";
11916
12044
  const currentTurn = num(board.turn, 1);
11917
- const gameResult = board.gameResult ?? null;
12045
+ const gameResult = board.result ?? null;
11918
12046
  const eventBus = useEventBus();
11919
12047
  const { t } = hooks.useTranslate();
11920
12048
  const [hoveredTile, setHoveredTile] = React83.useState(null);
@@ -11935,7 +12063,7 @@ function BattleBoard({
11935
12063
  const validMoves = React83.useMemo(() => {
11936
12064
  if (!selectedUnit || currentPhase !== "movement") return [];
11937
12065
  const moves = [];
11938
- const range = num(selectedUnit.movement);
12066
+ const range = num(board.movementRange, 2);
11939
12067
  const origin = unitPosition(selectedUnit);
11940
12068
  for (let dy = -range; dy <= range; dy++) {
11941
12069
  for (let dx = -range; dx <= range; dx++) {
@@ -20220,15 +20348,24 @@ function CastleBoard({
20220
20348
  featureClickEvent,
20221
20349
  unitClickEvent,
20222
20350
  tileClickEvent,
20351
+ playAgainEvent,
20223
20352
  className
20224
20353
  }) {
20225
20354
  const eventBus = useEventBus();
20355
+ const { t } = hooks.useTranslate();
20226
20356
  const resolved = boardEntity(entity);
20227
20357
  const tiles = propTiles ?? (Array.isArray(resolved?.tiles) ? resolved.tiles : []);
20228
20358
  const features = propFeatures ?? (Array.isArray(resolved?.features) ? resolved.features : []);
20229
20359
  const units = propUnits ?? (Array.isArray(resolved?.units) ? resolved.units : []);
20230
20360
  const assetManifest = propAssetManifest ?? resolved?.assetManifest;
20231
20361
  const backgroundImage = resolved?.backgroundImage;
20362
+ const gold = num(resolved?.gold);
20363
+ const health = num(resolved?.health);
20364
+ const maxHealth = num(resolved?.maxHealth);
20365
+ const wave = num(resolved?.wave);
20366
+ const tickCount = num(resolved?.tickCount);
20367
+ const buildings = rows(resolved?.buildings);
20368
+ const result = str(resolved?.result) || "none";
20232
20369
  const [hoveredTile, setHoveredTile] = React83.useState(null);
20233
20370
  const [selectedFeature, setSelectedFeature] = React83.useState(null);
20234
20371
  const hoveredFeature = React83.useMemo(() => {
@@ -20241,7 +20378,7 @@ function CastleBoard({
20241
20378
  (u) => u.position?.x === hoveredTile.x && u.position?.y === hoveredTile.y
20242
20379
  ) ?? null;
20243
20380
  }, [hoveredTile, units]);
20244
- const maxY = Math.max(...tiles.map((t) => t.y), 0);
20381
+ const maxY = Math.max(...tiles.map((t2) => t2.y), 0);
20245
20382
  const baseOffsetX = (maxY + 1) * (TILE_WIDTH * scale / 2);
20246
20383
  const tileToScreen = React83.useCallback(
20247
20384
  (tx, ty) => isoToScreen(tx, ty, scale, baseOffsetX),
@@ -20276,6 +20413,11 @@ function CastleBoard({
20276
20413
  }
20277
20414
  }, [units, onUnitClick, unitClickEvent, eventBus]);
20278
20415
  const clearSelection = React83.useCallback(() => setSelectedFeature(null), []);
20416
+ const handlePlayAgain = React83.useCallback(() => {
20417
+ if (playAgainEvent) {
20418
+ eventBus.emit(`UI:${playAgainEvent}`, {});
20419
+ }
20420
+ }, [playAgainEvent, eventBus]);
20279
20421
  const ctx = React83.useMemo(
20280
20422
  () => ({
20281
20423
  hoveredTile,
@@ -20284,11 +20426,18 @@ function CastleBoard({
20284
20426
  selectedFeature,
20285
20427
  clearSelection,
20286
20428
  tileToScreen,
20287
- scale
20429
+ scale,
20430
+ gold,
20431
+ health,
20432
+ maxHealth,
20433
+ wave,
20434
+ tickCount,
20435
+ buildings,
20436
+ result
20288
20437
  }),
20289
- [hoveredTile, hoveredFeature, hoveredUnit, selectedFeature, clearSelection, tileToScreen, scale]
20438
+ [hoveredTile, hoveredFeature, hoveredUnit, selectedFeature, clearSelection, tileToScreen, scale, gold, health, maxHealth, wave, tickCount, buildings, result]
20290
20439
  );
20291
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("castle-board min-h-screen flex flex-col bg-background", className), children: [
20440
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("castle-board relative min-h-screen flex flex-col bg-background", className), children: [
20292
20441
  header && header(ctx),
20293
20442
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 overflow-hidden", children: [
20294
20443
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 overflow-auto p-4 relative", children: [
@@ -20313,7 +20462,29 @@ function CastleBoard({
20313
20462
  ] }),
20314
20463
  sidePanel && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-96 shrink-0 border-l border-border bg-surface overflow-y-auto", children: sidePanel(ctx) })
20315
20464
  ] }),
20316
- footer && footer(ctx)
20465
+ footer && footer(ctx),
20466
+ result !== "none" && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "absolute inset-0 z-50 flex items-center justify-center bg-background/70 backdrop-blur-sm rounded-container", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { className: "text-center p-8", gap: "lg", children: [
20467
+ /* @__PURE__ */ jsxRuntime.jsx(
20468
+ Typography,
20469
+ {
20470
+ variant: "h2",
20471
+ className: cn(
20472
+ "text-4xl font-black tracking-widest uppercase",
20473
+ result === "victory" ? "text-warning" : "text-error"
20474
+ ),
20475
+ children: result === "victory" ? t("battle.victory") : t("battle.defeat")
20476
+ }
20477
+ ),
20478
+ /* @__PURE__ */ jsxRuntime.jsx(
20479
+ Button,
20480
+ {
20481
+ variant: "primary",
20482
+ className: "px-8 py-3 font-semibold",
20483
+ onClick: handlePlayAgain,
20484
+ children: t("battle.playAgain")
20485
+ }
20486
+ )
20487
+ ] }) })
20317
20488
  ] });
20318
20489
  }
20319
20490
  var init_CastleBoard = __esm({
@@ -20321,6 +20492,10 @@ var init_CastleBoard = __esm({
20321
20492
  "use client";
20322
20493
  init_cn();
20323
20494
  init_useEventBus();
20495
+ init_Box();
20496
+ init_Button();
20497
+ init_Typography();
20498
+ init_Stack();
20324
20499
  init_IsometricCanvas();
20325
20500
  init_boardEntity();
20326
20501
  init_isometric();
@@ -21197,22 +21372,16 @@ function ClassifierBoard({
21197
21372
  const { emit } = useEventBus();
21198
21373
  const { t } = hooks.useTranslate();
21199
21374
  const resolved = boardEntity(entity);
21200
- const [localAssignments, setLocalAssignments] = React83.useState({});
21201
21375
  const [headerError, setHeaderError] = React83.useState(false);
21202
- const [localSubmitted, setLocalSubmitted] = React83.useState(false);
21203
- const [localAttempts, setLocalAttempts] = React83.useState(0);
21204
- const [showHint, setShowHint] = React83.useState(false);
21205
21376
  const items = Array.isArray(resolved?.items) ? resolved.items : [];
21206
21377
  const categories = Array.isArray(resolved?.categories) ? resolved.categories : [];
21207
- const entityResult = str(resolved?.result);
21208
- const entityDrivesResult = entityResult.length > 0;
21209
- const entityHasAssignments = items.some((item) => item.assignedCategory != null);
21210
- const assignments = entityHasAssignments ? items.reduce((acc, item) => {
21211
- if (item.assignedCategory != null) acc[item.id] = item.assignedCategory;
21378
+ const result = str(resolved?.result);
21379
+ const submitted = result === "win";
21380
+ const attempts = num(resolved?.attempts);
21381
+ const assignments = items.reduce((acc, item) => {
21382
+ if (item.assignedCategory != null && item.assignedCategory !== "") acc[item.id] = item.assignedCategory;
21212
21383
  return acc;
21213
- }, {}) : localAssignments;
21214
- const attempts = entityDrivesResult ? num(resolved?.attempts) : localAttempts;
21215
- const submitted = entityDrivesResult || localSubmitted;
21384
+ }, {});
21216
21385
  const unassignedItems = items.filter((item) => !assignments[item.id]);
21217
21386
  const allAssigned = items.length > 0 && Object.keys(assignments).length === items.length;
21218
21387
  const results = submitted ? items.map((item) => ({
@@ -21220,54 +21389,25 @@ function ClassifierBoard({
21220
21389
  assigned: assignments[item.id],
21221
21390
  correct: assignments[item.id] === item.correctCategory
21222
21391
  })) : [];
21223
- const allCorrect = entityDrivesResult ? entityResult === "success" : results.length > 0 && results.every((r) => r.correct);
21392
+ const allCorrect = result === "win";
21224
21393
  const correctCount = results.filter((r) => r.correct).length;
21394
+ const showHint = attempts >= 2 && Boolean(str(resolved?.hint));
21225
21395
  const handleAssign = (itemId, categoryId) => {
21226
21396
  if (submitted) return;
21227
- if (assignEvent) {
21228
- emit(`UI:${assignEvent}`, { itemId, categoryId });
21229
- }
21230
- if (!entityHasAssignments) {
21231
- setLocalAssignments((prev) => ({ ...prev, [itemId]: categoryId }));
21232
- }
21397
+ if (assignEvent) emit(`UI:${assignEvent}`, { itemId, categoryId });
21233
21398
  };
21234
21399
  const handleUnassign = (itemId) => {
21235
21400
  if (submitted) return;
21236
- if (!entityHasAssignments) {
21237
- setLocalAssignments((prev) => {
21238
- const next = { ...prev };
21239
- delete next[itemId];
21240
- return next;
21241
- });
21242
- }
21401
+ if (assignEvent) emit(`UI:${assignEvent}`, { itemId, categoryId: "" });
21243
21402
  };
21244
- const handleSubmit = React83.useCallback(() => {
21245
- if (checkEvent) {
21246
- emit(`UI:${checkEvent}`, {});
21247
- }
21248
- if (!entityDrivesResult) {
21249
- setLocalSubmitted(true);
21250
- setLocalAttempts((a) => a + 1);
21251
- const correct = items.every((item) => assignments[item.id] === item.correctCategory);
21252
- if (correct) {
21253
- emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
21254
- }
21255
- }
21256
- }, [checkEvent, entityDrivesResult, items, assignments, attempts, completeEvent, emit]);
21257
- const handleReset = () => {
21258
- if (!entityDrivesResult) setLocalSubmitted(false);
21259
- if (attempts >= 2 && str(resolved?.hint)) {
21260
- setShowHint(true);
21403
+ const handleSubmit = () => {
21404
+ if (checkEvent) emit(`UI:${checkEvent}`, {});
21405
+ if (allAssigned && items.every((item) => assignments[item.id] === item.correctCategory)) {
21406
+ emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
21261
21407
  }
21262
21408
  };
21263
21409
  const handleFullReset = () => {
21264
- if (playAgainEvent) {
21265
- emit(`UI:${playAgainEvent}`, {});
21266
- }
21267
- setLocalAssignments({});
21268
- setLocalSubmitted(false);
21269
- setLocalAttempts(0);
21270
- setShowHint(false);
21410
+ if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
21271
21411
  };
21272
21412
  if (!resolved) return null;
21273
21413
  const theme = resolved.theme ?? void 0;
@@ -21306,17 +21446,17 @@ function ClassifierBoard({
21306
21446
  /* @__PURE__ */ jsxRuntime.jsx(Badge, { size: "sm", children: catItems.length })
21307
21447
  ] }),
21308
21448
  /* @__PURE__ */ jsxRuntime.jsx(HStack, { gap: "xs", className: "flex-wrap min-h-[2rem]", children: catItems.map((item) => {
21309
- const result = results.find((r) => r.item.id === item.id);
21449
+ const result2 = results.find((r) => r.item.id === item.id);
21310
21450
  return /* @__PURE__ */ jsxRuntime.jsxs(
21311
21451
  Badge,
21312
21452
  {
21313
21453
  size: "sm",
21314
- className: `cursor-pointer ${result ? result.correct ? "border-success bg-success/10" : "border-error bg-error/10" : ""}`,
21454
+ className: `cursor-pointer ${result2 ? result2.correct ? "border-success bg-success/10" : "border-error bg-error/10" : ""}`,
21315
21455
  onClick: () => handleUnassign(item.id),
21316
21456
  children: [
21317
21457
  item.iconUrl && /* @__PURE__ */ jsxRuntime.jsx("img", { src: item.iconUrl, alt: "", className: "w-3 h-3 object-contain inline-block" }),
21318
21458
  item.label,
21319
- result && /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: result.correct ? LucideIcons2.CheckCircle : LucideIcons2.XCircle, size: "xs", className: result.correct ? "text-success" : "text-error" })
21459
+ result2 && /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: result2.correct ? LucideIcons2.CheckCircle : LucideIcons2.XCircle, size: "xs", className: result2.correct ? "text-success" : "text-error" })
21320
21460
  ]
21321
21461
  },
21322
21462
  item.id
@@ -21345,10 +21485,10 @@ function ClassifierBoard({
21345
21485
  ] }) }),
21346
21486
  showHint && hint && /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: hint }) }),
21347
21487
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", justify: "center", children: [
21348
- !submitted ? /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: !allAssigned, children: [
21488
+ !submitted && /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: !allAssigned, children: [
21349
21489
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: LucideIcons2.Send, size: "sm" }),
21350
21490
  t("classifier.check")
21351
- ] }) : !allCorrect ? /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "primary", onClick: handleReset, children: t("classifier.tryAgain") }) : null,
21491
+ ] }),
21352
21492
  /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "secondary", onClick: handleFullReset, children: [
21353
21493
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: LucideIcons2.RotateCcw, size: "sm" }),
21354
21494
  t("classifier.reset")
@@ -25066,7 +25206,7 @@ var init_Flex = __esm({
25066
25206
  basis,
25067
25207
  className,
25068
25208
  children,
25069
- as: Component = "div"
25209
+ as: Component2 = "div"
25070
25210
  }) => {
25071
25211
  const flexStyle = {};
25072
25212
  if (grow !== void 0 || shrink !== void 0 || basis !== void 0) {
@@ -25078,7 +25218,7 @@ var init_Flex = __esm({
25078
25218
  flexStyle.flexBasis = typeof basis === "number" ? `${basis}px` : basis;
25079
25219
  }
25080
25220
  }
25081
- const Comp = Component;
25221
+ const Comp = Component2;
25082
25222
  return /* @__PURE__ */ jsxRuntime.jsx(
25083
25223
  Comp,
25084
25224
  {
@@ -25198,11 +25338,11 @@ var init_Grid = __esm({
25198
25338
  className,
25199
25339
  style,
25200
25340
  children,
25201
- as: Component = "div"
25341
+ as: Component2 = "div"
25202
25342
  }) => {
25203
25343
  const mergedStyle = rows2 ? { gridTemplateRows: `repeat(${rows2}, minmax(0, 1fr))`, ...style } : style;
25204
25344
  return React83__namespace.default.createElement(
25205
- Component,
25345
+ Component2,
25206
25346
  {
25207
25347
  className: cn(
25208
25348
  "grid",
@@ -27028,6 +27168,7 @@ var init_ProgressDots = __esm({
27028
27168
  }
27029
27169
  });
27030
27170
  function StatBadge({
27171
+ assetUrl = DEFAULT_ASSET_URL12,
27031
27172
  label,
27032
27173
  value = 0,
27033
27174
  max,
@@ -27051,7 +27192,17 @@ function StatBadge({
27051
27192
  className
27052
27193
  ),
27053
27194
  children: [
27054
- icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-shrink-0 text-lg", children: typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: icon, className: "w-4 h-4" }) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon, className: "w-4 h-4" }) }),
27195
+ assetUrl ? /* @__PURE__ */ jsxRuntime.jsx(
27196
+ "img",
27197
+ {
27198
+ src: assetUrl,
27199
+ alt: "",
27200
+ width: 16,
27201
+ height: 16,
27202
+ style: { imageRendering: "pixelated", objectFit: "contain" },
27203
+ className: "flex-shrink-0"
27204
+ }
27205
+ ) : icon ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-shrink-0 text-lg", children: typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: icon, className: "w-4 h-4" }) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon, className: "w-4 h-4" }) }) : null,
27055
27206
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground font-medium", children: label }),
27056
27207
  format === "hearts" && max && /* @__PURE__ */ jsxRuntime.jsx(
27057
27208
  HealthBar,
@@ -27084,7 +27235,7 @@ function StatBadge({
27084
27235
  }
27085
27236
  );
27086
27237
  }
27087
- var sizeMap17, variantMap2;
27238
+ var sizeMap17, variantMap2, DEFAULT_ASSET_URL12;
27088
27239
  var init_StatBadge = __esm({
27089
27240
  "components/game/molecules/StatBadge.tsx"() {
27090
27241
  init_cn();
@@ -27103,6 +27254,7 @@ var init_StatBadge = __esm({
27103
27254
  warning: "bg-warning/15 border-warning/40 text-foreground",
27104
27255
  danger: "bg-error/15 border-error/40 text-foreground"
27105
27256
  };
27257
+ DEFAULT_ASSET_URL12 = "https://almadar-kflow-assets.web.app/shared/effects/particles/light_01.png";
27106
27258
  StatBadge.displayName = "StatBadge";
27107
27259
  }
27108
27260
  });
@@ -27759,6 +27911,7 @@ var init_TurnPanel = __esm({
27759
27911
  }
27760
27912
  });
27761
27913
  function EnemyPlate({
27914
+ assetUrl = DEFAULT_ASSET_URL13,
27762
27915
  name = "Shadow Guard",
27763
27916
  health = 80,
27764
27917
  maxHealth = 100,
@@ -27776,15 +27929,28 @@ function EnemyPlate({
27776
27929
  ),
27777
27930
  children: [
27778
27931
  /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "flex items-center justify-between gap-2", children: [
27779
- /* @__PURE__ */ jsxRuntime.jsx(
27780
- Typography,
27781
- {
27782
- variant: "caption",
27783
- weight: "bold",
27784
- className: "text-[var(--color-foreground)] truncate",
27785
- children: name
27786
- }
27787
- ),
27932
+ /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "flex items-center gap-1.5 min-w-0", children: [
27933
+ assetUrl && /* @__PURE__ */ jsxRuntime.jsx(
27934
+ "img",
27935
+ {
27936
+ src: assetUrl,
27937
+ alt: name,
27938
+ width: 24,
27939
+ height: 24,
27940
+ style: { objectFit: "cover", borderRadius: "50%" },
27941
+ className: "flex-shrink-0"
27942
+ }
27943
+ ),
27944
+ /* @__PURE__ */ jsxRuntime.jsx(
27945
+ Typography,
27946
+ {
27947
+ variant: "caption",
27948
+ weight: "bold",
27949
+ className: "text-[var(--color-foreground)] truncate",
27950
+ children: name
27951
+ }
27952
+ )
27953
+ ] }),
27788
27954
  level != null && /* @__PURE__ */ jsxRuntime.jsxs(Badge, { variant: "neutral", size: "sm", children: [
27789
27955
  "Lv.",
27790
27956
  level
@@ -27829,7 +27995,7 @@ function EnemyPlate({
27829
27995
  }
27830
27996
  );
27831
27997
  }
27832
- var effectVariantMap2, DEFAULT_ENEMY_EFFECTS;
27998
+ var effectVariantMap2, DEFAULT_ENEMY_EFFECTS, DEFAULT_ASSET_URL13;
27833
27999
  var init_EnemyPlate = __esm({
27834
28000
  "components/game/molecules/EnemyPlate.tsx"() {
27835
28001
  "use client";
@@ -27846,6 +28012,7 @@ var init_EnemyPlate = __esm({
27846
28012
  DEFAULT_ENEMY_EFFECTS = [
27847
28013
  { icon: "flame", label: "Burn", variant: "debuff" }
27848
28014
  ];
28015
+ DEFAULT_ASSET_URL13 = "https://almadar-kflow-assets.web.app/shared/portraits/shadow-legion.png";
27849
28016
  EnemyPlate.displayName = "EnemyPlate";
27850
28017
  }
27851
28018
  });
@@ -29377,13 +29544,13 @@ var init_MapView = __esm({
29377
29544
  shadowSize: [41, 41]
29378
29545
  });
29379
29546
  L.Marker.prototype.options.icon = defaultIcon;
29380
- const { useEffect: useEffect73, useRef: useRef68, useCallback: useCallback112, useState: useState104 } = React83__namespace.default;
29547
+ const { useEffect: useEffect79, useRef: useRef74, useCallback: useCallback118, useState: useState109 } = React83__namespace.default;
29381
29548
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
29382
- const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
29549
+ const { useEventBus: useEventBus3 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
29383
29550
  function MapUpdater({ centerLat, centerLng, zoom }) {
29384
29551
  const map = useMap();
29385
- const prevRef = useRef68({ centerLat, centerLng, zoom });
29386
- useEffect73(() => {
29552
+ const prevRef = useRef74({ centerLat, centerLng, zoom });
29553
+ useEffect79(() => {
29387
29554
  const prev = prevRef.current;
29388
29555
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
29389
29556
  map.setView([centerLat, centerLng], zoom);
@@ -29394,7 +29561,7 @@ var init_MapView = __esm({
29394
29561
  }
29395
29562
  function MapClickHandler({ onMapClick }) {
29396
29563
  const map = useMap();
29397
- useEffect73(() => {
29564
+ useEffect79(() => {
29398
29565
  if (!onMapClick) return;
29399
29566
  const handler = (e) => {
29400
29567
  onMapClick(e.latlng.lat, e.latlng.lng);
@@ -29421,9 +29588,9 @@ var init_MapView = __esm({
29421
29588
  className,
29422
29589
  showAttribution = true
29423
29590
  }) {
29424
- const eventBus = useEventBus2();
29425
- const [clickedPosition, setClickedPosition] = useState104(null);
29426
- const handleMapClick = useCallback112((lat, lng) => {
29591
+ const eventBus = useEventBus3();
29592
+ const [clickedPosition, setClickedPosition] = useState109(null);
29593
+ const handleMapClick = useCallback118((lat, lng) => {
29427
29594
  if (showClickedPin) {
29428
29595
  setClickedPosition({ lat, lng });
29429
29596
  }
@@ -29432,7 +29599,7 @@ var init_MapView = __esm({
29432
29599
  eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
29433
29600
  }
29434
29601
  }, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
29435
- const handleMarkerClick = useCallback112((marker) => {
29602
+ const handleMarkerClick = useCallback118((marker) => {
29436
29603
  onMarkerClick?.(marker);
29437
29604
  if (markerClickEvent) {
29438
29605
  eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
@@ -36559,10 +36726,10 @@ var init_Section = __esm({
36559
36726
  children,
36560
36727
  headerClassName,
36561
36728
  contentClassName,
36562
- as: Component = "section"
36729
+ as: Component2 = "section"
36563
36730
  }) => {
36564
36731
  const hasHeader = title || description || action;
36565
- const Comp = Component;
36732
+ const Comp = Component2;
36566
36733
  return /* @__PURE__ */ jsxRuntime.jsxs(
36567
36734
  Comp,
36568
36735
  {
@@ -38663,44 +38830,27 @@ function DebuggerBoard({
38663
38830
  const { t } = hooks.useTranslate();
38664
38831
  const resolved = boardEntity(entity);
38665
38832
  const [headerError, setHeaderError] = React83.useState(false);
38666
- const [showHint, setShowHint] = React83.useState(false);
38667
38833
  const lines = Array.isArray(resolved?.lines) ? resolved.lines : [];
38668
- const result = resolved?.result ?? null;
38834
+ const result = str(resolved?.result) || "none";
38669
38835
  const attempts = num(resolved?.attempts);
38670
- const submitted = result != null;
38836
+ const submitted = result === "win";
38837
+ const showHint = !submitted && attempts >= 2 && Boolean(str(resolved?.hint));
38671
38838
  const bugLines = lines.filter((l) => l.isBug);
38672
38839
  const flaggedLines = lines.filter((l) => l.isFlagged);
38673
38840
  const correctFlags = lines.filter((l) => l.isBug && l.isFlagged);
38674
38841
  const falseFlags = lines.filter((l) => !l.isBug && l.isFlagged);
38675
- const allCorrect = result === "win";
38842
+ const allCorrect = submitted;
38676
38843
  const toggleLine = (lineId) => {
38677
38844
  if (submitted) return;
38678
- if (toggleFlagEvent) {
38679
- emit(`UI:${toggleFlagEvent}`, { lineId });
38680
- }
38845
+ if (toggleFlagEvent) emit(`UI:${toggleFlagEvent}`, { lineId });
38681
38846
  };
38682
38847
  const handleSubmit = React83.useCallback(() => {
38683
- if (checkEvent) {
38684
- emit(`UI:${checkEvent}`, {});
38685
- }
38848
+ if (checkEvent) emit(`UI:${checkEvent}`, {});
38686
38849
  const correct = correctFlags.length === bugLines.length && falseFlags.length === 0;
38687
- if (correct) {
38688
- emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
38689
- }
38850
+ if (correct) emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
38690
38851
  }, [checkEvent, correctFlags.length, bugLines.length, falseFlags.length, attempts, completeEvent, emit]);
38691
38852
  const handleReset = () => {
38692
- if (playAgainEvent) {
38693
- emit(`UI:${playAgainEvent}`, {});
38694
- }
38695
- if (attempts >= 2 && str(resolved?.hint)) {
38696
- setShowHint(true);
38697
- }
38698
- };
38699
- const handleFullReset = () => {
38700
- if (playAgainEvent) {
38701
- emit(`UI:${playAgainEvent}`, {});
38702
- }
38703
- setShowHint(false);
38853
+ if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
38704
38854
  };
38705
38855
  if (!resolved) return null;
38706
38856
  const theme = resolved.theme ?? void 0;
@@ -38724,7 +38874,7 @@ function DebuggerBoard({
38724
38874
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", weight: "bold", children: str(resolved.title) })
38725
38875
  ] }),
38726
38876
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: str(resolved.description) }),
38727
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "text-muted-foreground", children: t("debugger.findBugs", { count: String(num(resolved.bugCount)) }) })
38877
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "text-muted-foreground", children: t("debugger.findBugs", { count: String(lines.filter((l) => l.isBug).length) }) })
38728
38878
  ] }) }),
38729
38879
  /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-0 overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx(VStack, { gap: "none", children: lines.map((line, i) => {
38730
38880
  const isFlagged = !!line.isFlagged;
@@ -38774,11 +38924,11 @@ function DebuggerBoard({
38774
38924
  ] }) }),
38775
38925
  showHint && hint && /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: hint }) }),
38776
38926
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", justify: "center", children: [
38777
- !submitted ? /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: flaggedLines.length === 0, children: [
38927
+ !submitted && /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: flaggedLines.length === 0, children: [
38778
38928
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: LucideIcons2.Send, size: "sm" }),
38779
38929
  t("debugger.submit")
38780
- ] }) : !allCorrect ? /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "primary", onClick: handleReset, children: t("debugger.tryAgain") }) : null,
38781
- /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "secondary", onClick: handleFullReset, children: [
38930
+ ] }),
38931
+ /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "secondary", onClick: handleReset, children: [
38782
38932
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: LucideIcons2.RotateCcw, size: "sm" }),
38783
38933
  t("debugger.reset")
38784
38934
  ] })
@@ -39779,39 +39929,40 @@ function EventHandlerBoard({
39779
39929
  stepDurationMs = 800,
39780
39930
  playEvent,
39781
39931
  completeEvent,
39932
+ editRuleEvent,
39933
+ playAgainEvent,
39782
39934
  className
39783
39935
  }) {
39784
39936
  const { emit } = useEventBus();
39785
39937
  const { t } = hooks.useTranslate();
39786
39938
  const resolved = boardEntity(entity);
39787
- const entityObjects = rows(resolved?.objects);
39788
- const [objects, setObjects] = React83.useState(() => [...entityObjects]);
39939
+ const objects = rows(resolved?.objects);
39940
+ const entityResult = str(resolved?.result) || "none";
39941
+ const isSuccess = entityResult === "win";
39942
+ const attempts = typeof resolved?.attempts === "number" ? resolved.attempts : 0;
39789
39943
  const [selectedObjectId, setSelectedObjectId] = React83.useState(
39790
- entityObjects[0] ? objId(entityObjects[0]) : null
39944
+ objects[0] ? objId(objects[0]) : null
39791
39945
  );
39792
39946
  const [headerError, setHeaderError] = React83.useState(false);
39793
- const [playState, setPlayState] = React83.useState("editing");
39947
+ const [isPlaying, setIsPlaying] = React83.useState(false);
39794
39948
  const [eventLog, setEventLog] = React83.useState([]);
39795
- const [attempts, setAttempts] = React83.useState(0);
39796
39949
  const timerRef = React83.useRef(null);
39797
39950
  const logIdCounter = React83.useRef(0);
39798
39951
  React83.useEffect(() => () => {
39799
39952
  if (timerRef.current) clearTimeout(timerRef.current);
39800
39953
  }, []);
39801
- const selectedObject = objects.find((o) => objId(o) === selectedObjectId) || null;
39954
+ const selectedObject = objects.find((o) => objId(o) === selectedObjectId) ?? null;
39802
39955
  const handleRulesChange = React83.useCallback((objectId, rules) => {
39803
- setObjects((prev) => prev.map(
39804
- (o) => objId(o) === objectId ? { ...o, rules } : o
39805
- ));
39806
- }, []);
39956
+ if (editRuleEvent) emit(`UI:${editRuleEvent}`, { objectId, rules });
39957
+ }, [editRuleEvent, emit]);
39807
39958
  const addLogEntry = React83.useCallback((icon, message, status = "done") => {
39808
39959
  const id = `log-${logIdCounter.current++}`;
39809
39960
  setEventLog((prev) => [...prev, { id, timestamp: Date.now(), icon, message, status }]);
39810
39961
  }, []);
39811
39962
  const handlePlay = React83.useCallback(() => {
39812
- if (playState !== "editing") return;
39963
+ if (isPlaying || isSuccess) return;
39813
39964
  if (playEvent) emit(`UI:${playEvent}`, {});
39814
- setPlayState("playing");
39965
+ setIsPlaying(true);
39815
39966
  setEventLog([]);
39816
39967
  const allRules = [];
39817
39968
  objects.forEach((obj) => {
@@ -39827,15 +39978,8 @@ function EventHandlerBoard({
39827
39978
  let goalReached = false;
39828
39979
  const processNext = () => {
39829
39980
  if (eventQueue.length === 0 || stepIdx > 20) {
39830
- if (goalReached) {
39831
- setPlayState("success");
39832
- if (completeEvent) {
39833
- emit(`UI:${completeEvent}`, { success: true });
39834
- }
39835
- } else {
39836
- setAttempts((prev) => prev + 1);
39837
- setPlayState("fail");
39838
- }
39981
+ setIsPlaying(false);
39982
+ if (goalReached && completeEvent) emit(`UI:${completeEvent}`, { success: true });
39839
39983
  return;
39840
39984
  }
39841
39985
  const currentEvent = eventQueue.shift();
@@ -39866,21 +40010,19 @@ function EventHandlerBoard({
39866
40010
  addLogEntry("\u{1F3AC}", t("eventHandler.simulationStarted", { events: triggers.join(", ") }), "active");
39867
40011
  }
39868
40012
  timerRef.current = setTimeout(processNext, stepDurationMs);
39869
- }, [playState, objects, resolved, stepDurationMs, playEvent, completeEvent, emit, addLogEntry, t]);
40013
+ }, [isPlaying, isSuccess, objects, resolved, stepDurationMs, playEvent, completeEvent, emit, addLogEntry, t]);
39870
40014
  const handleTryAgain = React83.useCallback(() => {
39871
40015
  if (timerRef.current) clearTimeout(timerRef.current);
39872
- setPlayState("editing");
40016
+ setIsPlaying(false);
39873
40017
  setEventLog([]);
39874
40018
  }, []);
39875
40019
  const handleReset = React83.useCallback(() => {
39876
40020
  if (timerRef.current) clearTimeout(timerRef.current);
39877
- const resetObjects = rows(resolved?.objects);
39878
- setObjects([...resetObjects]);
39879
- setPlayState("editing");
40021
+ setIsPlaying(false);
39880
40022
  setEventLog([]);
39881
- setSelectedObjectId(resetObjects[0] ? objId(resetObjects[0]) : null);
39882
- setAttempts(0);
39883
- }, [resolved?.objects]);
40023
+ setSelectedObjectId(objects[0] ? objId(objects[0]) : null);
40024
+ if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
40025
+ }, [objects, playAgainEvent, emit]);
39884
40026
  if (!resolved) return null;
39885
40027
  const objectViewers = objects.map((obj) => {
39886
40028
  const states = objStates(obj);
@@ -39949,12 +40091,12 @@ function EventHandlerBoard({
39949
40091
  {
39950
40092
  object: selectedObject,
39951
40093
  onRulesChange: handleRulesChange,
39952
- disabled: playState !== "editing"
40094
+ disabled: isPlaying
39953
40095
  }
39954
40096
  ),
39955
40097
  eventLog.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(EventLog, { entries: eventLog }),
39956
- playState === "success" && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "p-4 rounded-container bg-success/20 border border-success text-center", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h5", className: "text-success", children: str(resolved.successMessage) || t("eventHandler.chainComplete") }) }),
39957
- playState === "fail" && /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
40098
+ isSuccess && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "p-4 rounded-container bg-success/20 border border-success text-center", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h5", className: "text-success", children: str(resolved.successMessage) || t("eventHandler.chainComplete") }) }),
40099
+ !isPlaying && !isSuccess && attempts > 0 && /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
39958
40100
  /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "p-4 rounded-container bg-warning/10 border border-warning/30 text-center", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body1", className: "text-foreground font-medium", children: t(encourageKey) }) }),
39959
40101
  showHint && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "p-3 rounded-container bg-accent/10 border border-accent/30", children: /* @__PURE__ */ jsxRuntime.jsxs(HStack, { className: "items-start", gap: "xs", children: [
39960
40102
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-accent font-bold shrink-0", children: "\u{1F4A1} " + t("game.hint") + ":" }),
@@ -39962,12 +40104,12 @@ function EventHandlerBoard({
39962
40104
  ] }) })
39963
40105
  ] }),
39964
40106
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", children: [
39965
- playState === "fail" ? /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "primary", onClick: handleTryAgain, children: "\u{1F504} " + t("puzzle.tryAgainButton") }) : /* @__PURE__ */ jsxRuntime.jsx(
40107
+ !isPlaying && !isSuccess && attempts > 0 ? /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "primary", onClick: handleTryAgain, children: "\u{1F504} " + t("puzzle.tryAgainButton") }) : /* @__PURE__ */ jsxRuntime.jsx(
39966
40108
  Button,
39967
40109
  {
39968
40110
  variant: "primary",
39969
40111
  onClick: handlePlay,
39970
- disabled: playState !== "editing",
40112
+ disabled: isPlaying || isSuccess,
39971
40113
  children: "\u25B6 " + t("game.play")
39972
40114
  }
39973
40115
  ),
@@ -41152,6 +41294,1568 @@ var init_GameAudioToggle = __esm({
41152
41294
  GameAudioToggle.displayName = "GameAudioToggle";
41153
41295
  }
41154
41296
  });
41297
+ function detectAssetRoot(modelUrl) {
41298
+ const idx = modelUrl.indexOf("/3d/");
41299
+ if (idx !== -1) {
41300
+ return modelUrl.substring(0, idx + 4);
41301
+ }
41302
+ return modelUrl.substring(0, modelUrl.lastIndexOf("/") + 1);
41303
+ }
41304
+ function createGLTFLoaderForUrl(url) {
41305
+ const loader = new GLTFLoader_js.GLTFLoader();
41306
+ loader.setResourcePath(detectAssetRoot(url));
41307
+ return loader;
41308
+ }
41309
+ var AssetLoader;
41310
+ var init_AssetLoader = __esm({
41311
+ "components/game/molecules/three/loaders/AssetLoader.ts"() {
41312
+ AssetLoader = class {
41313
+ constructor() {
41314
+ __publicField(this, "objLoader");
41315
+ __publicField(this, "textureLoader");
41316
+ __publicField(this, "modelCache");
41317
+ __publicField(this, "textureCache");
41318
+ __publicField(this, "loadingPromises");
41319
+ this.objLoader = new OBJLoader_js.OBJLoader();
41320
+ this.textureLoader = new THREE__namespace.TextureLoader();
41321
+ this.modelCache = /* @__PURE__ */ new Map();
41322
+ this.textureCache = /* @__PURE__ */ new Map();
41323
+ this.loadingPromises = /* @__PURE__ */ new Map();
41324
+ }
41325
+ /**
41326
+ * Load a GLB/GLTF model
41327
+ * @param url - URL to the .glb or .gltf file
41328
+ * @returns Promise with loaded model scene and animations
41329
+ */
41330
+ async loadModel(url) {
41331
+ if (this.modelCache.has(url)) {
41332
+ return this.modelCache.get(url);
41333
+ }
41334
+ if (this.loadingPromises.has(url)) {
41335
+ return this.loadingPromises.get(url);
41336
+ }
41337
+ const loader = createGLTFLoaderForUrl(url);
41338
+ const loadPromise = loader.loadAsync(url).then((gltf) => {
41339
+ const result = {
41340
+ scene: gltf.scene,
41341
+ animations: gltf.animations || []
41342
+ };
41343
+ this.modelCache.set(url, result);
41344
+ this.loadingPromises.delete(url);
41345
+ return result;
41346
+ }).catch((error) => {
41347
+ this.loadingPromises.delete(url);
41348
+ throw new Error(`Failed to load model ${url}: ${error.message}`);
41349
+ });
41350
+ this.loadingPromises.set(url, loadPromise);
41351
+ return loadPromise;
41352
+ }
41353
+ /**
41354
+ * Load an OBJ model (fallback for non-GLB assets)
41355
+ * @param url - URL to the .obj file
41356
+ * @returns Promise with loaded object group
41357
+ */
41358
+ async loadOBJ(url) {
41359
+ if (this.modelCache.has(url)) {
41360
+ return this.modelCache.get(url).scene;
41361
+ }
41362
+ if (this.loadingPromises.has(url)) {
41363
+ const result = await this.loadingPromises.get(url);
41364
+ return result.scene;
41365
+ }
41366
+ const loadPromise = this.objLoader.loadAsync(url).then((group) => {
41367
+ const result = {
41368
+ scene: group,
41369
+ animations: []
41370
+ };
41371
+ this.modelCache.set(url, result);
41372
+ this.loadingPromises.delete(url);
41373
+ return result;
41374
+ }).catch((error) => {
41375
+ this.loadingPromises.delete(url);
41376
+ throw new Error(`Failed to load OBJ ${url}: ${error.message}`);
41377
+ });
41378
+ this.loadingPromises.set(url, loadPromise);
41379
+ return (await loadPromise).scene;
41380
+ }
41381
+ /**
41382
+ * Load a texture
41383
+ * @param url - URL to the texture image
41384
+ * @returns Promise with loaded texture
41385
+ */
41386
+ async loadTexture(url) {
41387
+ if (this.textureCache.has(url)) {
41388
+ return this.textureCache.get(url);
41389
+ }
41390
+ if (this.loadingPromises.has(`texture:${url}`)) {
41391
+ return this.loadingPromises.get(`texture:${url}`);
41392
+ }
41393
+ const loadPromise = this.textureLoader.loadAsync(url).then((texture) => {
41394
+ texture.colorSpace = THREE__namespace.SRGBColorSpace;
41395
+ this.textureCache.set(url, texture);
41396
+ this.loadingPromises.delete(`texture:${url}`);
41397
+ return texture;
41398
+ }).catch((error) => {
41399
+ this.loadingPromises.delete(`texture:${url}`);
41400
+ throw new Error(`Failed to load texture ${url}: ${error.message}`);
41401
+ });
41402
+ this.loadingPromises.set(`texture:${url}`, loadPromise);
41403
+ return loadPromise;
41404
+ }
41405
+ /**
41406
+ * Preload multiple assets
41407
+ * @param urls - Array of asset URLs to preload
41408
+ * @returns Promise that resolves when all assets are loaded
41409
+ */
41410
+ async preload(urls) {
41411
+ const promises = urls.map((url) => {
41412
+ if (url.endsWith(".glb") || url.endsWith(".gltf")) {
41413
+ return this.loadModel(url).catch(() => null);
41414
+ } else if (url.endsWith(".obj")) {
41415
+ return this.loadOBJ(url).catch(() => null);
41416
+ } else if (/\.(png|jpg|jpeg|webp)$/i.test(url)) {
41417
+ return this.loadTexture(url).catch(() => null);
41418
+ }
41419
+ return Promise.resolve(null);
41420
+ });
41421
+ await Promise.all(promises);
41422
+ }
41423
+ /**
41424
+ * Check if a model is cached
41425
+ * @param url - Model URL
41426
+ */
41427
+ hasModel(url) {
41428
+ return this.modelCache.has(url);
41429
+ }
41430
+ /**
41431
+ * Check if a texture is cached
41432
+ * @param url - Texture URL
41433
+ */
41434
+ hasTexture(url) {
41435
+ return this.textureCache.has(url);
41436
+ }
41437
+ /**
41438
+ * Get cached model (throws if not cached)
41439
+ * @param url - Model URL
41440
+ */
41441
+ getModel(url) {
41442
+ const model = this.modelCache.get(url);
41443
+ if (!model) {
41444
+ throw new Error(`Model ${url} not in cache`);
41445
+ }
41446
+ return model;
41447
+ }
41448
+ /**
41449
+ * Get cached texture (throws if not cached)
41450
+ * @param url - Texture URL
41451
+ */
41452
+ getTexture(url) {
41453
+ const texture = this.textureCache.get(url);
41454
+ if (!texture) {
41455
+ throw new Error(`Texture ${url} not in cache`);
41456
+ }
41457
+ return texture;
41458
+ }
41459
+ /**
41460
+ * Clear all caches
41461
+ */
41462
+ clearCache() {
41463
+ this.textureCache.forEach((texture) => {
41464
+ texture.dispose();
41465
+ });
41466
+ this.modelCache.forEach((model) => {
41467
+ model.scene.traverse((child) => {
41468
+ if (child instanceof THREE__namespace.Mesh) {
41469
+ child.geometry.dispose();
41470
+ if (Array.isArray(child.material)) {
41471
+ child.material.forEach((m) => m.dispose());
41472
+ } else {
41473
+ child.material.dispose();
41474
+ }
41475
+ }
41476
+ });
41477
+ });
41478
+ this.modelCache.clear();
41479
+ this.textureCache.clear();
41480
+ this.loadingPromises.clear();
41481
+ }
41482
+ /**
41483
+ * Get cache statistics
41484
+ */
41485
+ getStats() {
41486
+ return {
41487
+ models: this.modelCache.size,
41488
+ textures: this.textureCache.size,
41489
+ loading: this.loadingPromises.size
41490
+ };
41491
+ }
41492
+ };
41493
+ new AssetLoader();
41494
+ }
41495
+ });
41496
+ function useAssetLoader(options = {}) {
41497
+ const { preloadUrls = [], loader: customLoader } = options;
41498
+ const loaderRef = React83.useRef(customLoader || new AssetLoader());
41499
+ const [state, setState] = React83.useState({
41500
+ isLoading: false,
41501
+ progress: 0,
41502
+ loaded: 0,
41503
+ total: 0,
41504
+ errors: []
41505
+ });
41506
+ React83.useEffect(() => {
41507
+ if (preloadUrls.length > 0) {
41508
+ preload(preloadUrls);
41509
+ }
41510
+ }, []);
41511
+ const updateProgress = React83.useCallback((loaded, total) => {
41512
+ setState((prev) => ({
41513
+ ...prev,
41514
+ loaded,
41515
+ total,
41516
+ progress: total > 0 ? Math.round(loaded / total * 100) : 0
41517
+ }));
41518
+ }, []);
41519
+ const loadModel = React83.useCallback(
41520
+ async (url) => {
41521
+ setState((prev) => ({ ...prev, isLoading: true }));
41522
+ try {
41523
+ const model = await loaderRef.current.loadModel(url);
41524
+ setState((prev) => ({
41525
+ ...prev,
41526
+ isLoading: false,
41527
+ loaded: prev.loaded + 1
41528
+ }));
41529
+ return model;
41530
+ } catch (error) {
41531
+ const errorMsg = error instanceof Error ? error.message : String(error);
41532
+ setState((prev) => ({
41533
+ ...prev,
41534
+ isLoading: false,
41535
+ errors: [...prev.errors, errorMsg]
41536
+ }));
41537
+ throw error;
41538
+ }
41539
+ },
41540
+ []
41541
+ );
41542
+ const loadOBJ = React83.useCallback(
41543
+ async (url) => {
41544
+ setState((prev) => ({ ...prev, isLoading: true }));
41545
+ try {
41546
+ const model = await loaderRef.current.loadOBJ(url);
41547
+ setState((prev) => ({
41548
+ ...prev,
41549
+ isLoading: false,
41550
+ loaded: prev.loaded + 1
41551
+ }));
41552
+ return model;
41553
+ } catch (error) {
41554
+ const errorMsg = error instanceof Error ? error.message : String(error);
41555
+ setState((prev) => ({
41556
+ ...prev,
41557
+ isLoading: false,
41558
+ errors: [...prev.errors, errorMsg]
41559
+ }));
41560
+ throw error;
41561
+ }
41562
+ },
41563
+ []
41564
+ );
41565
+ const loadTexture = React83.useCallback(
41566
+ async (url) => {
41567
+ setState((prev) => ({ ...prev, isLoading: true }));
41568
+ try {
41569
+ const texture = await loaderRef.current.loadTexture(url);
41570
+ setState((prev) => ({
41571
+ ...prev,
41572
+ isLoading: false,
41573
+ loaded: prev.loaded + 1
41574
+ }));
41575
+ return texture;
41576
+ } catch (error) {
41577
+ const errorMsg = error instanceof Error ? error.message : String(error);
41578
+ setState((prev) => ({
41579
+ ...prev,
41580
+ isLoading: false,
41581
+ errors: [...prev.errors, errorMsg]
41582
+ }));
41583
+ throw error;
41584
+ }
41585
+ },
41586
+ []
41587
+ );
41588
+ const preload = React83.useCallback(
41589
+ async (urls) => {
41590
+ setState((prev) => ({
41591
+ ...prev,
41592
+ isLoading: true,
41593
+ total: urls.length,
41594
+ loaded: 0,
41595
+ errors: []
41596
+ }));
41597
+ let completed = 0;
41598
+ const errors = [];
41599
+ await Promise.all(
41600
+ urls.map(async (url) => {
41601
+ try {
41602
+ if (url.endsWith(".glb") || url.endsWith(".gltf")) {
41603
+ await loaderRef.current.loadModel(url);
41604
+ } else if (url.endsWith(".obj")) {
41605
+ await loaderRef.current.loadOBJ(url);
41606
+ } else if (/\.(png|jpg|jpeg|webp)$/i.test(url)) {
41607
+ await loaderRef.current.loadTexture(url);
41608
+ }
41609
+ completed++;
41610
+ updateProgress(completed, urls.length);
41611
+ } catch (error) {
41612
+ const errorMsg = error instanceof Error ? error.message : String(error);
41613
+ errors.push(`${url}: ${errorMsg}`);
41614
+ completed++;
41615
+ updateProgress(completed, urls.length);
41616
+ }
41617
+ })
41618
+ );
41619
+ setState((prev) => ({
41620
+ ...prev,
41621
+ isLoading: false,
41622
+ errors
41623
+ }));
41624
+ },
41625
+ [updateProgress]
41626
+ );
41627
+ const hasModel = React83.useCallback((url) => {
41628
+ return loaderRef.current.hasModel(url);
41629
+ }, []);
41630
+ const hasTexture = React83.useCallback((url) => {
41631
+ return loaderRef.current.hasTexture(url);
41632
+ }, []);
41633
+ const getModel = React83.useCallback((url) => {
41634
+ try {
41635
+ return loaderRef.current.getModel(url);
41636
+ } catch {
41637
+ return void 0;
41638
+ }
41639
+ }, []);
41640
+ const getTexture = React83.useCallback((url) => {
41641
+ try {
41642
+ return loaderRef.current.getTexture(url);
41643
+ } catch {
41644
+ return void 0;
41645
+ }
41646
+ }, []);
41647
+ const clearCache = React83.useCallback(() => {
41648
+ loaderRef.current.clearCache();
41649
+ setState({
41650
+ isLoading: false,
41651
+ progress: 0,
41652
+ loaded: 0,
41653
+ total: 0,
41654
+ errors: []
41655
+ });
41656
+ }, []);
41657
+ return {
41658
+ ...state,
41659
+ loadModel,
41660
+ loadOBJ,
41661
+ loadTexture,
41662
+ preload,
41663
+ hasModel,
41664
+ hasTexture,
41665
+ getModel,
41666
+ getTexture,
41667
+ clearCache
41668
+ };
41669
+ }
41670
+ var init_useAssetLoader = __esm({
41671
+ "components/game/molecules/three/hooks/useAssetLoader.ts"() {
41672
+ "use client";
41673
+ init_AssetLoader();
41674
+ }
41675
+ });
41676
+ function useGameCanvas3DEvents(options) {
41677
+ const {
41678
+ tileClickEvent,
41679
+ unitClickEvent,
41680
+ featureClickEvent,
41681
+ canvasClickEvent,
41682
+ tileHoverEvent,
41683
+ tileLeaveEvent,
41684
+ unitAnimationEvent,
41685
+ cameraChangeEvent,
41686
+ onTileClick,
41687
+ onUnitClick,
41688
+ onFeatureClick,
41689
+ onCanvasClick,
41690
+ onTileHover,
41691
+ onUnitAnimation
41692
+ } = options;
41693
+ const emit = useEmitEvent();
41694
+ const optionsRef = React83.useRef(options);
41695
+ optionsRef.current = options;
41696
+ const handleTileClick = React83.useCallback(
41697
+ (tile, event) => {
41698
+ if (tileClickEvent) {
41699
+ emit(tileClickEvent, {
41700
+ tileId: tile.id,
41701
+ x: tile.x,
41702
+ z: tile.z ?? tile.y ?? 0,
41703
+ type: tile.type,
41704
+ terrain: tile.terrain,
41705
+ elevation: tile.elevation
41706
+ });
41707
+ }
41708
+ optionsRef.current.onTileClick?.(tile, event);
41709
+ },
41710
+ [tileClickEvent, emit]
41711
+ );
41712
+ const handleUnitClick = React83.useCallback(
41713
+ (unit, event) => {
41714
+ if (unitClickEvent) {
41715
+ emit(unitClickEvent, {
41716
+ unitId: unit.id,
41717
+ x: unit.x,
41718
+ z: unit.z ?? unit.y ?? 0,
41719
+ unitType: unit.unitType,
41720
+ name: unit.name,
41721
+ team: unit.team,
41722
+ faction: unit.faction,
41723
+ health: unit.health,
41724
+ maxHealth: unit.maxHealth
41725
+ });
41726
+ }
41727
+ optionsRef.current.onUnitClick?.(unit, event);
41728
+ },
41729
+ [unitClickEvent, emit]
41730
+ );
41731
+ const handleFeatureClick = React83.useCallback(
41732
+ (feature, event) => {
41733
+ if (featureClickEvent) {
41734
+ emit(featureClickEvent, {
41735
+ featureId: feature.id,
41736
+ x: feature.x,
41737
+ z: feature.z ?? feature.y ?? 0,
41738
+ type: feature.type,
41739
+ elevation: feature.elevation
41740
+ });
41741
+ }
41742
+ optionsRef.current.onFeatureClick?.(feature, event);
41743
+ },
41744
+ [featureClickEvent, emit]
41745
+ );
41746
+ const handleCanvasClick = React83.useCallback(
41747
+ (event) => {
41748
+ if (canvasClickEvent) {
41749
+ emit(canvasClickEvent, {
41750
+ clientX: event.clientX,
41751
+ clientY: event.clientY,
41752
+ button: event.button
41753
+ });
41754
+ }
41755
+ optionsRef.current.onCanvasClick?.(event);
41756
+ },
41757
+ [canvasClickEvent, emit]
41758
+ );
41759
+ const handleTileHover = React83.useCallback(
41760
+ (tile, event) => {
41761
+ if (tile) {
41762
+ if (tileHoverEvent) {
41763
+ emit(tileHoverEvent, {
41764
+ tileId: tile.id,
41765
+ x: tile.x,
41766
+ z: tile.z ?? tile.y ?? 0,
41767
+ type: tile.type
41768
+ });
41769
+ }
41770
+ } else {
41771
+ if (tileLeaveEvent) {
41772
+ emit(tileLeaveEvent, {});
41773
+ }
41774
+ }
41775
+ optionsRef.current.onTileHover?.(tile, event);
41776
+ },
41777
+ [tileHoverEvent, tileLeaveEvent, emit]
41778
+ );
41779
+ const handleUnitAnimation = React83.useCallback(
41780
+ (unitId, state) => {
41781
+ if (unitAnimationEvent) {
41782
+ emit(unitAnimationEvent, {
41783
+ unitId,
41784
+ state,
41785
+ timestamp: Date.now()
41786
+ });
41787
+ }
41788
+ optionsRef.current.onUnitAnimation?.(unitId, state);
41789
+ },
41790
+ [unitAnimationEvent, emit]
41791
+ );
41792
+ const handleCameraChange = React83.useCallback(
41793
+ (position) => {
41794
+ if (cameraChangeEvent) {
41795
+ emit(cameraChangeEvent, {
41796
+ position,
41797
+ timestamp: Date.now()
41798
+ });
41799
+ }
41800
+ },
41801
+ [cameraChangeEvent, emit]
41802
+ );
41803
+ return {
41804
+ handleTileClick,
41805
+ handleUnitClick,
41806
+ handleFeatureClick,
41807
+ handleCanvasClick,
41808
+ handleTileHover,
41809
+ handleUnitAnimation,
41810
+ handleCameraChange
41811
+ };
41812
+ }
41813
+ var init_useGameCanvas3DEvents = __esm({
41814
+ "components/game/molecules/three/hooks/useGameCanvas3DEvents.ts"() {
41815
+ "use client";
41816
+ init_useEventBus();
41817
+ }
41818
+ });
41819
+
41820
+ // components/game/molecules/three/components/Canvas3DLoadingState.css
41821
+ var init_Canvas3DLoadingState = __esm({
41822
+ "components/game/molecules/three/components/Canvas3DLoadingState.css"() {
41823
+ }
41824
+ });
41825
+ function Canvas3DLoadingState({
41826
+ progress = 0,
41827
+ loaded = 0,
41828
+ total = 0,
41829
+ message = "Loading 3D Scene...",
41830
+ details,
41831
+ showSpinner = true,
41832
+ className
41833
+ }) {
41834
+ const clampedProgress = Math.max(0, Math.min(100, progress));
41835
+ const hasProgress = total > 0;
41836
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `canvas-3d-loading ${className || ""}`, children: [
41837
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "canvas-3d-loading__content", children: [
41838
+ showSpinner && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "canvas-3d-loading__spinner", children: [
41839
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "spinner__ring" }),
41840
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "spinner__ring spinner__ring--secondary" })
41841
+ ] }),
41842
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "canvas-3d-loading__message", children: message }),
41843
+ details && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "canvas-3d-loading__details", children: details }),
41844
+ hasProgress && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "canvas-3d-loading__progress", children: [
41845
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "progress__bar", children: /* @__PURE__ */ jsxRuntime.jsx(
41846
+ "div",
41847
+ {
41848
+ className: "progress__fill",
41849
+ style: { width: `${clampedProgress}%` }
41850
+ }
41851
+ ) }),
41852
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "progress__text", children: [
41853
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "progress__percentage", children: [
41854
+ clampedProgress,
41855
+ "%"
41856
+ ] }),
41857
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "progress__count", children: [
41858
+ "(",
41859
+ loaded,
41860
+ "/",
41861
+ total,
41862
+ ")"
41863
+ ] })
41864
+ ] })
41865
+ ] })
41866
+ ] }),
41867
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "canvas-3d-loading__background", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg__grid" }) })
41868
+ ] });
41869
+ }
41870
+ var init_Canvas3DLoadingState2 = __esm({
41871
+ "components/game/molecules/three/components/Canvas3DLoadingState.tsx"() {
41872
+ init_Canvas3DLoadingState();
41873
+ }
41874
+ });
41875
+
41876
+ // components/game/molecules/three/components/Canvas3DErrorBoundary.css
41877
+ var init_Canvas3DErrorBoundary = __esm({
41878
+ "components/game/molecules/three/components/Canvas3DErrorBoundary.css"() {
41879
+ }
41880
+ });
41881
+ var log11, Canvas3DErrorBoundary;
41882
+ var init_Canvas3DErrorBoundary2 = __esm({
41883
+ "components/game/molecules/three/components/Canvas3DErrorBoundary.tsx"() {
41884
+ init_Canvas3DErrorBoundary();
41885
+ log11 = logger.createLogger("almadar:ui:game:canvas3d:error-boundary");
41886
+ Canvas3DErrorBoundary = class extends React83.Component {
41887
+ constructor(props) {
41888
+ super(props);
41889
+ __publicField(this, "handleReset", () => {
41890
+ this.setState({
41891
+ hasError: false,
41892
+ error: null,
41893
+ errorInfo: null
41894
+ });
41895
+ this.props.onReset?.();
41896
+ });
41897
+ this.state = {
41898
+ hasError: false,
41899
+ error: null,
41900
+ errorInfo: null
41901
+ };
41902
+ }
41903
+ static getDerivedStateFromError(error) {
41904
+ return {
41905
+ hasError: true,
41906
+ error,
41907
+ errorInfo: null
41908
+ };
41909
+ }
41910
+ componentDidCatch(error, errorInfo) {
41911
+ this.setState({ errorInfo });
41912
+ this.props.onError?.(error, errorInfo);
41913
+ log11.error("Error caught", { error });
41914
+ log11.error("Component stack", { componentStack: errorInfo.componentStack ?? "<none>" });
41915
+ }
41916
+ render() {
41917
+ if (this.state.hasError) {
41918
+ if (this.props.fallback) {
41919
+ return this.props.fallback;
41920
+ }
41921
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "canvas-3d-error", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "canvas-3d-error__content", children: [
41922
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "canvas-3d-error__icon", children: "\u26A0\uFE0F" }),
41923
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "canvas-3d-error__title", children: "3D Scene Error" }),
41924
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "canvas-3d-error__message", children: "Something went wrong while rendering the 3D scene." }),
41925
+ this.state.error && /* @__PURE__ */ jsxRuntime.jsxs("details", { className: "canvas-3d-error__details", children: [
41926
+ /* @__PURE__ */ jsxRuntime.jsx("summary", { children: "Error Details" }),
41927
+ /* @__PURE__ */ jsxRuntime.jsxs("pre", { className: "error__stack", children: [
41928
+ this.state.error.message,
41929
+ "\n",
41930
+ this.state.error.stack
41931
+ ] }),
41932
+ this.state.errorInfo && /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "error__component-stack", children: this.state.errorInfo.componentStack })
41933
+ ] }),
41934
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "canvas-3d-error__actions", children: [
41935
+ /* @__PURE__ */ jsxRuntime.jsx(
41936
+ "button",
41937
+ {
41938
+ className: "error__button error__button--primary",
41939
+ onClick: this.handleReset,
41940
+ children: "Try Again"
41941
+ }
41942
+ ),
41943
+ /* @__PURE__ */ jsxRuntime.jsx(
41944
+ "button",
41945
+ {
41946
+ className: "error__button error__button--secondary",
41947
+ onClick: () => window.location.reload(),
41948
+ children: "Reload Page"
41949
+ }
41950
+ )
41951
+ ] })
41952
+ ] }) });
41953
+ }
41954
+ return this.props.children;
41955
+ }
41956
+ };
41957
+ }
41958
+ });
41959
+ function detectAssetRoot2(modelUrl) {
41960
+ const idx = modelUrl.indexOf("/3d/");
41961
+ if (idx !== -1) {
41962
+ return modelUrl.substring(0, idx + 4);
41963
+ }
41964
+ return modelUrl.substring(0, modelUrl.lastIndexOf("/") + 1);
41965
+ }
41966
+ function useGLTFModel(url, resourceBasePath) {
41967
+ const [state, setState] = React83.useState({
41968
+ model: null,
41969
+ isLoading: false,
41970
+ error: null
41971
+ });
41972
+ React83.useEffect(() => {
41973
+ if (!url) {
41974
+ setState({ model: null, isLoading: false, error: null });
41975
+ return;
41976
+ }
41977
+ log12.debug("Loading", { url });
41978
+ setState((prev) => ({ ...prev, isLoading: true, error: null }));
41979
+ const assetRoot = resourceBasePath || detectAssetRoot2(url);
41980
+ const loader = new GLTFLoader.GLTFLoader();
41981
+ loader.setResourcePath(assetRoot);
41982
+ loader.load(
41983
+ url,
41984
+ (gltf) => {
41985
+ log12.debug("Loaded", { url });
41986
+ setState({
41987
+ model: gltf.scene,
41988
+ isLoading: false,
41989
+ error: null
41990
+ });
41991
+ },
41992
+ void 0,
41993
+ (err) => {
41994
+ log12.warn("Failed", { url, error: err instanceof Error ? err : String(err) });
41995
+ setState({
41996
+ model: null,
41997
+ isLoading: false,
41998
+ error: err instanceof Error ? err : new Error(String(err))
41999
+ });
42000
+ }
42001
+ );
42002
+ }, [url, resourceBasePath]);
42003
+ return state;
42004
+ }
42005
+ function ModelLoader({
42006
+ url,
42007
+ position = [0, 0, 0],
42008
+ scale = 1,
42009
+ rotation = [0, 0, 0],
42010
+ isSelected = false,
42011
+ isHovered = false,
42012
+ onClick,
42013
+ onHover,
42014
+ fallbackGeometry = "box",
42015
+ castShadow = true,
42016
+ receiveShadow = true,
42017
+ resourceBasePath
42018
+ }) {
42019
+ const { model: loadedModel, isLoading, error } = useGLTFModel(url, resourceBasePath);
42020
+ const model = React83.useMemo(() => {
42021
+ if (!loadedModel) return null;
42022
+ const cloned = loadedModel.clone();
42023
+ cloned.traverse((child) => {
42024
+ if (child instanceof THREE__namespace.Mesh) {
42025
+ child.castShadow = castShadow;
42026
+ child.receiveShadow = receiveShadow;
42027
+ }
42028
+ });
42029
+ return cloned;
42030
+ }, [loadedModel, castShadow, receiveShadow]);
42031
+ const scaleArray = React83.useMemo(() => {
42032
+ if (typeof scale === "number") {
42033
+ return [scale, scale, scale];
42034
+ }
42035
+ return scale;
42036
+ }, [scale]);
42037
+ const rotationRad = React83.useMemo(() => {
42038
+ return [
42039
+ rotation[0] * Math.PI / 180,
42040
+ rotation[1] * Math.PI / 180,
42041
+ rotation[2] * Math.PI / 180
42042
+ ];
42043
+ }, [rotation]);
42044
+ if (isLoading) {
42045
+ return /* @__PURE__ */ jsxRuntime.jsx("group", { position, children: /* @__PURE__ */ jsxRuntime.jsxs("mesh", { rotation: [Math.PI / 2, 0, 0], children: [
42046
+ /* @__PURE__ */ jsxRuntime.jsx("ringGeometry", { args: [0.3, 0.35, 16] }),
42047
+ /* @__PURE__ */ jsxRuntime.jsx("meshBasicMaterial", { color: "#4a90d9", transparent: true, opacity: 0.8 })
42048
+ ] }) });
42049
+ }
42050
+ if (error || !model) {
42051
+ if (fallbackGeometry === "none") {
42052
+ return /* @__PURE__ */ jsxRuntime.jsx("group", { position });
42053
+ }
42054
+ const fallbackProps = {
42055
+ onClick,
42056
+ onPointerOver: () => onHover?.(true),
42057
+ onPointerOut: () => onHover?.(false)
42058
+ };
42059
+ return /* @__PURE__ */ jsxRuntime.jsxs("group", { position, children: [
42060
+ (isSelected || isHovered) && /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, 0.02, 0], rotation: [-Math.PI / 2, 0, 0], children: [
42061
+ /* @__PURE__ */ jsxRuntime.jsx("ringGeometry", { args: [0.6, 0.7, 32] }),
42062
+ /* @__PURE__ */ jsxRuntime.jsx(
42063
+ "meshBasicMaterial",
42064
+ {
42065
+ color: isSelected ? 16755200 : 16777215,
42066
+ transparent: true,
42067
+ opacity: 0.5
42068
+ }
42069
+ )
42070
+ ] }),
42071
+ fallbackGeometry === "box" && /* @__PURE__ */ jsxRuntime.jsxs("mesh", { ...fallbackProps, position: [0, 0.5, 0], children: [
42072
+ /* @__PURE__ */ jsxRuntime.jsx("boxGeometry", { args: [0.8, 0.8, 0.8] }),
42073
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color: error ? 16729156 : 8947848 })
42074
+ ] }),
42075
+ fallbackGeometry === "sphere" && /* @__PURE__ */ jsxRuntime.jsxs("mesh", { ...fallbackProps, position: [0, 0.5, 0], children: [
42076
+ /* @__PURE__ */ jsxRuntime.jsx("sphereGeometry", { args: [0.4, 16, 16] }),
42077
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color: error ? 16729156 : 8947848 })
42078
+ ] }),
42079
+ fallbackGeometry === "cylinder" && /* @__PURE__ */ jsxRuntime.jsxs("mesh", { ...fallbackProps, position: [0, 0.5, 0], children: [
42080
+ /* @__PURE__ */ jsxRuntime.jsx("cylinderGeometry", { args: [0.3, 0.3, 0.8, 16] }),
42081
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color: error ? 16729156 : 8947848 })
42082
+ ] })
42083
+ ] });
42084
+ }
42085
+ return /* @__PURE__ */ jsxRuntime.jsxs(
42086
+ "group",
42087
+ {
42088
+ position,
42089
+ rotation: rotationRad,
42090
+ onClick,
42091
+ onPointerOver: () => onHover?.(true),
42092
+ onPointerOut: () => onHover?.(false),
42093
+ children: [
42094
+ (isSelected || isHovered) && /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, 0.02, 0], rotation: [-Math.PI / 2, 0, 0], children: [
42095
+ /* @__PURE__ */ jsxRuntime.jsx("ringGeometry", { args: [0.6, 0.7, 32] }),
42096
+ /* @__PURE__ */ jsxRuntime.jsx(
42097
+ "meshBasicMaterial",
42098
+ {
42099
+ color: isSelected ? 16755200 : 16777215,
42100
+ transparent: true,
42101
+ opacity: 0.5
42102
+ }
42103
+ )
42104
+ ] }),
42105
+ /* @__PURE__ */ jsxRuntime.jsx("primitive", { object: model, scale: scaleArray })
42106
+ ]
42107
+ }
42108
+ );
42109
+ }
42110
+ var log12;
42111
+ var init_ModelLoader = __esm({
42112
+ "components/game/molecules/three/components/ModelLoader.tsx"() {
42113
+ "use client";
42114
+ log12 = logger.createLogger("almadar:ui:game:model-loader");
42115
+ }
42116
+ });
42117
+
42118
+ // components/game/molecules/GameCanvas3D.css
42119
+ var init_GameCanvas3D = __esm({
42120
+ "components/game/molecules/GameCanvas3D.css"() {
42121
+ }
42122
+ });
42123
+ function CameraController({
42124
+ onCameraChange
42125
+ }) {
42126
+ const { camera } = fiber.useThree();
42127
+ React83.useEffect(() => {
42128
+ if (onCameraChange) {
42129
+ onCameraChange({
42130
+ x: camera.position.x,
42131
+ y: camera.position.y,
42132
+ z: camera.position.z
42133
+ });
42134
+ }
42135
+ }, [camera.position, onCameraChange]);
42136
+ return null;
42137
+ }
42138
+ var DEFAULT_GRID_CONFIG, GameCanvas3D;
42139
+ var init_GameCanvas3D2 = __esm({
42140
+ "components/game/molecules/GameCanvas3D.tsx"() {
42141
+ "use client";
42142
+ init_AssetLoader();
42143
+ init_useAssetLoader();
42144
+ init_useGameCanvas3DEvents();
42145
+ init_Canvas3DLoadingState2();
42146
+ init_Canvas3DErrorBoundary2();
42147
+ init_ModelLoader();
42148
+ init_cn();
42149
+ init_GameCanvas3D();
42150
+ DEFAULT_GRID_CONFIG = {
42151
+ cellSize: 1,
42152
+ offsetX: 0,
42153
+ offsetZ: 0
42154
+ };
42155
+ GameCanvas3D = React83.forwardRef(
42156
+ ({
42157
+ tiles = [],
42158
+ units = [],
42159
+ features = [],
42160
+ events: events2 = [],
42161
+ orientation = "standard",
42162
+ cameraMode = "isometric",
42163
+ showGrid = true,
42164
+ showCoordinates = false,
42165
+ showTileInfo = false,
42166
+ overlay = "default",
42167
+ shadows = true,
42168
+ backgroundColor = "#1a1a2e",
42169
+ onTileClick,
42170
+ onUnitClick,
42171
+ onFeatureClick,
42172
+ onCanvasClick,
42173
+ onTileHover,
42174
+ onUnitAnimation,
42175
+ assetLoader: customAssetLoader,
42176
+ tileRenderer: CustomTileRenderer,
42177
+ unitRenderer: CustomUnitRenderer,
42178
+ featureRenderer: CustomFeatureRenderer,
42179
+ className,
42180
+ isLoading: externalLoading,
42181
+ error: externalError,
42182
+ entity,
42183
+ preloadAssets = [],
42184
+ tileClickEvent,
42185
+ unitClickEvent,
42186
+ featureClickEvent,
42187
+ canvasClickEvent,
42188
+ tileHoverEvent,
42189
+ tileLeaveEvent,
42190
+ unitAnimationEvent,
42191
+ cameraChangeEvent,
42192
+ loadingMessage = "Loading 3D Scene...",
42193
+ useInstancing = true,
42194
+ validMoves = [],
42195
+ attackTargets = [],
42196
+ selectedTileIds = [],
42197
+ selectedUnitId = null,
42198
+ children
42199
+ }, ref) => {
42200
+ const containerRef = React83.useRef(null);
42201
+ const controlsRef = React83.useRef(null);
42202
+ const [hoveredTile, setHoveredTile] = React83.useState(null);
42203
+ const [internalError, setInternalError] = React83.useState(null);
42204
+ const { isLoading: assetsLoading, progress, loaded, total } = useAssetLoader({
42205
+ preloadUrls: preloadAssets,
42206
+ loader: customAssetLoader
42207
+ });
42208
+ const eventHandlers = useGameCanvas3DEvents({
42209
+ tileClickEvent,
42210
+ unitClickEvent,
42211
+ featureClickEvent,
42212
+ canvasClickEvent,
42213
+ tileHoverEvent,
42214
+ tileLeaveEvent,
42215
+ unitAnimationEvent,
42216
+ cameraChangeEvent,
42217
+ onTileClick,
42218
+ onUnitClick,
42219
+ onFeatureClick,
42220
+ onCanvasClick,
42221
+ onTileHover,
42222
+ onUnitAnimation
42223
+ });
42224
+ const gridBounds = React83.useMemo(() => {
42225
+ if (tiles.length === 0) {
42226
+ return { minX: 0, maxX: 10, minZ: 0, maxZ: 10 };
42227
+ }
42228
+ const xs = tiles.map((t) => t.x);
42229
+ const zs = tiles.map((t) => t.z || t.y || 0);
42230
+ return {
42231
+ minX: Math.min(...xs),
42232
+ maxX: Math.max(...xs),
42233
+ minZ: Math.min(...zs),
42234
+ maxZ: Math.max(...zs)
42235
+ };
42236
+ }, [tiles]);
42237
+ const cameraTarget = React83.useMemo(() => {
42238
+ return [
42239
+ (gridBounds.minX + gridBounds.maxX) / 2,
42240
+ 0,
42241
+ (gridBounds.minZ + gridBounds.maxZ) / 2
42242
+ ];
42243
+ }, [gridBounds]);
42244
+ const gridConfig = React83.useMemo(
42245
+ () => ({
42246
+ ...DEFAULT_GRID_CONFIG,
42247
+ offsetX: -(gridBounds.maxX - gridBounds.minX) / 2,
42248
+ offsetZ: -(gridBounds.maxZ - gridBounds.minZ) / 2
42249
+ }),
42250
+ [gridBounds]
42251
+ );
42252
+ const gridToWorld = React83.useCallback(
42253
+ (x, z, y = 0) => {
42254
+ const worldX = (x - gridBounds.minX) * gridConfig.cellSize;
42255
+ const worldZ = (z - gridBounds.minZ) * gridConfig.cellSize;
42256
+ return [worldX, y * gridConfig.cellSize, worldZ];
42257
+ },
42258
+ [gridBounds, gridConfig]
42259
+ );
42260
+ React83.useImperativeHandle(ref, () => ({
42261
+ getCameraPosition: () => {
42262
+ if (controlsRef.current) {
42263
+ const pos = controlsRef.current.object.position;
42264
+ return new THREE__namespace.Vector3(pos.x, pos.y, pos.z);
42265
+ }
42266
+ return null;
42267
+ },
42268
+ setCameraPosition: (x, y, z) => {
42269
+ if (controlsRef.current) {
42270
+ controlsRef.current.object.position.set(x, y, z);
42271
+ controlsRef.current.update();
42272
+ }
42273
+ },
42274
+ lookAt: (x, y, z) => {
42275
+ if (controlsRef.current) {
42276
+ controlsRef.current.target.set(x, y, z);
42277
+ controlsRef.current.update();
42278
+ }
42279
+ },
42280
+ resetCamera: () => {
42281
+ if (controlsRef.current) {
42282
+ controlsRef.current.reset();
42283
+ }
42284
+ },
42285
+ screenshot: () => {
42286
+ const canvas = containerRef.current?.querySelector("canvas");
42287
+ if (canvas) {
42288
+ return canvas.toDataURL("image/png");
42289
+ }
42290
+ return null;
42291
+ },
42292
+ export: () => ({
42293
+ tiles,
42294
+ units,
42295
+ features
42296
+ })
42297
+ }));
42298
+ const handleTileClick = React83.useCallback(
42299
+ (tile, event) => {
42300
+ eventHandlers.handleTileClick(tile, event);
42301
+ },
42302
+ [eventHandlers]
42303
+ );
42304
+ const handleUnitClick = React83.useCallback(
42305
+ (unit, event) => {
42306
+ eventHandlers.handleUnitClick(unit, event);
42307
+ },
42308
+ [eventHandlers]
42309
+ );
42310
+ const handleFeatureClick = React83.useCallback(
42311
+ (feature, event) => {
42312
+ if (event) {
42313
+ eventHandlers.handleFeatureClick(feature, event);
42314
+ }
42315
+ },
42316
+ [eventHandlers]
42317
+ );
42318
+ const handleTileHover = React83.useCallback(
42319
+ (tile, event) => {
42320
+ setHoveredTile(tile);
42321
+ if (event) {
42322
+ eventHandlers.handleTileHover(tile, event);
42323
+ }
42324
+ },
42325
+ [eventHandlers]
42326
+ );
42327
+ const cameraConfig = React83.useMemo(() => {
42328
+ const size = Math.max(
42329
+ gridBounds.maxX - gridBounds.minX,
42330
+ gridBounds.maxZ - gridBounds.minZ
42331
+ );
42332
+ const distance = size * 1.5;
42333
+ switch (cameraMode) {
42334
+ case "isometric":
42335
+ return {
42336
+ position: [distance, distance * 0.8, distance],
42337
+ fov: 45
42338
+ };
42339
+ case "top-down":
42340
+ return {
42341
+ position: [0, distance * 2, 0],
42342
+ fov: 45
42343
+ };
42344
+ case "perspective":
42345
+ default:
42346
+ return {
42347
+ position: [distance, distance, distance],
42348
+ fov: 45
42349
+ };
42350
+ }
42351
+ }, [cameraMode, gridBounds]);
42352
+ const DefaultTileRenderer = React83.useCallback(
42353
+ ({ tile, position }) => {
42354
+ const isSelected = tile.id ? selectedTileIds.includes(tile.id) : false;
42355
+ const isHovered = hoveredTile?.id === tile.id;
42356
+ const isValidMove = validMoves.some(
42357
+ (m) => m.x === tile.x && m.z === (tile.z ?? tile.y ?? 0)
42358
+ );
42359
+ const isAttackTarget = attackTargets.some(
42360
+ (m) => m.x === tile.x && m.z === (tile.z ?? tile.y ?? 0)
42361
+ );
42362
+ let color = 8421504;
42363
+ if (tile.type === "water") color = 4491468;
42364
+ else if (tile.type === "grass") color = 4500036;
42365
+ else if (tile.type === "sand") color = 14535816;
42366
+ else if (tile.type === "rock") color = 8947848;
42367
+ else if (tile.type === "snow") color = 15658734;
42368
+ let emissive = 0;
42369
+ if (isSelected) emissive = 4473924;
42370
+ else if (isAttackTarget) emissive = 4456448;
42371
+ else if (isValidMove) emissive = 17408;
42372
+ else if (isHovered) emissive = 2236962;
42373
+ if (tile.modelUrl) {
42374
+ return /* @__PURE__ */ jsxRuntime.jsx(
42375
+ "group",
42376
+ {
42377
+ position,
42378
+ onClick: (e) => handleTileClick(tile, e),
42379
+ onPointerEnter: (e) => handleTileHover(tile, e),
42380
+ onPointerLeave: (e) => handleTileHover(null, e),
42381
+ userData: { type: "tile", tileId: tile.id, gridX: tile.x, gridZ: tile.z ?? tile.y },
42382
+ children: /* @__PURE__ */ jsxRuntime.jsx(
42383
+ ModelLoader,
42384
+ {
42385
+ url: tile.modelUrl,
42386
+ scale: 0.95,
42387
+ fallbackGeometry: "box",
42388
+ castShadow: true,
42389
+ receiveShadow: true
42390
+ }
42391
+ )
42392
+ }
42393
+ );
42394
+ }
42395
+ return /* @__PURE__ */ jsxRuntime.jsxs(
42396
+ "mesh",
42397
+ {
42398
+ position,
42399
+ onClick: (e) => handleTileClick(tile, e),
42400
+ onPointerEnter: (e) => handleTileHover(tile, e),
42401
+ onPointerLeave: (e) => handleTileHover(null, e),
42402
+ userData: { type: "tile", tileId: tile.id, gridX: tile.x, gridZ: tile.z ?? tile.y },
42403
+ children: [
42404
+ /* @__PURE__ */ jsxRuntime.jsx("boxGeometry", { args: [0.95, 0.2, 0.95] }),
42405
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color, emissive })
42406
+ ]
42407
+ }
42408
+ );
42409
+ },
42410
+ [selectedTileIds, hoveredTile, validMoves, attackTargets, handleTileClick, handleTileHover]
42411
+ );
42412
+ const DefaultUnitRenderer = React83.useCallback(
42413
+ ({ unit, position }) => {
42414
+ const isSelected = selectedUnitId === unit.id;
42415
+ const color = unit.faction === "player" ? 4491519 : unit.faction === "enemy" ? 16729156 : 16777028;
42416
+ return /* @__PURE__ */ jsxRuntime.jsxs(
42417
+ "group",
42418
+ {
42419
+ position,
42420
+ onClick: (e) => handleUnitClick(unit, e),
42421
+ userData: { type: "unit", unitId: unit.id },
42422
+ children: [
42423
+ isSelected && /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, 0.05, 0], rotation: [-Math.PI / 2, 0, 0], children: [
42424
+ /* @__PURE__ */ jsxRuntime.jsx("ringGeometry", { args: [0.4, 0.5, 32] }),
42425
+ /* @__PURE__ */ jsxRuntime.jsx("meshBasicMaterial", { color: "#ffff00", transparent: true, opacity: 0.8 })
42426
+ ] }),
42427
+ unit.modelUrl ? (
42428
+ /* GLB unit model (box fallback while loading / on error) */
42429
+ /* @__PURE__ */ jsxRuntime.jsx(
42430
+ ModelLoader,
42431
+ {
42432
+ url: unit.modelUrl,
42433
+ scale: 0.5,
42434
+ fallbackGeometry: "box",
42435
+ castShadow: true
42436
+ }
42437
+ )
42438
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
42439
+ /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, 0.3, 0], children: [
42440
+ /* @__PURE__ */ jsxRuntime.jsx("cylinderGeometry", { args: [0.3, 0.3, 0.1, 8] }),
42441
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color })
42442
+ ] }),
42443
+ /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, 0.6, 0], children: [
42444
+ /* @__PURE__ */ jsxRuntime.jsx("capsuleGeometry", { args: [0.2, 0.4, 4, 8] }),
42445
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color })
42446
+ ] }),
42447
+ /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, 0.9, 0], children: [
42448
+ /* @__PURE__ */ jsxRuntime.jsx("sphereGeometry", { args: [0.12, 8, 8] }),
42449
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color })
42450
+ ] })
42451
+ ] }),
42452
+ unit.health !== void 0 && unit.maxHealth !== void 0 && /* @__PURE__ */ jsxRuntime.jsxs("group", { position: [0, 1.2, 0], children: [
42453
+ /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [-0.25, 0, 0], children: [
42454
+ /* @__PURE__ */ jsxRuntime.jsx("planeGeometry", { args: [0.5, 0.05] }),
42455
+ /* @__PURE__ */ jsxRuntime.jsx("meshBasicMaterial", { color: 3355443 })
42456
+ ] }),
42457
+ /* @__PURE__ */ jsxRuntime.jsxs(
42458
+ "mesh",
42459
+ {
42460
+ position: [
42461
+ -0.25 + 0.5 * (unit.health / unit.maxHealth) / 2,
42462
+ 0,
42463
+ 0.01
42464
+ ],
42465
+ children: [
42466
+ /* @__PURE__ */ jsxRuntime.jsx("planeGeometry", { args: [0.5 * (unit.health / unit.maxHealth), 0.05] }),
42467
+ /* @__PURE__ */ jsxRuntime.jsx(
42468
+ "meshBasicMaterial",
42469
+ {
42470
+ color: unit.health / unit.maxHealth > 0.5 ? 4500036 : unit.health / unit.maxHealth > 0.25 ? 11184708 : 16729156
42471
+ }
42472
+ )
42473
+ ]
42474
+ }
42475
+ )
42476
+ ] })
42477
+ ]
42478
+ }
42479
+ );
42480
+ },
42481
+ [selectedUnitId, handleUnitClick]
42482
+ );
42483
+ const DefaultFeatureRenderer = React83.useCallback(
42484
+ ({
42485
+ feature,
42486
+ position
42487
+ }) => {
42488
+ if (feature.assetUrl) {
42489
+ return /* @__PURE__ */ jsxRuntime.jsx(
42490
+ ModelLoader,
42491
+ {
42492
+ url: feature.assetUrl,
42493
+ position,
42494
+ scale: 0.5,
42495
+ rotation: [0, feature.rotation ?? 0, 0],
42496
+ onClick: () => handleFeatureClick(feature, null),
42497
+ fallbackGeometry: "box"
42498
+ },
42499
+ feature.id
42500
+ );
42501
+ }
42502
+ if (feature.type === "tree") {
42503
+ return /* @__PURE__ */ jsxRuntime.jsxs(
42504
+ "group",
42505
+ {
42506
+ position,
42507
+ onClick: (e) => handleFeatureClick(feature, e),
42508
+ userData: { type: "feature", featureId: feature.id },
42509
+ children: [
42510
+ /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, 0.4, 0], children: [
42511
+ /* @__PURE__ */ jsxRuntime.jsx("cylinderGeometry", { args: [0.1, 0.15, 0.8, 6] }),
42512
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color: 9127187 })
42513
+ ] }),
42514
+ /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, 0.9, 0], children: [
42515
+ /* @__PURE__ */ jsxRuntime.jsx("coneGeometry", { args: [0.5, 0.8, 8] }),
42516
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color: 2263842 })
42517
+ ] })
42518
+ ]
42519
+ }
42520
+ );
42521
+ }
42522
+ if (feature.type === "rock") {
42523
+ return /* @__PURE__ */ jsxRuntime.jsxs(
42524
+ "mesh",
42525
+ {
42526
+ position: [position[0], position[1] + 0.3, position[2]],
42527
+ onClick: (e) => handleFeatureClick(feature, e),
42528
+ userData: { type: "feature", featureId: feature.id },
42529
+ children: [
42530
+ /* @__PURE__ */ jsxRuntime.jsx("dodecahedronGeometry", { args: [0.3, 0] }),
42531
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color: 8421504 })
42532
+ ]
42533
+ }
42534
+ );
42535
+ }
42536
+ return null;
42537
+ },
42538
+ [handleFeatureClick]
42539
+ );
42540
+ if (externalLoading || assetsLoading && preloadAssets.length > 0) {
42541
+ return /* @__PURE__ */ jsxRuntime.jsx(
42542
+ Canvas3DLoadingState,
42543
+ {
42544
+ progress,
42545
+ loaded,
42546
+ total,
42547
+ message: loadingMessage,
42548
+ className
42549
+ }
42550
+ );
42551
+ }
42552
+ const displayError = externalError || internalError;
42553
+ if (displayError) {
42554
+ return /* @__PURE__ */ jsxRuntime.jsx(Canvas3DErrorBoundary, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "game-canvas-3d game-canvas-3d--error", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "game-canvas-3d__error", children: [
42555
+ "Error: ",
42556
+ displayError
42557
+ ] }) }) });
42558
+ }
42559
+ return /* @__PURE__ */ jsxRuntime.jsx(
42560
+ Canvas3DErrorBoundary,
42561
+ {
42562
+ onError: (err) => setInternalError(err.message),
42563
+ onReset: () => setInternalError(null),
42564
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
42565
+ "div",
42566
+ {
42567
+ ref: containerRef,
42568
+ className: cn("game-canvas-3d relative w-full h-full min-h-[85vh] overflow-hidden", className),
42569
+ "data-orientation": orientation,
42570
+ "data-camera-mode": cameraMode,
42571
+ "data-overlay": overlay,
42572
+ children: [
42573
+ /* @__PURE__ */ jsxRuntime.jsxs(
42574
+ fiber.Canvas,
42575
+ {
42576
+ shadows,
42577
+ camera: {
42578
+ position: cameraConfig.position,
42579
+ fov: cameraConfig.fov,
42580
+ near: 0.1,
42581
+ far: 1e3
42582
+ },
42583
+ style: { background: backgroundColor },
42584
+ onClick: (e) => {
42585
+ if (e.target === e.currentTarget) {
42586
+ eventHandlers.handleCanvasClick(e);
42587
+ }
42588
+ },
42589
+ children: [
42590
+ /* @__PURE__ */ jsxRuntime.jsx(CameraController, { onCameraChange: eventHandlers.handleCameraChange }),
42591
+ /* @__PURE__ */ jsxRuntime.jsx("ambientLight", { intensity: 0.6 }),
42592
+ /* @__PURE__ */ jsxRuntime.jsx(
42593
+ "directionalLight",
42594
+ {
42595
+ position: [10, 20, 10],
42596
+ intensity: 0.8,
42597
+ castShadow: shadows,
42598
+ "shadow-mapSize": [2048, 2048]
42599
+ }
42600
+ ),
42601
+ /* @__PURE__ */ jsxRuntime.jsx("hemisphereLight", { intensity: 0.3, color: "#87ceeb", groundColor: "#362d1d" }),
42602
+ showGrid && /* @__PURE__ */ jsxRuntime.jsx(
42603
+ drei.Grid,
42604
+ {
42605
+ args: [
42606
+ Math.max(gridBounds.maxX - gridBounds.minX + 2, 10),
42607
+ Math.max(gridBounds.maxZ - gridBounds.minZ + 2, 10)
42608
+ ],
42609
+ position: [
42610
+ (gridBounds.maxX - gridBounds.minX) / 2 - 0.5,
42611
+ 0,
42612
+ (gridBounds.maxZ - gridBounds.minZ) / 2 - 0.5
42613
+ ],
42614
+ cellSize: 1,
42615
+ cellThickness: 1,
42616
+ cellColor: "#444444",
42617
+ sectionSize: 5,
42618
+ sectionThickness: 1.5,
42619
+ sectionColor: "#666666",
42620
+ fadeDistance: 50,
42621
+ fadeStrength: 1
42622
+ }
42623
+ ),
42624
+ tiles.map((tile, index) => {
42625
+ const position = gridToWorld(
42626
+ tile.x,
42627
+ tile.z ?? tile.y ?? 0,
42628
+ tile.elevation ?? 0
42629
+ );
42630
+ const Renderer = CustomTileRenderer || DefaultTileRenderer;
42631
+ return /* @__PURE__ */ jsxRuntime.jsx(Renderer, { tile, position }, tile.id ?? `tile-${index}`);
42632
+ }),
42633
+ features.map((feature, index) => {
42634
+ const position = gridToWorld(
42635
+ feature.x,
42636
+ feature.z ?? feature.y ?? 0,
42637
+ (feature.elevation ?? 0) + 0.5
42638
+ );
42639
+ const Renderer = CustomFeatureRenderer || DefaultFeatureRenderer;
42640
+ return /* @__PURE__ */ jsxRuntime.jsx(Renderer, { feature, position }, feature.id ?? `feature-${index}`);
42641
+ }),
42642
+ units.map((unit) => {
42643
+ const position = gridToWorld(
42644
+ unit.x ?? 0,
42645
+ unit.z ?? unit.y ?? 0,
42646
+ (unit.elevation ?? 0) + 0.5
42647
+ );
42648
+ const Renderer = CustomUnitRenderer || DefaultUnitRenderer;
42649
+ return /* @__PURE__ */ jsxRuntime.jsx(Renderer, { unit, position }, unit.id);
42650
+ }),
42651
+ children,
42652
+ /* @__PURE__ */ jsxRuntime.jsx(
42653
+ drei.OrbitControls,
42654
+ {
42655
+ ref: controlsRef,
42656
+ target: cameraTarget,
42657
+ enableDamping: true,
42658
+ dampingFactor: 0.05,
42659
+ minDistance: 2,
42660
+ maxDistance: 100,
42661
+ maxPolarAngle: Math.PI / 2 - 0.1
42662
+ }
42663
+ )
42664
+ ]
42665
+ }
42666
+ ),
42667
+ showCoordinates && hoveredTile && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "game-canvas-3d__coordinates", children: [
42668
+ "X: ",
42669
+ hoveredTile.x,
42670
+ ", Z: ",
42671
+ hoveredTile.z ?? hoveredTile.y ?? 0
42672
+ ] }),
42673
+ showTileInfo && hoveredTile && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "game-canvas-3d__tile-info", children: [
42674
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "tile-info__type", children: hoveredTile.type }),
42675
+ hoveredTile.terrain && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "tile-info__terrain", children: hoveredTile.terrain })
42676
+ ] })
42677
+ ]
42678
+ }
42679
+ )
42680
+ }
42681
+ );
42682
+ }
42683
+ );
42684
+ GameCanvas3D.displayName = "GameCanvas3D";
42685
+ }
42686
+ });
42687
+ function GameBoard3D({
42688
+ entity,
42689
+ tiles = [],
42690
+ features = [],
42691
+ cameraMode = "perspective",
42692
+ backgroundColor = "#2a1a1a",
42693
+ tileClickEvent,
42694
+ unitClickEvent,
42695
+ attackEvent,
42696
+ endTurnEvent,
42697
+ cancelEvent,
42698
+ playAgainEvent,
42699
+ gameEndEvent,
42700
+ className
42701
+ }) {
42702
+ const row = boardEntity(entity);
42703
+ const eventBus = useEventBus();
42704
+ const entityUnits = row ? rows(row.units) : [];
42705
+ const units = entityUnits;
42706
+ const selectedUnitId = row ? str(row.selectedUnitId) || null : null;
42707
+ const phase = row ? str(row.phase) : "observation";
42708
+ const result = row ? str(row.result) : "none";
42709
+ const validMoves = row && Array.isArray(row.validMoves) ? row.validMoves : [];
42710
+ const attackTargets = row && Array.isArray(row.attackTargets) ? row.attackTargets : [];
42711
+ const turn = row ? num(row.turn) : 0;
42712
+ const currentTeam = row ? str(row.currentTeam) : "player";
42713
+ const isGameOver = result !== "none";
42714
+ const gameEndEmittedRef = React83.useRef(false);
42715
+ React83.useEffect(() => {
42716
+ if ((result === "victory" || result === "defeat") && gameEndEvent) {
42717
+ if (!gameEndEmittedRef.current) {
42718
+ gameEndEmittedRef.current = true;
42719
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
42720
+ }
42721
+ } else {
42722
+ gameEndEmittedRef.current = false;
42723
+ }
42724
+ }, [result, gameEndEvent, eventBus]);
42725
+ const checkGameEnd = React83.useCallback(() => {
42726
+ const alivePlayer = entityUnits.filter((u) => unitTeam(u) === "player" && unitHealth(u) > 0);
42727
+ const aliveEnemy = entityUnits.filter((u) => unitTeam(u) === "enemy" && unitHealth(u) > 0);
42728
+ if (alivePlayer.length === 0 && gameEndEvent) {
42729
+ eventBus.emit(`UI:${gameEndEvent}`, { result: "defeat" });
42730
+ } else if (aliveEnemy.length === 0 && gameEndEvent) {
42731
+ eventBus.emit(`UI:${gameEndEvent}`, { result: "victory" });
42732
+ }
42733
+ }, [entityUnits, gameEndEvent, eventBus]);
42734
+ const handleUnitClickCallback = React83.useCallback((isoUnit) => {
42735
+ if (phase !== "action" || !selectedUnitId || !attackEvent) return;
42736
+ const attackerRow = entityUnits.find((u) => str(u.id) === selectedUnitId);
42737
+ const targetRow = entityUnits.find((u) => str(u.id) === isoUnit.id);
42738
+ if (!attackerRow || !targetRow) return;
42739
+ if (unitTeam(targetRow) !== "enemy" || unitHealth(targetRow) <= 0) return;
42740
+ const ap = unitPosition(attackerRow);
42741
+ const tp = unitPosition(targetRow);
42742
+ const dx = Math.abs(ap.x - tp.x);
42743
+ const dy = Math.abs(ap.y - tp.y);
42744
+ if (dx <= 1 && dy <= 1 && dx + dy > 0) {
42745
+ const damage = Math.max(1, num(attackerRow.attack) - num(targetRow.defense));
42746
+ eventBus.emit(`UI:${attackEvent}`, {
42747
+ attackerId: str(attackerRow.id),
42748
+ targetId: str(targetRow.id),
42749
+ damage
42750
+ });
42751
+ setTimeout(checkGameEnd, 100);
42752
+ }
42753
+ }, [phase, selectedUnitId, entityUnits, attackEvent, eventBus, checkGameEnd]);
42754
+ const handleEndTurn = React83.useCallback(() => {
42755
+ if (endTurnEvent) eventBus.emit(`UI:${endTurnEvent}`, {});
42756
+ }, [endTurnEvent, eventBus]);
42757
+ const handleCancel = React83.useCallback(() => {
42758
+ if (cancelEvent) eventBus.emit(`UI:${cancelEvent}`, {});
42759
+ }, [cancelEvent, eventBus]);
42760
+ const handlePlayAgain = React83.useCallback(() => {
42761
+ if (playAgainEvent) eventBus.emit(`UI:${playAgainEvent}`, {});
42762
+ }, [playAgainEvent, eventBus]);
42763
+ return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { className: cn("game-board-3d block w-full min-h-[85vh] relative", className), children: [
42764
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "game-board-3d__status absolute top-3 left-3 z-10 flex gap-2 items-center", children: [
42765
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", className: "status__phase capitalize", children: phase.replace("_", " ") }),
42766
+ currentTeam && !isGameOver && /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "small", color: "muted", className: "status__team", children: [
42767
+ "\u2014 ",
42768
+ currentTeam === "player" ? "Your Turn" : "Enemy's Turn"
42769
+ ] }),
42770
+ /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "small", color: "muted", className: "status__turn", children: [
42771
+ "Turn ",
42772
+ turn
42773
+ ] })
42774
+ ] }),
42775
+ /* @__PURE__ */ jsxRuntime.jsx(
42776
+ GameCanvas3D,
42777
+ {
42778
+ tiles,
42779
+ units,
42780
+ features,
42781
+ cameraMode,
42782
+ showGrid: true,
42783
+ showCoordinates: false,
42784
+ showTileInfo: false,
42785
+ shadows: true,
42786
+ backgroundColor,
42787
+ tileClickEvent,
42788
+ unitClickEvent,
42789
+ onUnitClick: handleUnitClickCallback,
42790
+ selectedUnitId,
42791
+ validMoves,
42792
+ attackTargets,
42793
+ className: "game-board-3d__canvas w-full min-h-[85vh]"
42794
+ }
42795
+ ),
42796
+ !isGameOver && /* @__PURE__ */ jsxRuntime.jsxs(HStack, { className: "fixed bottom-6 right-6 z-50", gap: "sm", children: [
42797
+ (phase === "selection" || phase === "movement" || phase === "action") && /* @__PURE__ */ jsxRuntime.jsx(
42798
+ Button,
42799
+ {
42800
+ variant: "secondary",
42801
+ className: "shadow-xl",
42802
+ onClick: handleCancel,
42803
+ children: "Cancel"
42804
+ }
42805
+ ),
42806
+ phase !== "enemy_turn" && /* @__PURE__ */ jsxRuntime.jsx(
42807
+ Button,
42808
+ {
42809
+ variant: "primary",
42810
+ className: "shadow-xl",
42811
+ onClick: handleEndTurn,
42812
+ children: "End Turn"
42813
+ }
42814
+ )
42815
+ ] }),
42816
+ isGameOver && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "game-board-3d__overlay absolute inset-0 z-20 flex items-center justify-center bg-black/60", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { align: "center", gap: "md", className: "overlay__card p-8 rounded-xl bg-gray-900/90 shadow-2xl", children: [
42817
+ /* @__PURE__ */ jsxRuntime.jsx(
42818
+ Typography,
42819
+ {
42820
+ variant: "h2",
42821
+ className: cn(
42822
+ "overlay__result",
42823
+ result === "victory" ? "text-yellow-400" : "text-red-500"
42824
+ ),
42825
+ children: result === "victory" ? "Victory!" : "Defeat"
42826
+ }
42827
+ ),
42828
+ /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "body", color: "muted", className: "overlay__turn-count", children: [
42829
+ "Completed in ",
42830
+ turn,
42831
+ " turn",
42832
+ turn !== 1 ? "s" : ""
42833
+ ] }),
42834
+ /* @__PURE__ */ jsxRuntime.jsx(
42835
+ Button,
42836
+ {
42837
+ variant: "primary",
42838
+ className: "px-8 py-3 font-semibold",
42839
+ onClick: handlePlayAgain,
42840
+ children: "Play Again"
42841
+ }
42842
+ )
42843
+ ] }) })
42844
+ ] });
42845
+ }
42846
+ var init_GameBoard3D = __esm({
42847
+ "components/game/organisms/GameBoard3D.tsx"() {
42848
+ "use client";
42849
+ init_cn();
42850
+ init_Button();
42851
+ init_Typography();
42852
+ init_Stack();
42853
+ init_useEventBus();
42854
+ init_GameCanvas3D2();
42855
+ init_boardEntity();
42856
+ GameBoard3D.displayName = "GameBoard3D";
42857
+ }
42858
+ });
41155
42859
  var GameShell;
41156
42860
  var init_GameShell = __esm({
41157
42861
  "components/game/templates/GameShell.tsx"() {
@@ -42520,21 +44224,6 @@ var init_ModalSlot = __esm({
42520
44224
  ModalSlot.displayName = "ModalSlot";
42521
44225
  }
42522
44226
  });
42523
- function getOpponentAction(strategy, actions, history) {
42524
- const actionIds = actions.map((a) => a.id);
42525
- switch (strategy) {
42526
- case "always-cooperate":
42527
- return actionIds[0];
42528
- case "always-defect":
42529
- return actionIds[actionIds.length - 1];
42530
- case "tit-for-tat":
42531
- if (history.length === 0) return actionIds[0];
42532
- return history[history.length - 1].playerAction;
42533
- case "random":
42534
- default:
42535
- return actionIds[Math.floor(Math.random() * actionIds.length)];
42536
- }
42537
- }
42538
44227
  function NegotiatorBoard({
42539
44228
  entity,
42540
44229
  completeEvent = "PUZZLE_COMPLETE",
@@ -42556,28 +44245,35 @@ function NegotiatorBoard({
42556
44245
  const playerTotal = num(resolved?.score);
42557
44246
  const isComplete = result !== "none" || totalRounds > 0 && currentRound >= totalRounds;
42558
44247
  const won = result === "win";
42559
- const opponentTotal = history.reduce((s, r) => s + r.opponentPayoff, 0);
44248
+ const lastPlayerAction = str(resolved?.lastPlayerAction);
44249
+ const lastOpponentAction = str(resolved?.lastOpponentAction);
44250
+ const lastPayoff = num(resolved?.lastPayoff);
42560
44251
  const actions = Array.isArray(resolved?.actions) ? resolved.actions : [];
42561
44252
  const payoffMatrix = Array.isArray(resolved?.payoffMatrix) ? resolved.payoffMatrix : [];
44253
+ const prevRoundRef = React83.useRef(currentRound);
44254
+ React83.useEffect(() => {
44255
+ if (currentRound > prevRoundRef.current && lastPlayerAction) {
44256
+ const opponentPayoffEntry = payoffMatrix.find(
44257
+ (p2) => p2.playerAction === lastPlayerAction && p2.opponentAction === lastOpponentAction
44258
+ );
44259
+ setHistory((prev) => [
44260
+ ...prev,
44261
+ {
44262
+ round: currentRound,
44263
+ playerAction: lastPlayerAction,
44264
+ opponentAction: lastOpponentAction,
44265
+ playerPayoff: lastPayoff,
44266
+ opponentPayoff: opponentPayoffEntry?.opponentPayoff ?? 0
44267
+ }
44268
+ ]);
44269
+ }
44270
+ prevRoundRef.current = currentRound;
44271
+ }, [currentRound, lastPlayerAction, lastOpponentAction, lastPayoff, payoffMatrix]);
44272
+ const opponentTotal = React83.useMemo(() => history.reduce((s, r) => s + r.opponentPayoff, 0), [history]);
42562
44273
  const handleAction = React83.useCallback((actionId) => {
42563
44274
  if (isComplete) return;
42564
- const opponentAction = getOpponentAction(str(resolved?.opponentStrategy) || "random", actions, history);
42565
- const payoff = payoffMatrix.find(
42566
- (p2) => p2.playerAction === actionId && p2.opponentAction === opponentAction
42567
- );
42568
- const playerPayoff = payoff?.playerPayoff ?? 0;
42569
- setHistory((prev) => [
42570
- ...prev,
42571
- {
42572
- round: prev.length + 1,
42573
- playerAction: actionId,
42574
- opponentAction,
42575
- playerPayoff,
42576
- opponentPayoff: payoff?.opponentPayoff ?? 0
42577
- }
42578
- ]);
42579
44275
  if (playRoundEvent) {
42580
- emit(`UI:${playRoundEvent}`, { playerAction: actionId, payoff: playerPayoff });
44276
+ emit(`UI:${playRoundEvent}`, { playerAction: actionId, payoff: 0 });
42581
44277
  }
42582
44278
  if (totalRounds > 0 && currentRound + 1 >= totalRounds) {
42583
44279
  if (finishEvent) {
@@ -42587,10 +44283,11 @@ function NegotiatorBoard({
42587
44283
  setShowHint(true);
42588
44284
  }
42589
44285
  }
42590
- }, [isComplete, resolved, totalRounds, currentRound, actions, payoffMatrix, history, playRoundEvent, finishEvent, emit]);
44286
+ }, [isComplete, resolved, totalRounds, currentRound, playRoundEvent, finishEvent, emit]);
42591
44287
  const handleReset = React83.useCallback(() => {
42592
44288
  setHistory([]);
42593
44289
  setShowHint(false);
44290
+ prevRoundRef.current = 0;
42594
44291
  if (playAgainEvent) {
42595
44292
  emit(`UI:${playAgainEvent}`, {});
42596
44293
  }
@@ -42701,6 +44398,236 @@ var init_NegotiatorBoard = __esm({
42701
44398
  NegotiatorBoard.displayName = "NegotiatorBoard";
42702
44399
  }
42703
44400
  });
44401
+ function resolveEntity(entity) {
44402
+ if (!entity) return {};
44403
+ if (Array.isArray(entity)) return entity[0] ?? {};
44404
+ return entity;
44405
+ }
44406
+ function numField(row, key, fallback) {
44407
+ const v = row[key];
44408
+ return typeof v === "number" ? v : fallback;
44409
+ }
44410
+ function strField(row, key, fallback) {
44411
+ const v = row[key];
44412
+ return typeof v === "string" ? v : fallback;
44413
+ }
44414
+ function PlatformerBoard({
44415
+ entity,
44416
+ player: propPlayer,
44417
+ platforms: propPlatforms,
44418
+ result: propResult,
44419
+ lives: propLives,
44420
+ score: propScore,
44421
+ level: propLevel,
44422
+ worldWidth = 800,
44423
+ worldHeight = 400,
44424
+ canvasWidth = 800,
44425
+ canvasHeight = 400,
44426
+ playerSprite,
44427
+ tileSprites,
44428
+ bgColor,
44429
+ leftEvent = "LEFT",
44430
+ rightEvent = "RIGHT",
44431
+ jumpEvent = "JUMP",
44432
+ stopEvent = "STOP",
44433
+ playAgainEvent = "PLAY_AGAIN",
44434
+ gameEndEvent = "GAME_END",
44435
+ className
44436
+ }) {
44437
+ const row = resolveEntity(entity);
44438
+ const eventBus = useEventBus();
44439
+ const { t } = hooks.useTranslate();
44440
+ const result = propResult ?? strField(row, "result", "none");
44441
+ propLives ?? numField(row, "lives", 3);
44442
+ const score = propScore ?? numField(row, "score", 0);
44443
+ const level = propLevel ?? numField(row, "level", 1);
44444
+ const entityPlayer = row["player"];
44445
+ const player = propPlayer ?? entityPlayer ?? {
44446
+ x: 80,
44447
+ y: 320,
44448
+ width: 32,
44449
+ height: 48,
44450
+ vx: 0,
44451
+ vy: 0,
44452
+ grounded: false,
44453
+ facingRight: true
44454
+ };
44455
+ const entityPlatforms = Array.isArray(row["platforms"]) ? row["platforms"] : void 0;
44456
+ const platforms = propPlatforms ?? entityPlatforms ?? [
44457
+ { x: 0, y: 368, width: 800, height: 32, type: "ground" },
44458
+ { x: 150, y: 280, width: 160, height: 16, type: "platform" },
44459
+ { x: 420, y: 220, width: 160, height: 16, type: "platform" },
44460
+ { x: 580, y: 300, width: 80, height: 16, type: "hazard" },
44461
+ { x: 700, y: 340, width: 64, height: 28, type: "goal" }
44462
+ ];
44463
+ const handleRestart = React83.useCallback(() => {
44464
+ if (playAgainEvent) {
44465
+ eventBus.emit(`UI:${playAgainEvent}`, {});
44466
+ }
44467
+ }, [playAgainEvent, eventBus]);
44468
+ const isGameOver = result === "won" || result === "lost";
44469
+ const gameEndEmittedRef = React83.useRef(false);
44470
+ React83.useEffect(() => {
44471
+ if ((result === "won" || result === "lost") && gameEndEvent) {
44472
+ if (!gameEndEmittedRef.current) {
44473
+ gameEndEmittedRef.current = true;
44474
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
44475
+ }
44476
+ } else {
44477
+ gameEndEmittedRef.current = false;
44478
+ }
44479
+ }, [result, gameEndEvent, eventBus]);
44480
+ return /* @__PURE__ */ jsxRuntime.jsx(
44481
+ VStack,
44482
+ {
44483
+ className: cn("platformer-board relative bg-background", className),
44484
+ gap: "none",
44485
+ children: /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "relative", children: [
44486
+ /* @__PURE__ */ jsxRuntime.jsx(
44487
+ PlatformerCanvas,
44488
+ {
44489
+ player,
44490
+ platforms,
44491
+ worldWidth,
44492
+ worldHeight,
44493
+ canvasWidth,
44494
+ canvasHeight,
44495
+ followCamera: true,
44496
+ bgColor,
44497
+ playerSprite,
44498
+ tileSprites,
44499
+ leftEvent,
44500
+ rightEvent,
44501
+ jumpEvent,
44502
+ stopEvent
44503
+ }
44504
+ ),
44505
+ isGameOver && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "absolute inset-0 z-50 flex items-center justify-center bg-background/70 backdrop-blur-sm rounded-container", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { className: "text-center p-8", gap: "lg", children: [
44506
+ /* @__PURE__ */ jsxRuntime.jsx(
44507
+ Typography,
44508
+ {
44509
+ variant: "h2",
44510
+ className: cn(
44511
+ "text-4xl font-black tracking-widest uppercase",
44512
+ result === "won" ? "text-warning" : "text-error"
44513
+ ),
44514
+ children: result === "won" ? t("platformer.won") : t("platformer.lost")
44515
+ }
44516
+ ),
44517
+ result === "won" && /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "body1", className: "text-muted-foreground", children: [
44518
+ t("platformer.level"),
44519
+ " ",
44520
+ level,
44521
+ " \u2014 ",
44522
+ t("platformer.score"),
44523
+ ": ",
44524
+ score
44525
+ ] }),
44526
+ result === "lost" && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body1", className: "text-muted-foreground", children: t("platformer.noLives") }),
44527
+ /* @__PURE__ */ jsxRuntime.jsx(
44528
+ Button,
44529
+ {
44530
+ variant: "primary",
44531
+ className: "px-8 py-3 font-semibold",
44532
+ onClick: handleRestart,
44533
+ children: result === "won" ? t("platformer.nextLevel") : t("platformer.playAgain")
44534
+ }
44535
+ )
44536
+ ] }) })
44537
+ ] })
44538
+ }
44539
+ );
44540
+ }
44541
+ var init_PlatformerBoard = __esm({
44542
+ "components/game/organisms/PlatformerBoard.tsx"() {
44543
+ "use client";
44544
+ init_cn();
44545
+ init_useEventBus();
44546
+ init_Box();
44547
+ init_Button();
44548
+ init_Typography();
44549
+ init_Stack();
44550
+ init_PlatformerCanvas();
44551
+ PlatformerBoard.displayName = "PlatformerBoard";
44552
+ }
44553
+ });
44554
+ function PlatformerTemplate({
44555
+ entity,
44556
+ platforms = DEFAULT_PLATFORMS,
44557
+ worldWidth = 800,
44558
+ worldHeight = 400,
44559
+ canvasWidth = 800,
44560
+ canvasHeight = 400,
44561
+ playerSprite = `${CDN3}/characters/platformChar_idle.png`,
44562
+ tileSprites = DEFAULT_TILE_SPRITES,
44563
+ bgColor = "#5c94fc",
44564
+ gameEndEvent = "GAME_END",
44565
+ className
44566
+ }) {
44567
+ const row = entity && !Array.isArray(entity) ? entity : void 0;
44568
+ const lives = typeof row?.["lives"] === "number" ? row["lives"] : 3;
44569
+ const score = typeof row?.["score"] === "number" ? row["score"] : 0;
44570
+ const level = typeof row?.["level"] === "number" ? row["level"] : 1;
44571
+ return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { className: cn("platformer-template w-full", className), gap: "none", children: [
44572
+ /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "relative", children: /* @__PURE__ */ jsxRuntime.jsx(
44573
+ GameHud,
44574
+ {
44575
+ position: "top",
44576
+ stats: [
44577
+ { label: "Lives", value: lives, format: "number", variant: "danger" },
44578
+ { label: "Level", value: level, format: "number", variant: "default" }
44579
+ ]
44580
+ }
44581
+ ) }),
44582
+ /* @__PURE__ */ jsxRuntime.jsx(
44583
+ PlatformerBoard,
44584
+ {
44585
+ entity,
44586
+ platforms,
44587
+ worldWidth,
44588
+ worldHeight,
44589
+ canvasWidth,
44590
+ canvasHeight,
44591
+ playerSprite,
44592
+ tileSprites,
44593
+ bgColor,
44594
+ leftEvent: "LEFT",
44595
+ rightEvent: "RIGHT",
44596
+ jumpEvent: "JUMP",
44597
+ stopEvent: "STOP",
44598
+ playAgainEvent: "PLAY_AGAIN",
44599
+ gameEndEvent
44600
+ }
44601
+ ),
44602
+ /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "px-4 py-2", children: /* @__PURE__ */ jsxRuntime.jsx(ScoreBoard, { score, level }) })
44603
+ ] });
44604
+ }
44605
+ var CDN3, DEFAULT_TILE_SPRITES, DEFAULT_PLATFORMS;
44606
+ var init_PlatformerTemplate = __esm({
44607
+ "components/game/templates/PlatformerTemplate.tsx"() {
44608
+ init_cn();
44609
+ init_Box();
44610
+ init_Stack();
44611
+ init_PlatformerBoard();
44612
+ init_GameHud();
44613
+ init_ScoreBoard();
44614
+ CDN3 = "https://almadar-kflow-assets.web.app/shared/platformer";
44615
+ DEFAULT_TILE_SPRITES = {
44616
+ ground: `${CDN3}/tiles/platformPack_tile001.png`,
44617
+ platform: `${CDN3}/tiles/platformPack_tile004.png`,
44618
+ hazard: `${CDN3}/tiles/platformPack_tile017.png`,
44619
+ goal: `${CDN3}/tiles/platformPack_tile007.png`
44620
+ };
44621
+ DEFAULT_PLATFORMS = [
44622
+ { x: 0, y: 368, width: 800, height: 32, type: "ground" },
44623
+ { x: 150, y: 280, width: 160, height: 16, type: "platform" },
44624
+ { x: 420, y: 220, width: 160, height: 16, type: "platform" },
44625
+ { x: 580, y: 300, width: 80, height: 16, type: "hazard" },
44626
+ { x: 700, y: 340, width: 64, height: 28, type: "goal" }
44627
+ ];
44628
+ PlatformerTemplate.displayName = "PlatformerTemplate";
44629
+ }
44630
+ });
42704
44631
  var PricingOrganism;
42705
44632
  var init_PricingOrganism = __esm({
42706
44633
  "components/marketing/organisms/PricingOrganism.tsx"() {
@@ -42823,6 +44750,356 @@ var init_PricingPageTemplate = __esm({
42823
44750
  PricingPageTemplate.displayName = "PricingPageTemplate";
42824
44751
  }
42825
44752
  });
44753
+ function RoguelikeBoard({
44754
+ entity,
44755
+ tiles: propTiles,
44756
+ enemies: propEnemies,
44757
+ items: propItems,
44758
+ player: propPlayer,
44759
+ playerHp: propPlayerHp,
44760
+ playerMaxHp: propPlayerMaxHp,
44761
+ playerAttack: propPlayerAttack,
44762
+ depth: propDepth,
44763
+ result: propResult,
44764
+ phase: propPhase,
44765
+ stairsX: propStairsX,
44766
+ stairsY: propStairsY,
44767
+ assetManifest: propAssetManifest,
44768
+ scale = 0.45,
44769
+ unitScale = 1,
44770
+ moveEvent,
44771
+ playAgainEvent,
44772
+ gameEndEvent,
44773
+ className
44774
+ }) {
44775
+ const board = boardEntity(entity) ?? {};
44776
+ const tiles = propTiles ?? (Array.isArray(board.tiles) ? board.tiles : DEFAULT_TILES2);
44777
+ const enemies = propEnemies ?? (Array.isArray(board.enemies) ? board.enemies : DEFAULT_ENEMIES);
44778
+ const items = propItems ?? (Array.isArray(board.items) ? board.items : DEFAULT_ITEMS);
44779
+ const player = propPlayer ?? board.player ?? { x: 1, y: 1 };
44780
+ const playerHp = propPlayerHp ?? num(board.playerHp, 10);
44781
+ const playerMaxHp = propPlayerMaxHp ?? num(board.playerMaxHp, 10);
44782
+ const playerAttack = propPlayerAttack ?? num(board.playerAttack, 3);
44783
+ const depth = propDepth ?? num(board.depth, 1);
44784
+ const result = propResult ?? (str(board.result) || "none");
44785
+ const phase = propPhase ?? (str(board.phase) || "player_turn");
44786
+ propStairsX ?? num(board.stairsX, 3);
44787
+ propStairsY ?? num(board.stairsY, 3);
44788
+ const eventBus = useEventBus();
44789
+ const { t } = hooks.useTranslate();
44790
+ const [hoveredTile, setHoveredTile] = React83.useState(null);
44791
+ const emitMove = React83.useCallback((dx, dy) => {
44792
+ if (!moveEvent) return;
44793
+ eventBus.emit(`UI:${moveEvent}`, { dx, dy });
44794
+ }, [moveEvent, eventBus]);
44795
+ React83.useEffect(() => {
44796
+ const handler = (e) => {
44797
+ if (result !== "none" || phase !== "player_turn") return;
44798
+ if (e.key === "ArrowUp" || e.key === "w") emitMove(0, -1);
44799
+ if (e.key === "ArrowDown" || e.key === "s") emitMove(0, 1);
44800
+ if (e.key === "ArrowLeft" || e.key === "a") emitMove(-1, 0);
44801
+ if (e.key === "ArrowRight" || e.key === "d") emitMove(1, 0);
44802
+ };
44803
+ window.addEventListener("keydown", handler);
44804
+ return () => window.removeEventListener("keydown", handler);
44805
+ }, [result, phase, emitMove]);
44806
+ React83.useEffect(() => {
44807
+ if ((result === "won" || result === "lost") && gameEndEvent) {
44808
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
44809
+ }
44810
+ }, [result, gameEndEvent, eventBus]);
44811
+ const handleTileClick = React83.useCallback((x, y) => {
44812
+ if (result !== "none" || phase !== "player_turn") return;
44813
+ const dx = x - player.x;
44814
+ const dy = y - player.y;
44815
+ if (Math.abs(dx) + Math.abs(dy) === 1) {
44816
+ emitMove(dx, dy);
44817
+ }
44818
+ }, [result, phase, player, emitMove]);
44819
+ const handleReset = React83.useCallback(() => {
44820
+ if (!playAgainEvent) return;
44821
+ eventBus.emit(`UI:${playAgainEvent}`, {});
44822
+ }, [playAgainEvent, eventBus]);
44823
+ const isoUnits = React83.useMemo(() => {
44824
+ const liveEnemies = enemies.filter((e) => e.hp > 0);
44825
+ return [
44826
+ {
44827
+ id: "player",
44828
+ position: player,
44829
+ name: t("roguelike.player") || "Hero",
44830
+ team: "player",
44831
+ health: playerHp,
44832
+ maxHealth: playerMaxHp,
44833
+ unitType: "player",
44834
+ sprite: `${CDN4}/isometric-dungeon/Characters/Male/Male_0_Idle0.png`
44835
+ },
44836
+ ...liveEnemies.map((e) => ({
44837
+ id: e.id,
44838
+ position: { x: e.x, y: e.y },
44839
+ name: t("roguelike.enemy") || "Enemy",
44840
+ team: "enemy",
44841
+ health: e.hp,
44842
+ maxHealth: 6,
44843
+ unitType: "enemy",
44844
+ sprite: `${CDN4}/sprite-sheets/shadow-legion-sprite-sheet-sw.png`
44845
+ }))
44846
+ ];
44847
+ }, [player, enemies, playerHp, playerMaxHp, t]);
44848
+ const isoFeatures = React83.useMemo(
44849
+ () => items.map((it) => ({
44850
+ id: it.id,
44851
+ x: it.x,
44852
+ y: it.y,
44853
+ type: it.kind,
44854
+ sprite: FEATURE_SPRITE[it.kind] ?? FEATURE_SPRITE.health_potion
44855
+ })),
44856
+ [items]
44857
+ );
44858
+ const validMoves = React83.useMemo(() => {
44859
+ if (result !== "none" || phase !== "player_turn") return [];
44860
+ const dirs = [{ dx: 0, dy: -1 }, { dx: 0, dy: 1 }, { dx: -1, dy: 0 }, { dx: 1, dy: 0 }];
44861
+ return dirs.map(({ dx, dy }) => ({ x: player.x + dx, y: player.y + dy })).filter(({ x, y }) => {
44862
+ const tile = tiles.find((t2) => t2.x === x && t2.y === y);
44863
+ return tile?.passable ?? false;
44864
+ });
44865
+ }, [result, phase, player, tiles]);
44866
+ const isGameOver = result === "won" || result === "lost";
44867
+ return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { className: cn("roguelike-board relative min-h-[600px] bg-background", className), gap: "none", children: [
44868
+ /* @__PURE__ */ jsxRuntime.jsxs(HStack, { className: "items-center justify-between px-4 py-2 border-b border-border", gap: "md", children: [
44869
+ /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", className: "items-center", children: [
44870
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: t("roguelike.depth") || "Depth" }),
44871
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body1", className: "font-bold tabular-nums", children: depth })
44872
+ ] }),
44873
+ /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", className: "items-center", children: [
44874
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: t("roguelike.hp") || "HP" }),
44875
+ /* @__PURE__ */ jsxRuntime.jsxs(
44876
+ Typography,
44877
+ {
44878
+ variant: "body1",
44879
+ className: cn(
44880
+ "font-bold tabular-nums",
44881
+ playerHp <= Math.floor(playerMaxHp * 0.25) ? "text-error" : "text-success"
44882
+ ),
44883
+ children: [
44884
+ playerHp,
44885
+ " / ",
44886
+ playerMaxHp
44887
+ ]
44888
+ }
44889
+ )
44890
+ ] }),
44891
+ /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", className: "items-center", children: [
44892
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: t("roguelike.atk") || "ATK" }),
44893
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body1", className: "font-bold tabular-nums text-foreground", children: playerAttack })
44894
+ ] }),
44895
+ /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", className: "items-center", children: [
44896
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: t("roguelike.phase") || "Phase" }),
44897
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "capitalize text-foreground", children: phase.replace("_", " ") })
44898
+ ] })
44899
+ ] }),
44900
+ /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "relative flex-1", children: /* @__PURE__ */ jsxRuntime.jsx(
44901
+ IsometricCanvas_default,
44902
+ {
44903
+ tiles,
44904
+ units: isoUnits,
44905
+ features: isoFeatures,
44906
+ selectedUnitId: null,
44907
+ validMoves,
44908
+ attackTargets: [],
44909
+ hoveredTile,
44910
+ onTileClick: handleTileClick,
44911
+ onUnitClick: () => void 0,
44912
+ onTileHover: (x, y) => setHoveredTile({ x, y }),
44913
+ onTileLeave: () => setHoveredTile(null),
44914
+ scale,
44915
+ assetBaseUrl: propAssetManifest?.baseUrl ?? CDN4,
44916
+ assetManifest: propAssetManifest,
44917
+ unitScale
44918
+ }
44919
+ ) }),
44920
+ !isGameOver && /* @__PURE__ */ jsxRuntime.jsx(HStack, { className: "justify-center gap-2 p-3", gap: "none", children: [
44921
+ { label: "\u2191", dx: 0, dy: -1 },
44922
+ { label: "\u2193", dx: 0, dy: 1 },
44923
+ { label: "\u2190", dx: -1, dy: 0 },
44924
+ { label: "\u2192", dx: 1, dy: 0 }
44925
+ ].map(({ label, dx, dy }) => /* @__PURE__ */ jsxRuntime.jsx(
44926
+ Button,
44927
+ {
44928
+ variant: "secondary",
44929
+ className: "w-9 h-9 p-0 text-base font-mono",
44930
+ onClick: () => emitMove(dx, dy),
44931
+ disabled: phase !== "player_turn",
44932
+ children: label
44933
+ },
44934
+ label
44935
+ )) }),
44936
+ isGameOver && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "absolute inset-0 z-50 flex items-center justify-center bg-background/75 backdrop-blur-sm rounded-container", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { className: "text-center p-8", gap: "lg", children: [
44937
+ /* @__PURE__ */ jsxRuntime.jsx(
44938
+ Typography,
44939
+ {
44940
+ variant: "h2",
44941
+ className: cn(
44942
+ "text-4xl font-black tracking-widest uppercase",
44943
+ result === "won" ? "text-warning" : "text-error"
44944
+ ),
44945
+ children: result === "won" ? t("roguelike.victory") || "Victory!" : t("roguelike.defeat") || "Defeated!"
44946
+ }
44947
+ ),
44948
+ result === "won" && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body1", className: "text-muted-foreground", children: t("roguelike.cleared") || `Dungeon cleared! Depth reached: ${depth}` }),
44949
+ /* @__PURE__ */ jsxRuntime.jsx(
44950
+ Button,
44951
+ {
44952
+ variant: "primary",
44953
+ className: "px-8 py-3 font-semibold",
44954
+ onClick: handleReset,
44955
+ children: t("roguelike.playAgain") || "Play Again"
44956
+ }
44957
+ )
44958
+ ] }) })
44959
+ ] });
44960
+ }
44961
+ var CDN4, DEFAULT_TILES2, DEFAULT_ENEMIES, DEFAULT_ITEMS, FEATURE_SPRITE;
44962
+ var init_RoguelikeBoard = __esm({
44963
+ "components/game/organisms/RoguelikeBoard.tsx"() {
44964
+ "use client";
44965
+ init_cn();
44966
+ init_useEventBus();
44967
+ init_Box();
44968
+ init_Button();
44969
+ init_Typography();
44970
+ init_Stack();
44971
+ init_IsometricCanvas();
44972
+ init_boardEntity();
44973
+ CDN4 = "https://almadar-kflow-assets.web.app/shared";
44974
+ DEFAULT_TILES2 = [
44975
+ { x: 0, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44976
+ { x: 1, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44977
+ { x: 2, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44978
+ { x: 3, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44979
+ { x: 4, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44980
+ { x: 0, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44981
+ { x: 1, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
44982
+ { x: 2, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
44983
+ { x: 3, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
44984
+ { x: 4, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44985
+ { x: 0, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44986
+ { x: 1, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirtTiles_E.png` },
44987
+ { x: 2, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirtTiles_E.png` },
44988
+ { x: 3, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
44989
+ { x: 4, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44990
+ { x: 0, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44991
+ { x: 1, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
44992
+ { x: 2, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirtTiles_E.png` },
44993
+ { x: 3, y: 3, terrain: "stairs", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stairs_E.png` },
44994
+ { x: 4, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44995
+ { x: 0, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44996
+ { x: 1, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44997
+ { x: 2, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44998
+ { x: 3, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44999
+ { x: 4, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` }
45000
+ ];
45001
+ DEFAULT_ENEMIES = [
45002
+ { id: "e1", x: 3, y: 1, hp: 5, attack: 2 },
45003
+ { id: "e2", x: 1, y: 3, hp: 4, attack: 1 }
45004
+ ];
45005
+ DEFAULT_ITEMS = [
45006
+ { id: "i1", x: 2, y: 2, kind: "health_potion" }
45007
+ ];
45008
+ FEATURE_SPRITE = {
45009
+ health_potion: `${CDN4}/isometric-dungeon/Isometric/chestClosed_E.png`,
45010
+ sword: `${CDN4}/isometric-dungeon/Isometric/barrel_E.png`,
45011
+ shield: `${CDN4}/isometric-dungeon/Isometric/barrel_E.png`
45012
+ };
45013
+ RoguelikeBoard.displayName = "RoguelikeBoard";
45014
+ }
45015
+ });
45016
+ function RoguelikeTemplate({
45017
+ entity,
45018
+ scale = 0.45,
45019
+ unitScale = 1,
45020
+ tiles = DEFAULT_ROGUELIKE_TILES,
45021
+ enemies,
45022
+ items,
45023
+ stairsX,
45024
+ stairsY,
45025
+ assetManifest = DEFAULT_ROGUELIKE_MANIFEST,
45026
+ gameEndEvent,
45027
+ className
45028
+ }) {
45029
+ const resolved = entity && typeof entity === "object" && !Array.isArray(entity) ? entity : void 0;
45030
+ if (!resolved && !tiles && !assetManifest) return null;
45031
+ return /* @__PURE__ */ jsxRuntime.jsx(
45032
+ RoguelikeBoard,
45033
+ {
45034
+ entity: resolved,
45035
+ tiles,
45036
+ enemies,
45037
+ items,
45038
+ stairsX,
45039
+ stairsY,
45040
+ assetManifest,
45041
+ scale,
45042
+ unitScale,
45043
+ moveEvent: "MOVE",
45044
+ playAgainEvent: "PLAY_AGAIN",
45045
+ gameEndEvent: gameEndEvent ?? "GAME_END",
45046
+ className
45047
+ }
45048
+ );
45049
+ }
45050
+ var CDN5, DEFAULT_ROGUELIKE_TILES, DEFAULT_ROGUELIKE_MANIFEST;
45051
+ var init_RoguelikeTemplate = __esm({
45052
+ "components/game/templates/RoguelikeTemplate.tsx"() {
45053
+ init_RoguelikeBoard();
45054
+ CDN5 = "https://almadar-kflow-assets.web.app/shared";
45055
+ DEFAULT_ROGUELIKE_TILES = [
45056
+ { x: 0, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45057
+ { x: 1, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45058
+ { x: 2, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45059
+ { x: 3, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45060
+ { x: 4, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45061
+ { x: 0, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45062
+ { x: 1, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
45063
+ { x: 2, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
45064
+ { x: 3, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
45065
+ { x: 4, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45066
+ { x: 0, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45067
+ { x: 1, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirtTiles_E.png` },
45068
+ { x: 2, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirtTiles_E.png` },
45069
+ { x: 3, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
45070
+ { x: 4, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45071
+ { x: 0, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45072
+ { x: 1, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
45073
+ { x: 2, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirtTiles_E.png` },
45074
+ { x: 3, y: 3, terrain: "stairs", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stairs_E.png` },
45075
+ { x: 4, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45076
+ { x: 0, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45077
+ { x: 1, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45078
+ { x: 2, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45079
+ { x: 3, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45080
+ { x: 4, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` }
45081
+ ];
45082
+ DEFAULT_ROGUELIKE_MANIFEST = {
45083
+ baseUrl: CDN5,
45084
+ terrains: {
45085
+ floor: "/isometric-dungeon/Isometric/dirt_E.png",
45086
+ wall: "/isometric-dungeon/Isometric/stoneInset_E.png",
45087
+ stairs: "/isometric-dungeon/Isometric/stairs_E.png",
45088
+ planks: "/isometric-dungeon/Isometric/planks_E.png"
45089
+ },
45090
+ units: {
45091
+ player: "/isometric-dungeon/Characters/Male/Male_0_Idle0.png",
45092
+ enemy: "/sprite-sheets/shadow-legion-sprite-sheet-sw.png"
45093
+ },
45094
+ features: {
45095
+ health_potion: "/isometric-dungeon/Isometric/chestClosed_E.png",
45096
+ sword: "/isometric-dungeon/Isometric/barrel_E.png",
45097
+ shield: "/isometric-dungeon/Isometric/barrel_E.png"
45098
+ }
45099
+ };
45100
+ RoguelikeTemplate.displayName = "RoguelikeTemplate";
45101
+ }
45102
+ });
42826
45103
 
42827
45104
  // lib/traitRegistry.ts
42828
45105
  function getAllTraits() {
@@ -44057,7 +46334,7 @@ function getAllEvents(traits2) {
44057
46334
  function EventDispatcherTab({ traits: traits2, schema }) {
44058
46335
  const eventBus = useEventBus();
44059
46336
  const { t } = hooks.useTranslate();
44060
- const [log12, setLog] = React83__namespace.useState([]);
46337
+ const [log14, setLog] = React83__namespace.useState([]);
44061
46338
  const prevStatesRef = React83__namespace.useRef(/* @__PURE__ */ new Map());
44062
46339
  React83__namespace.useEffect(() => {
44063
46340
  for (const trait of traits2) {
@@ -44121,9 +46398,9 @@ function EventDispatcherTab({ traits: traits2, schema }) {
44121
46398
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", weight: "medium", className: "text-gray-500 mb-1", children: t("debug.otherEvents") }),
44122
46399
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-1", children: unavailableEvents.map((event) => /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: "default", size: "sm", className: "opacity-50", children: event }, event)) })
44123
46400
  ] }),
44124
- log12.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
46401
+ log14.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
44125
46402
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", weight: "medium", className: "text-gray-500 mb-1", children: t("debug.recentTransitions") }),
44126
- /* @__PURE__ */ jsxRuntime.jsx(Stack, { gap: "xs", children: log12.map((entry, i) => /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "small", className: "font-mono text-xs", children: [
46403
+ /* @__PURE__ */ jsxRuntime.jsx(Stack, { gap: "xs", children: log14.map((entry, i) => /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "small", className: "font-mono text-xs", children: [
44127
46404
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-purple-400", children: entry.traitName }),
44128
46405
  " ",
44129
46406
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-500", children: entry.from }),
@@ -44926,17 +47203,24 @@ function SequencerBoard({
44926
47203
  const { emit } = useEventBus();
44927
47204
  const { t } = hooks.useTranslate();
44928
47205
  const resolved = boardEntity(entity);
44929
- const maxSlots = num(resolved?.maxSlots);
47206
+ const maxSlots = num(resolved?.maxSlots) || 3;
44930
47207
  const solutions = Array.isArray(resolved?.solutions) ? resolved.solutions : [];
44931
47208
  const availableActions = Array.isArray(resolved?.availableActions) ? resolved.availableActions : [];
44932
47209
  const allowDuplicates = resolved?.allowDuplicates !== false;
47210
+ const entitySlots = Array.isArray(resolved?.slots) ? resolved.slots : [];
47211
+ const entityResult = str(resolved?.result);
47212
+ const entityAttempts = num(resolved?.attempts);
47213
+ const entityCurrentStep = typeof resolved?.currentStep === "number" ? resolved.currentStep : -1;
47214
+ const slots = Array.from({ length: maxSlots }, (_, i) => {
47215
+ const entitySlot = entitySlots.find((s) => s.index === i);
47216
+ if (!entitySlot?.placedActionId) return void 0;
47217
+ return availableActions.find((a) => a.id === entitySlot.placedActionId);
47218
+ });
47219
+ const isSuccess = entityResult === "win";
47220
+ const attempts = entityAttempts;
47221
+ const isPlayingBack = entityCurrentStep >= 0 && !isSuccess;
47222
+ const currentStep = entityCurrentStep;
44933
47223
  const [headerError, setHeaderError] = React83.useState(false);
44934
- const [slots, setSlots] = React83.useState(
44935
- () => Array.from({ length: maxSlots }, () => void 0)
44936
- );
44937
- const [playState, setPlayState] = React83.useState("idle");
44938
- const [currentStep, setCurrentStep] = React83.useState(-1);
44939
- const [attempts, setAttempts] = React83.useState(0);
44940
47224
  const [slotFeedback, setSlotFeedback] = React83.useState(
44941
47225
  () => Array.from({ length: maxSlots }, () => null)
44942
47226
  );
@@ -44945,11 +47229,6 @@ function SequencerBoard({
44945
47229
  if (timerRef.current) clearTimeout(timerRef.current);
44946
47230
  }, []);
44947
47231
  const handleSlotDrop = React83.useCallback((index, item) => {
44948
- setSlots((prev) => {
44949
- const next = [...prev];
44950
- next[index] = item;
44951
- return next;
44952
- });
44953
47232
  setSlotFeedback((prev) => {
44954
47233
  const next = [...prev];
44955
47234
  next[index] = null;
@@ -44959,11 +47238,6 @@ function SequencerBoard({
44959
47238
  if (placeEvent) emit(`UI:${placeEvent}`, { slotIndex: index, actionId: item.id });
44960
47239
  }, [emit, placeEvent]);
44961
47240
  const handleSlotRemove = React83.useCallback((index) => {
44962
- setSlots((prev) => {
44963
- const next = [...prev];
44964
- next[index] = void 0;
44965
- return next;
44966
- });
44967
47241
  setSlotFeedback((prev) => {
44968
47242
  const next = [...prev];
44969
47243
  next[index] = null;
@@ -44974,48 +47248,34 @@ function SequencerBoard({
44974
47248
  }, [emit, removeEvent]);
44975
47249
  const handleReset = React83.useCallback(() => {
44976
47250
  if (timerRef.current) clearTimeout(timerRef.current);
44977
- setSlots(Array.from({ length: maxSlots }, () => void 0));
44978
- setPlayState("idle");
44979
- setCurrentStep(-1);
44980
- setAttempts(0);
44981
47251
  setSlotFeedback(Array.from({ length: maxSlots }, () => null));
44982
47252
  if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
44983
47253
  }, [maxSlots, playAgainEvent, emit]);
44984
47254
  const filledSlots = slots.filter((s) => !!s);
44985
- const canPlay = filledSlots.length > 0 && playState === "idle";
47255
+ const canPlay = filledSlots.length > 0 && !isPlayingBack && !isSuccess;
44986
47256
  const handlePlay = React83.useCallback(() => {
44987
47257
  if (!canPlay) return;
44988
47258
  setSlotFeedback(Array.from({ length: maxSlots }, () => null));
44989
47259
  emit("UI:PLAY_SOUND", { key: "confirm" });
44990
47260
  const sequence = slots.map((s) => s?.id || "");
44991
- if (playEvent) {
44992
- emit(`UI:${playEvent}`, { sequence });
44993
- }
44994
- setPlayState("playing");
44995
- setCurrentStep(0);
47261
+ const playerIds = slots.filter(Boolean).map((s) => s?.id || "");
47262
+ if (playEvent) emit(`UI:${playEvent}`, { sequence });
44996
47263
  let step = 0;
44997
47264
  const advance = () => {
44998
47265
  step++;
47266
+ emit("UI:STEP", { step });
44999
47267
  if (step >= maxSlots) {
47268
+ if (checkEvent) emit(`UI:${checkEvent}`, { sequence: playerIds });
45000
47269
  const playerSeq = slots.map((s) => s?.id);
45001
- const playerIds = slots.filter(Boolean).map((s) => s?.id || "");
45002
47270
  const success = solutions.some(
45003
47271
  (sol) => sol.length === playerIds.length && sol.every((id, i) => id === playerIds[i])
45004
47272
  );
45005
- if (checkEvent) emit(`UI:${checkEvent}`, { sequence: playerIds });
45006
47273
  if (success) {
45007
- setPlayState("success");
45008
- setCurrentStep(-1);
45009
47274
  emit("UI:PLAY_SOUND", { key: "levelComplete" });
45010
- if (completeEvent) {
45011
- emit(`UI:${completeEvent}`, { success: true, sequence: playerIds });
45012
- }
47275
+ if (completeEvent) emit(`UI:${completeEvent}`, { success: true, sequence: playerIds });
45013
47276
  } else {
45014
- setAttempts((prev) => prev + 1);
45015
47277
  const feedback = computeSlotFeedback(playerSeq, solutions);
45016
47278
  setSlotFeedback(feedback);
45017
- setPlayState("idle");
45018
- setCurrentStep(-1);
45019
47279
  emit("UI:PLAY_SOUND", { key: "fail" });
45020
47280
  const correctCount2 = feedback.filter((f3) => f3 === "correct").length;
45021
47281
  for (let ci = 0; ci < correctCount2; ci++) {
@@ -45025,7 +47285,6 @@ function SequencerBoard({
45025
47285
  }
45026
47286
  }
45027
47287
  } else {
45028
- setCurrentStep(step);
45029
47288
  timerRef.current = setTimeout(advance, stepDurationMs);
45030
47289
  }
45031
47290
  };
@@ -45075,7 +47334,7 @@ function SequencerBoard({
45075
47334
  /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "xs", children: [
45076
47335
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { className: "items-center justify-between", children: [
45077
47336
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("sequencer.yourSequence") + ":" }),
45078
- hasFeedback && playState === "idle" && /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "caption", className: "text-muted-foreground", children: [
47337
+ hasFeedback && !isPlayingBack && !isSuccess && /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "caption", className: "text-muted-foreground", children: [
45079
47338
  `${correctCount}/${maxSlots} `,
45080
47339
  "\u2705"
45081
47340
  ] })
@@ -45087,7 +47346,7 @@ function SequencerBoard({
45087
47346
  maxSlots,
45088
47347
  onSlotDrop: handleSlotDrop,
45089
47348
  onSlotRemove: handleSlotRemove,
45090
- playing: playState === "playing",
47349
+ playing: isPlayingBack,
45091
47350
  currentStep,
45092
47351
  categoryColors,
45093
47352
  slotFeedback,
@@ -45095,7 +47354,7 @@ function SequencerBoard({
45095
47354
  }
45096
47355
  )
45097
47356
  ] }),
45098
- playState !== "playing" && /* @__PURE__ */ jsxRuntime.jsx(
47357
+ !isPlayingBack && /* @__PURE__ */ jsxRuntime.jsx(
45099
47358
  ActionPalette,
45100
47359
  {
45101
47360
  actions: availableActions,
@@ -45105,8 +47364,8 @@ function SequencerBoard({
45105
47364
  label: t("sequencer.dragActions")
45106
47365
  }
45107
47366
  ),
45108
- hasFeedback && playState === "idle" && attempts > 0 && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "p-3 rounded-container bg-warning/10 border border-warning/30 text-center", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-foreground", children: t(encourageKey) }) }),
45109
- playState === "success" && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "p-4 rounded-container bg-success/20 border border-success text-center", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h5", className: "text-success", children: str(resolved.successMessage) || t("sequencer.levelComplete") }) }),
47367
+ hasFeedback && !isPlayingBack && !isSuccess && attempts > 0 && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "p-3 rounded-container bg-warning/10 border border-warning/30 text-center", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-foreground", children: t(encourageKey) }) }),
47368
+ isSuccess && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "p-4 rounded-container bg-success/20 border border-success text-center", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h5", className: "text-success", children: str(resolved.successMessage) || t("sequencer.levelComplete") }) }),
45110
47369
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", children: [
45111
47370
  /* @__PURE__ */ jsxRuntime.jsx(
45112
47371
  Button,
@@ -46062,9 +48321,9 @@ function VariablePanel({
46062
48321
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("stateArchitect.variables", { name: entityName }) }),
46063
48322
  variables.map((v) => {
46064
48323
  const name = v.name == null ? "" : String(v.name);
46065
- const value = numField(v.value);
46066
- const max = numField(v.max, 100);
46067
- const min = numField(v.min, 0);
48324
+ const value = numField2(v.value);
48325
+ const max = numField2(v.max, 100);
48326
+ const min = numField2(v.min, 0);
46068
48327
  const unit = v.unit == null ? "" : String(v.unit);
46069
48328
  const pct = Math.round((value - min) / (max - min) * 100);
46070
48329
  const isHigh = pct > 80;
@@ -46094,12 +48353,12 @@ function VariablePanel({
46094
48353
  })
46095
48354
  ] });
46096
48355
  }
46097
- var numField;
48356
+ var numField2;
46098
48357
  var init_VariablePanel = __esm({
46099
48358
  "components/game/organisms/puzzles/state-architect/VariablePanel.tsx"() {
46100
48359
  init_atoms2();
46101
48360
  init_cn();
46102
- numField = (v, fallback = 0) => {
48361
+ numField2 = (v, fallback = 0) => {
46103
48362
  const n = Number(v);
46104
48363
  return Number.isFinite(n) ? n : fallback;
46105
48364
  };
@@ -46156,6 +48415,9 @@ function StateArchitectBoard({
46156
48415
  stepDurationMs = 600,
46157
48416
  testEvent,
46158
48417
  completeEvent,
48418
+ addTransitionEvent,
48419
+ removeTransitionEvent,
48420
+ playAgainEvent,
46159
48421
  className
46160
48422
  }) {
46161
48423
  const { emit } = useEventBus();
@@ -46168,14 +48430,16 @@ function StateArchitectBoard({
46168
48430
  const testCases = Array.isArray(resolved?.testCases) ? resolved.testCases : [];
46169
48431
  const entityTransitions = Array.isArray(resolved?.transitions) ? resolved.transitions : [];
46170
48432
  const entityVariables = rows(resolved?.variables);
46171
- const [transitions, setTransitions] = React83.useState(entityTransitions);
48433
+ const transitions = entityTransitions;
48434
+ const attempts = typeof resolved?.attempts === "number" ? resolved.attempts : 0;
48435
+ const entityResult = str(resolved?.result) || "none";
48436
+ const isSuccess = entityResult === "win";
48437
+ const [isTesting, setIsTesting] = React83.useState(false);
46172
48438
  const [headerError, setHeaderError] = React83.useState(false);
46173
- const [playState, setPlayState] = React83.useState("editing");
46174
48439
  const [currentState, setCurrentState] = React83.useState(initialState);
46175
48440
  const [selectedState, setSelectedState] = React83.useState(null);
46176
48441
  const [testResults, setTestResults] = React83.useState([]);
46177
48442
  const [variables, setVariables] = React83.useState(() => [...entityVariables]);
46178
- const [attempts, setAttempts] = React83.useState(0);
46179
48443
  const timerRef = React83.useRef(null);
46180
48444
  const [addingFrom, setAddingFrom] = React83.useState(null);
46181
48445
  React83.useEffect(() => () => {
@@ -46185,7 +48449,7 @@ function StateArchitectBoard({
46185
48449
  const GRAPH_H = 400;
46186
48450
  const positions = React83.useMemo(() => layoutStates(entityStates, GRAPH_W, GRAPH_H), [entityStates]);
46187
48451
  const handleStateClick = React83.useCallback((state) => {
46188
- if (playState !== "editing") return;
48452
+ if (isTesting) return;
46189
48453
  if (addingFrom) {
46190
48454
  if (addingFrom !== state) {
46191
48455
  const event = availableEvents[0] || "EVENT";
@@ -46195,20 +48459,20 @@ function StateArchitectBoard({
46195
48459
  to: state,
46196
48460
  event
46197
48461
  };
46198
- setTransitions((prev) => [...prev, newTrans]);
48462
+ if (addTransitionEvent) emit(`UI:${addTransitionEvent}`, { id: newTrans.id, from: newTrans.from, to: newTrans.to, event: newTrans.event });
46199
48463
  }
46200
48464
  setAddingFrom(null);
46201
48465
  } else {
46202
48466
  setSelectedState(state);
46203
48467
  }
46204
- }, [playState, addingFrom, availableEvents]);
48468
+ }, [isTesting, addingFrom, availableEvents, addTransitionEvent, emit]);
46205
48469
  const handleStartAddTransition = React83.useCallback(() => {
46206
48470
  if (!selectedState) return;
46207
48471
  setAddingFrom(selectedState);
46208
48472
  }, [selectedState]);
46209
48473
  const handleRemoveTransition = React83.useCallback((transId) => {
46210
- setTransitions((prev) => prev.filter((t2) => t2.id !== transId));
46211
- }, []);
48474
+ if (removeTransitionEvent) emit(`UI:${removeTransitionEvent}`, { id: transId });
48475
+ }, [removeTransitionEvent, emit]);
46212
48476
  const machine = React83.useMemo(() => ({
46213
48477
  name: entityName,
46214
48478
  description: str(resolved?.description),
@@ -46222,16 +48486,16 @@ function StateArchitectBoard({
46222
48486
  }))
46223
48487
  }), [entityName, resolved, entityStates, currentState, transitions]);
46224
48488
  const handleTest = React83.useCallback(() => {
46225
- if (playState !== "editing") return;
48489
+ if (isTesting) return;
46226
48490
  if (testEvent) emit(`UI:${testEvent}`, {});
46227
- setPlayState("testing");
48491
+ setIsTesting(true);
46228
48492
  setTestResults([]);
46229
48493
  const results = [];
46230
48494
  let testIdx = 0;
46231
48495
  const runNextTest = () => {
46232
48496
  if (testIdx >= testCases.length) {
46233
48497
  const allPassed = results.every((r) => r.passed);
46234
- setPlayState(allPassed ? "success" : "fail");
48498
+ setIsTesting(false);
46235
48499
  setTestResults(results);
46236
48500
  if (allPassed && completeEvent) {
46237
48501
  emit(`UI:${completeEvent}`, {
@@ -46239,9 +48503,6 @@ function StateArchitectBoard({
46239
48503
  passedTests: results.filter((r) => r.passed).length
46240
48504
  });
46241
48505
  }
46242
- if (!allPassed) {
46243
- setAttempts((prev) => prev + 1);
46244
- }
46245
48506
  return;
46246
48507
  }
46247
48508
  const testCase = testCases[testIdx];
@@ -46264,24 +48525,23 @@ function StateArchitectBoard({
46264
48525
  timerRef.current = setTimeout(runNextTest, stepDurationMs);
46265
48526
  };
46266
48527
  timerRef.current = setTimeout(runNextTest, stepDurationMs);
46267
- }, [playState, transitions, testCases, initialState, stepDurationMs, testEvent, completeEvent, emit]);
48528
+ }, [isTesting, transitions, testCases, initialState, stepDurationMs, testEvent, completeEvent, emit]);
46268
48529
  const handleTryAgain = React83.useCallback(() => {
46269
48530
  if (timerRef.current) clearTimeout(timerRef.current);
46270
- setPlayState("editing");
48531
+ setIsTesting(false);
46271
48532
  setCurrentState(initialState);
46272
48533
  setTestResults([]);
46273
48534
  }, [initialState]);
46274
48535
  const handleReset = React83.useCallback(() => {
46275
48536
  if (timerRef.current) clearTimeout(timerRef.current);
46276
- setTransitions(entityTransitions);
46277
- setPlayState("editing");
48537
+ setIsTesting(false);
46278
48538
  setCurrentState(initialState);
46279
48539
  setTestResults([]);
46280
48540
  setVariables([...entityVariables]);
46281
48541
  setSelectedState(null);
46282
48542
  setAddingFrom(null);
46283
- setAttempts(0);
46284
- }, [entityTransitions, initialState, entityVariables]);
48543
+ if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
48544
+ }, [initialState, entityVariables, playAgainEvent, emit]);
46285
48545
  const codeData = React83.useMemo(() => ({
46286
48546
  name: entityName,
46287
48547
  states: entityStates,
@@ -46377,7 +48637,7 @@ function StateArchitectBoard({
46377
48637
  ]
46378
48638
  }
46379
48639
  ),
46380
- playState === "editing" && /* @__PURE__ */ jsxRuntime.jsx(HStack, { gap: "sm", children: /* @__PURE__ */ jsxRuntime.jsx(
48640
+ !isTesting && /* @__PURE__ */ jsxRuntime.jsx(HStack, { gap: "sm", children: /* @__PURE__ */ jsxRuntime.jsx(
46381
48641
  Button,
46382
48642
  {
46383
48643
  variant: "ghost",
@@ -46399,7 +48659,7 @@ function StateArchitectBoard({
46399
48659
  t2.guardHint,
46400
48660
  ")"
46401
48661
  ] }),
46402
- playState === "editing" && /* @__PURE__ */ jsxRuntime.jsx(
48662
+ !isTesting && /* @__PURE__ */ jsxRuntime.jsx(
46403
48663
  Button,
46404
48664
  {
46405
48665
  variant: "ghost",
@@ -46431,21 +48691,21 @@ function StateArchitectBoard({
46431
48691
  resolved.showCodeView !== false && /* @__PURE__ */ jsxRuntime.jsx(StateJsonView, { data: codeData, label: "View Code" })
46432
48692
  ] })
46433
48693
  ] }),
46434
- playState === "success" && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "p-4 rounded-container bg-success/20 border border-success text-center", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h5", className: "text-success", children: str(resolved.successMessage) || t("stateArchitect.allPassed") }) }),
46435
- playState === "fail" && /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
48694
+ isSuccess && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "p-4 rounded-container bg-success/20 border border-success text-center", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h5", className: "text-success", children: str(resolved.successMessage) || t("stateArchitect.allPassed") }) }),
48695
+ !isTesting && !isSuccess && testResults.some((r) => !r.passed) && /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
46436
48696
  /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "p-4 rounded-container bg-warning/10 border border-warning/30 text-center", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body1", className: "text-foreground font-medium", children: t(ENCOURAGEMENT_KEYS3[Math.min(attempts - 1, ENCOURAGEMENT_KEYS3.length - 1)] ?? ENCOURAGEMENT_KEYS3[0]) }) }),
46437
- attempts >= 3 && hint && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "p-3 rounded-container bg-accent/10 border border-accent/30", children: /* @__PURE__ */ jsxRuntime.jsxs(HStack, { className: "items-start", gap: "xs", children: [
48697
+ !isSuccess && attempts >= 2 && Boolean(str(resolved?.hint)) && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "p-3 rounded-container bg-accent/10 border border-accent/30", children: /* @__PURE__ */ jsxRuntime.jsxs(HStack, { className: "items-start", gap: "xs", children: [
46438
48698
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-accent font-bold shrink-0", children: "\u{1F4A1} " + t("game.hint") + ":" }),
46439
48699
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-foreground", children: hint })
46440
48700
  ] }) })
46441
48701
  ] }),
46442
48702
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", children: [
46443
- playState === "fail" ? /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "primary", onClick: handleTryAgain, children: "\u{1F504} " + t("puzzle.tryAgainButton") }) : /* @__PURE__ */ jsxRuntime.jsx(
48703
+ !isTesting && !isSuccess && testResults.some((r) => !r.passed) ? /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "primary", onClick: handleTryAgain, children: "\u{1F504} " + t("puzzle.tryAgainButton") }) : /* @__PURE__ */ jsxRuntime.jsx(
46444
48704
  Button,
46445
48705
  {
46446
48706
  variant: "primary",
46447
48707
  onClick: handleTest,
46448
- disabled: playState !== "editing",
48708
+ disabled: isTesting,
46449
48709
  children: "\u25B6 " + t("game.runTests")
46450
48710
  }
46451
48711
  ),
@@ -46951,6 +49211,289 @@ var init_ToastSlot = __esm({
46951
49211
  ToastSlot.displayName = "ToastSlot";
46952
49212
  }
46953
49213
  });
49214
+ function tilesToIso(tiles) {
49215
+ return tiles.map((t) => ({
49216
+ x: t.x,
49217
+ y: t.y,
49218
+ terrain: t.terrain,
49219
+ terrainSprite: t.terrainSprite ?? TERRAIN_SPRITES[t.terrain ?? "ground"],
49220
+ passable: t.passable
49221
+ }));
49222
+ }
49223
+ function towersToUnits(towers) {
49224
+ return towers.map((t) => ({
49225
+ id: t.id,
49226
+ position: { x: t.x, y: t.y },
49227
+ name: "Tower",
49228
+ team: "player",
49229
+ sprite: TOWER_SPRITE,
49230
+ unitType: "guardian",
49231
+ health: 1,
49232
+ maxHealth: 1
49233
+ }));
49234
+ }
49235
+ function creepsToUnits(creeps) {
49236
+ return creeps.map((c) => ({
49237
+ id: c.id,
49238
+ position: { x: c.x, y: c.y },
49239
+ name: "Creep",
49240
+ team: "enemy",
49241
+ sprite: CREEP_SPRITE,
49242
+ unitType: "scrapper",
49243
+ health: c.hp,
49244
+ maxHealth: c.maxHp
49245
+ }));
49246
+ }
49247
+ function pathToFeatures(path) {
49248
+ return path.map((p2, i) => ({
49249
+ id: `path-${i}`,
49250
+ x: p2.x,
49251
+ y: p2.y,
49252
+ type: "path-marker",
49253
+ sprite: `${CDN6}world-map/road_straight.png`
49254
+ }));
49255
+ }
49256
+ function TowerDefenseBoard({
49257
+ entity,
49258
+ tiles: propTiles,
49259
+ path: propPath,
49260
+ towers: propTowers,
49261
+ creeps: propCreeps,
49262
+ gold: propGold,
49263
+ lives: propLives,
49264
+ wave: propWave,
49265
+ maxWaves: propMaxWaves,
49266
+ result: propResult,
49267
+ waveActive: propWaveActive,
49268
+ towerCost = 25,
49269
+ scale = 0.45,
49270
+ placeTowerEvent,
49271
+ startWaveEvent,
49272
+ playAgainEvent,
49273
+ gameEndEvent,
49274
+ className
49275
+ }) {
49276
+ const board = boardEntity(entity) ?? {};
49277
+ const eventBus = useEventBus();
49278
+ const { t } = hooks.useTranslate();
49279
+ const tiles = propTiles ?? rows(board.tiles);
49280
+ const path = propPath ?? rows(board.path);
49281
+ const towers = propTowers ?? rows(board.towers);
49282
+ const creeps = propCreeps ?? rows(board.creeps);
49283
+ const gold = propGold ?? num(board.gold, 100);
49284
+ const lives = propLives ?? num(board.lives, 20);
49285
+ const wave = propWave ?? num(board.wave, 1);
49286
+ const maxWaves = propMaxWaves ?? num(board.maxWaves, 5);
49287
+ const result = propResult ?? (str(board.result) || "none");
49288
+ const waveActive = propWaveActive ?? Boolean(board.waveActive);
49289
+ const [hoveredTile, setHoveredTile] = React83.useState(null);
49290
+ const emittedGameEnd = React83.useRef(false);
49291
+ if (result !== "none" && !emittedGameEnd.current) {
49292
+ emittedGameEnd.current = true;
49293
+ if (gameEndEvent) {
49294
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
49295
+ }
49296
+ }
49297
+ if (result === "none") {
49298
+ emittedGameEnd.current = false;
49299
+ }
49300
+ const towerPositions = React83.useMemo(() => new Set(towers.map((t2) => `${t2.x},${t2.y}`)), [towers]);
49301
+ const pathPositions = React83.useMemo(() => new Set(path.map((p2) => `${p2.x},${p2.y}`)), [path]);
49302
+ const validMoves = React83.useMemo(() => {
49303
+ if (result !== "none" || gold < towerCost) return [];
49304
+ return tiles.filter(
49305
+ (t2) => t2.passable !== false && !towerPositions.has(`${t2.x},${t2.y}`) && !pathPositions.has(`${t2.x},${t2.y}`)
49306
+ ).map((t2) => ({ x: t2.x, y: t2.y }));
49307
+ }, [tiles, towerPositions, pathPositions, result, gold, towerCost]);
49308
+ const isoTiles = React83.useMemo(() => tilesToIso(tiles), [tiles]);
49309
+ const towerUnits = React83.useMemo(() => towersToUnits(towers), [towers]);
49310
+ const creepUnits = React83.useMemo(() => creepsToUnits(creeps), [creeps]);
49311
+ const isoUnits = React83.useMemo(() => [...towerUnits, ...creepUnits], [towerUnits, creepUnits]);
49312
+ const pathFeatures = React83.useMemo(() => pathToFeatures(path), [path]);
49313
+ const handleTileClick = React83.useCallback((x, y) => {
49314
+ if (result !== "none") return;
49315
+ if (pathPositions.has(`${x},${y}`)) return;
49316
+ if (towerPositions.has(`${x},${y}`)) return;
49317
+ if (gold < towerCost) return;
49318
+ if (placeTowerEvent) {
49319
+ eventBus.emit(`UI:${placeTowerEvent}`, { x, y });
49320
+ }
49321
+ }, [result, pathPositions, towerPositions, gold, towerCost, placeTowerEvent, eventBus]);
49322
+ const handleStartWave = React83.useCallback(() => {
49323
+ if (startWaveEvent) {
49324
+ eventBus.emit(`UI:${startWaveEvent}`, { wave });
49325
+ }
49326
+ }, [startWaveEvent, wave, eventBus]);
49327
+ const handlePlayAgain = React83.useCallback(() => {
49328
+ if (playAgainEvent) {
49329
+ eventBus.emit(`UI:${playAgainEvent}`, {});
49330
+ }
49331
+ }, [playAgainEvent, eventBus]);
49332
+ const canPlaceTower = gold >= towerCost && result === "none";
49333
+ const canStartWave = !waveActive && result === "none";
49334
+ return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { className: cn("tower-defense-board relative min-h-[600px] bg-background", className), gap: "none", children: [
49335
+ /* @__PURE__ */ jsxRuntime.jsxs(
49336
+ HStack,
49337
+ {
49338
+ className: "px-4 py-2 border-b border-border bg-surface",
49339
+ gap: "lg",
49340
+ align: "center",
49341
+ children: [
49342
+ /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", align: "center", children: [
49343
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", color: "muted", children: t("td.gold") ?? "Gold" }),
49344
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", weight: "bold", className: "text-warning", children: gold })
49345
+ ] }),
49346
+ /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", align: "center", children: [
49347
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", color: "muted", children: t("td.lives") ?? "Lives" }),
49348
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", weight: "bold", className: lives <= 5 ? "text-error" : "text-success", children: lives })
49349
+ ] }),
49350
+ /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", align: "center", children: [
49351
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", color: "muted", children: t("td.wave") ?? "Wave" }),
49352
+ /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "body2", weight: "bold", children: [
49353
+ wave,
49354
+ " / ",
49355
+ maxWaves
49356
+ ] })
49357
+ ] }),
49358
+ /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "ml-auto", children: [
49359
+ canStartWave && /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "primary", size: "sm", onClick: handleStartWave, children: t("td.startWave") ?? "Start Wave" }),
49360
+ waveActive && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", className: "text-warning animate-pulse", children: t("td.waveInProgress") ?? "Wave in progress\u2026" })
49361
+ ] })
49362
+ ]
49363
+ }
49364
+ ),
49365
+ /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "relative flex-1", children: [
49366
+ /* @__PURE__ */ jsxRuntime.jsx(
49367
+ IsometricCanvas_default,
49368
+ {
49369
+ tiles: isoTiles,
49370
+ units: isoUnits,
49371
+ features: pathFeatures,
49372
+ validMoves,
49373
+ hoveredTile,
49374
+ onTileClick: handleTileClick,
49375
+ onTileHover: (x, y) => setHoveredTile({ x, y }),
49376
+ onTileLeave: () => setHoveredTile(null),
49377
+ scale
49378
+ }
49379
+ ),
49380
+ hoveredTile && canPlaceTower && validMoves.some((m) => m.x === hoveredTile.x && m.y === hoveredTile.y) && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "absolute bottom-4 left-1/2 -translate-x-1/2 z-10 bg-background/80 rounded px-3 py-1 backdrop-blur-sm pointer-events-none", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", children: t("td.placeTower") ?? `Place tower (${towerCost} gold)` }) })
49381
+ ] }),
49382
+ result !== "none" && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "absolute inset-0 z-50 flex items-center justify-center bg-background/70 backdrop-blur-sm", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { className: "text-center p-8", gap: "lg", children: [
49383
+ /* @__PURE__ */ jsxRuntime.jsx(
49384
+ Typography,
49385
+ {
49386
+ variant: "h2",
49387
+ className: cn(
49388
+ "text-4xl font-black tracking-widest uppercase",
49389
+ result === "won" ? "text-warning" : "text-error"
49390
+ ),
49391
+ children: result === "won" ? t("td.victory") ?? "Victory!" : t("td.defeat") ?? "Defeat!"
49392
+ }
49393
+ ),
49394
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body1", className: "text-muted-foreground", children: result === "won" ? t("td.survivedAllWaves") ?? `Survived all ${maxWaves} waves!` : t("td.livesReachedZero") ?? "Your base was overrun." }),
49395
+ /* @__PURE__ */ jsxRuntime.jsx(
49396
+ Button,
49397
+ {
49398
+ variant: "primary",
49399
+ className: "px-8 py-3 font-semibold",
49400
+ onClick: handlePlayAgain,
49401
+ children: t("td.playAgain") ?? "Play Again"
49402
+ }
49403
+ )
49404
+ ] }) })
49405
+ ] });
49406
+ }
49407
+ var CDN6, TERRAIN_SPRITES, TOWER_SPRITE, CREEP_SPRITE;
49408
+ var init_TowerDefenseBoard = __esm({
49409
+ "components/game/organisms/TowerDefenseBoard.tsx"() {
49410
+ "use client";
49411
+ init_cn();
49412
+ init_useEventBus();
49413
+ init_Box();
49414
+ init_Button();
49415
+ init_Typography();
49416
+ init_Stack();
49417
+ init_IsometricCanvas();
49418
+ init_boardEntity();
49419
+ CDN6 = "https://almadar-kflow-assets.web.app/shared/";
49420
+ TERRAIN_SPRITES = {
49421
+ ground: `${CDN6}isometric-blocks/PNG/Platformer tiles/platformerTile_01.png`,
49422
+ path: `${CDN6}isometric-blocks/PNG/Platformer tiles/platformerTile_09.png`,
49423
+ wall: `${CDN6}isometric-blocks/PNG/Platformer tiles/platformerTile_05.png`
49424
+ };
49425
+ TOWER_SPRITE = `${CDN6}units/guardian.png`;
49426
+ CREEP_SPRITE = `${CDN6}units/scrapper.png`;
49427
+ TowerDefenseBoard.displayName = "TowerDefenseBoard";
49428
+ }
49429
+ });
49430
+ function TowerDefenseTemplate({
49431
+ entity,
49432
+ title = "Tower Defense",
49433
+ tiles,
49434
+ path,
49435
+ towers,
49436
+ gold,
49437
+ lives,
49438
+ maxWaves,
49439
+ towerCost,
49440
+ scale,
49441
+ placeTowerEvent,
49442
+ startWaveEvent,
49443
+ playAgainEvent,
49444
+ gameEndEvent,
49445
+ className
49446
+ }) {
49447
+ const resolved = entity && typeof entity === "object" && !Array.isArray(entity) ? entity : void 0;
49448
+ return /* @__PURE__ */ jsxRuntime.jsxs(
49449
+ Box,
49450
+ {
49451
+ display: "flex",
49452
+ fullHeight: true,
49453
+ className: cn("tower-defense-template flex-col", className),
49454
+ children: [
49455
+ /* @__PURE__ */ jsxRuntime.jsx(
49456
+ HStack,
49457
+ {
49458
+ gap: "sm",
49459
+ align: "center",
49460
+ className: "px-4 py-3 border-b-2 border-border bg-surface shrink-0",
49461
+ children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title })
49462
+ }
49463
+ ),
49464
+ /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "flex-1 relative overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx(
49465
+ TowerDefenseBoard,
49466
+ {
49467
+ entity: resolved,
49468
+ tiles,
49469
+ path,
49470
+ towers,
49471
+ gold,
49472
+ lives,
49473
+ maxWaves,
49474
+ towerCost,
49475
+ scale,
49476
+ placeTowerEvent,
49477
+ startWaveEvent,
49478
+ playAgainEvent,
49479
+ gameEndEvent,
49480
+ className: "h-full"
49481
+ }
49482
+ ) })
49483
+ ]
49484
+ }
49485
+ );
49486
+ }
49487
+ var init_TowerDefenseTemplate = __esm({
49488
+ "components/game/templates/TowerDefenseTemplate.tsx"() {
49489
+ init_cn();
49490
+ init_Box();
49491
+ init_Stack();
49492
+ init_Typography();
49493
+ init_TowerDefenseBoard();
49494
+ TowerDefenseTemplate.displayName = "TowerDefenseTemplate";
49495
+ }
49496
+ });
46954
49497
  function useBattleState(initialUnits, eventConfig = {}, callbacks = {}) {
46955
49498
  const eventBus = useEventBus();
46956
49499
  const {
@@ -47149,17 +49692,12 @@ var init_UncontrolledBattleBoard = __esm({
47149
49692
  }
47150
49693
  });
47151
49694
  function heroPosition(h) {
49695
+ if ("position" in h && h.position != null) return h.position;
47152
49696
  return vec2(h.position);
47153
49697
  }
47154
- function heroOwner(h) {
47155
- return str(h.owner);
47156
- }
47157
49698
  function heroMovement(h) {
47158
49699
  return num(h.movement);
47159
49700
  }
47160
- function hexPassable(h) {
47161
- return h.passable !== false;
47162
- }
47163
49701
  function defaultIsInRange(from, to, range) {
47164
49702
  return Math.abs(from.x - to.x) + Math.abs(from.y - to.y) <= range;
47165
49703
  }
@@ -47195,38 +49733,41 @@ function WorldMapBoard({
47195
49733
  }) {
47196
49734
  const eventBus = useEventBus();
47197
49735
  const resolved = boardEntity(entity);
47198
- const hexes = rows(resolved?.hexes);
47199
- const heroes = rows(resolved?.heroes);
49736
+ const entityUnits = rows(resolved?.units);
49737
+ const entityTiles = rows(resolved?.tiles);
47200
49738
  const features = propFeatures ?? (Array.isArray(resolved?.features) ? resolved.features : []);
47201
49739
  const selectedHeroId = resolved?.selectedHeroId ?? null;
47202
49740
  const assetManifest = propAssetManifest ?? resolved?.assetManifest;
47203
49741
  const backgroundImage = resolved?.backgroundImage;
47204
49742
  const [hoveredTile, setHoveredTile] = React83.useState(null);
47205
- const selectedHero = React83.useMemo(
47206
- () => heroes.find((h) => str(h.id) === selectedHeroId) ?? null,
47207
- [heroes, selectedHeroId]
47208
- );
47209
49743
  const derivedTiles = React83.useMemo(
47210
- () => hexes.map((hex) => ({
47211
- x: num(hex.x),
47212
- y: num(hex.y),
47213
- terrain: str(hex.terrain),
47214
- terrainSprite: hex.terrainSprite == null ? void 0 : str(hex.terrainSprite)
49744
+ () => entityTiles.map((t) => ({
49745
+ x: num(t.x),
49746
+ y: num(t.y),
49747
+ terrain: str(t.terrain),
49748
+ terrainSprite: t.terrainSprite == null ? void 0 : str(t.terrainSprite),
49749
+ passable: t.passable !== false
47215
49750
  })),
47216
- [hexes]
49751
+ [entityTiles]
47217
49752
  );
47218
49753
  const tiles = propTiles ?? derivedTiles;
47219
49754
  const baseUnits = React83.useMemo(
47220
- () => propUnits ?? heroes.map((hero) => ({
47221
- id: str(hero.id),
47222
- position: heroPosition(hero),
47223
- name: str(hero.name),
47224
- team: heroOwner(hero) === "enemy" ? "enemy" : "player",
47225
- health: 100,
47226
- maxHealth: 100,
47227
- sprite: hero.sprite == null ? void 0 : str(hero.sprite)
49755
+ () => propUnits ?? entityUnits.map((u) => ({
49756
+ id: str(u.id),
49757
+ position: heroPosition(u),
49758
+ name: str(u.name),
49759
+ // lolo uses `team` field (not `owner`)
49760
+ team: str(u.team) === "enemy" ? "enemy" : "player",
49761
+ health: num(u.health) || 100,
49762
+ maxHealth: num(u.maxHealth) || 100,
49763
+ sprite: u.sprite == null ? void 0 : str(u.sprite)
47228
49764
  })),
47229
- [heroes, propUnits]
49765
+ [entityUnits, propUnits]
49766
+ );
49767
+ const gameUnits = baseUnits;
49768
+ const selectedHero = React83.useMemo(
49769
+ () => gameUnits.find((u) => u.id === selectedHeroId) ?? null,
49770
+ [gameUnits, selectedHeroId]
47230
49771
  );
47231
49772
  const MOVE_SPEED_MS_PER_TILE = 300;
47232
49773
  const movementAnimRef = React83.useRef(null);
@@ -47272,51 +49813,50 @@ function WorldMapBoard({
47272
49813
  const validMoves = React83.useMemo(() => {
47273
49814
  if (!selectedHero || heroMovement(selectedHero) <= 0) return [];
47274
49815
  const sp = heroPosition(selectedHero);
47275
- const sOwner = heroOwner(selectedHero);
49816
+ const sTeam = str(selectedHero.team);
47276
49817
  const range = heroMovement(selectedHero);
47277
49818
  const moves = [];
47278
- hexes.forEach((hex) => {
47279
- const hx = num(hex.x);
47280
- const hy = num(hex.y);
47281
- if (!hexPassable(hex)) return;
47282
- if (hx === sp.x && hy === sp.y) return;
47283
- if (!isInRange(sp, { x: hx, y: hy }, range)) return;
47284
- if (heroes.some((h) => {
47285
- const hp = heroPosition(h);
47286
- return hp.x === hx && hp.y === hy && heroOwner(h) === sOwner;
49819
+ tiles.forEach((t) => {
49820
+ const tx = t.x;
49821
+ const ty = t.y;
49822
+ if (t.passable === false) return;
49823
+ if (tx === sp.x && ty === sp.y) return;
49824
+ if (!isInRange(sp, { x: tx, y: ty }, range)) return;
49825
+ if (gameUnits.some((u) => {
49826
+ const up = u.position ?? { x: u.x ?? -1, y: u.y ?? -1 };
49827
+ return up.x === tx && up.y === ty && str(u.team) === sTeam;
47287
49828
  })) return;
47288
- moves.push({ x: hx, y: hy });
49829
+ moves.push({ x: tx, y: ty });
47289
49830
  });
47290
49831
  return moves;
47291
- }, [selectedHero, hexes, heroes, isInRange]);
49832
+ }, [selectedHero, tiles, gameUnits, isInRange]);
47292
49833
  const attackTargets = React83.useMemo(() => {
47293
49834
  if (!selectedHero || heroMovement(selectedHero) <= 0) return [];
47294
49835
  const sp = heroPosition(selectedHero);
47295
- const sOwner = heroOwner(selectedHero);
49836
+ const sTeam = str(selectedHero.team);
47296
49837
  const range = heroMovement(selectedHero);
47297
- return heroes.filter((h) => heroOwner(h) !== sOwner).filter((h) => isInRange(sp, heroPosition(h), range)).map((h) => heroPosition(h));
47298
- }, [selectedHero, heroes, isInRange]);
47299
- const maxY = Math.max(...hexes.map((h) => num(h.y)), 0);
49838
+ return gameUnits.filter((u) => str(u.team) !== sTeam).filter((u) => isInRange(sp, u.position ?? { x: u.x ?? -1, y: u.y ?? -1 }, range)).map((u) => u.position ?? { x: u.x ?? -1, y: u.y ?? -1 });
49839
+ }, [selectedHero, gameUnits, isInRange]);
49840
+ const maxY = Math.max(...tiles.map((t) => t.y), 0);
47300
49841
  const baseOffsetX = (maxY + 1) * (TILE_WIDTH * scale / 2);
47301
49842
  const tileToScreen = React83.useCallback(
47302
49843
  (tx, ty) => isoToScreen(tx, ty, scale, baseOffsetX),
47303
49844
  [scale, baseOffsetX]
47304
49845
  );
47305
49846
  const hoveredHex = React83.useMemo(
47306
- () => hoveredTile ? hexes.find((h) => num(h.x) === hoveredTile.x && num(h.y) === hoveredTile.y) ?? null : null,
47307
- [hoveredTile, hexes]
49847
+ () => hoveredTile ? tiles.find((t) => t.x === hoveredTile.x && t.y === hoveredTile.y) ?? null : null,
49848
+ [hoveredTile, tiles]
47308
49849
  );
47309
49850
  const hoveredHero = React83.useMemo(
47310
- () => hoveredTile ? heroes.find((h) => {
47311
- const hp = heroPosition(h);
47312
- return hp.x === hoveredTile.x && hp.y === hoveredTile.y;
49851
+ () => hoveredTile ? gameUnits.find((u) => {
49852
+ const up = u.position ?? { x: u.x ?? -1, y: u.y ?? -1 };
49853
+ return up.x === hoveredTile.x && up.y === hoveredTile.y;
47313
49854
  }) ?? null : null,
47314
- [hoveredTile, heroes]
49855
+ [hoveredTile, gameUnits]
47315
49856
  );
47316
49857
  const handleTileClick = React83.useCallback((x, y) => {
47317
49858
  if (movementAnimRef.current) return;
47318
- const hex = hexes.find((h) => num(h.x) === x && num(h.y) === y);
47319
- if (!hex) return;
49859
+ const tile = tiles.find((t) => t.x === x && t.y === y);
47320
49860
  if (tileClickEvent) {
47321
49861
  eventBus.emit(`UI:${tileClickEvent}`, { x, y });
47322
49862
  }
@@ -47327,38 +49867,39 @@ function WorldMapBoard({
47327
49867
  if (heroMoveEvent) {
47328
49868
  eventBus.emit(`UI:${heroMoveEvent}`, { heroId, toX: x, toY: y });
47329
49869
  }
47330
- const feature = str(hex.feature);
49870
+ const feature = tile ? str(tile.feature) : "";
47331
49871
  if (feature && feature !== "none") {
47332
- onFeatureEnter?.(heroId, hex);
49872
+ const tileRow = tile;
49873
+ onFeatureEnter?.(heroId, tileRow);
47333
49874
  if (featureEnterEvent) {
47334
- eventBus.emit(`UI:${featureEnterEvent}`, { heroId, feature, hex });
49875
+ eventBus.emit(`UI:${featureEnterEvent}`, { heroId, feature, hex: tileRow });
47335
49876
  }
47336
49877
  }
47337
49878
  });
47338
49879
  return;
47339
49880
  }
47340
- const enemy = heroes.find((h) => {
47341
- const hp = heroPosition(h);
47342
- return hp.x === x && hp.y === y && heroOwner(h) === "enemy";
49881
+ const enemy = gameUnits.find((u) => {
49882
+ const up = u.position ?? { x: u.x ?? -1, y: u.y ?? -1 };
49883
+ return up.x === x && up.y === y && str(u.team) === "enemy";
47343
49884
  });
47344
49885
  if (selectedHero && enemy && attackTargets.some((t) => t.x === x && t.y === y)) {
47345
49886
  const attackerId = str(selectedHero.id);
47346
- const defenderId = str(enemy.id);
49887
+ const defenderId = enemy.id;
47347
49888
  onBattleEncounter?.(attackerId, defenderId);
47348
49889
  if (battleEncounterEvent) {
47349
49890
  eventBus.emit(`UI:${battleEncounterEvent}`, { attackerId, defenderId });
47350
49891
  }
47351
49892
  }
47352
- }, [hexes, heroes, selectedHero, validMoves, attackTargets, startMoveAnimation, onHeroMove, onFeatureEnter, onBattleEncounter, eventBus, tileClickEvent, heroMoveEvent, featureEnterEvent, battleEncounterEvent]);
49893
+ }, [tiles, gameUnits, selectedHero, validMoves, attackTargets, startMoveAnimation, onHeroMove, onFeatureEnter, onBattleEncounter, eventBus, tileClickEvent, heroMoveEvent, featureEnterEvent, battleEncounterEvent]);
47353
49894
  const handleUnitClick = React83.useCallback((unitId) => {
47354
- const hero = heroes.find((h) => str(h.id) === unitId);
47355
- if (hero && (heroOwner(hero) === "player" || allowMoveAllHeroes)) {
49895
+ const unit = gameUnits.find((u) => u.id === unitId);
49896
+ if (unit && (str(unit.team) === "player" || allowMoveAllHeroes)) {
47356
49897
  onHeroSelect?.(unitId);
47357
49898
  if (heroSelectEvent) {
47358
49899
  eventBus.emit(`UI:${heroSelectEvent}`, { heroId: unitId });
47359
49900
  }
47360
49901
  }
47361
- }, [heroes, onHeroSelect, allowMoveAllHeroes, eventBus, heroSelectEvent]);
49902
+ }, [gameUnits, onHeroSelect, allowMoveAllHeroes, eventBus, heroSelectEvent]);
47362
49903
  const selectHero = React83.useCallback((id) => {
47363
49904
  onHeroSelect?.(id);
47364
49905
  if (heroSelectEvent) {
@@ -47462,48 +50003,48 @@ function WorldMapTemplate({
47462
50003
  }
47463
50004
  );
47464
50005
  }
47465
- var CDN3, DEFAULT_WORLDMAP_TILES, DEFAULT_WORLDMAP_UNITS, DEFAULT_WORLDMAP_FEATURES, DEFAULT_WORLDMAP_MANIFEST;
50006
+ var CDN7, DEFAULT_WORLDMAP_TILES, DEFAULT_WORLDMAP_UNITS, DEFAULT_WORLDMAP_FEATURES, DEFAULT_WORLDMAP_MANIFEST;
47466
50007
  var init_WorldMapTemplate = __esm({
47467
50008
  "components/game/templates/WorldMapTemplate.tsx"() {
47468
50009
  init_WorldMapBoard();
47469
- CDN3 = "https://almadar-kflow-assets.web.app/shared";
50010
+ CDN7 = "https://almadar-kflow-assets.web.app/shared";
47470
50011
  DEFAULT_WORLDMAP_TILES = [
47471
- { x: 0, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47472
- { x: 1, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47473
- { x: 2, y: 0, terrain: "water", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
47474
- { x: 3, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47475
- { x: 4, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47476
- { x: 0, y: 1, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47477
- { x: 1, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47478
- { x: 2, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47479
- { x: 3, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47480
- { x: 4, y: 1, terrain: "water", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
47481
- { x: 0, y: 2, terrain: "water", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
47482
- { x: 1, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47483
- { x: 2, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
47484
- { x: 3, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47485
- { x: 4, y: 2, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47486
- { x: 0, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47487
- { x: 1, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47488
- { x: 2, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47489
- { x: 3, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
47490
- { x: 4, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47491
- { x: 0, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47492
- { x: 1, y: 4, terrain: "water", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
47493
- { x: 2, y: 4, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47494
- { x: 3, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47495
- { x: 4, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` }
50012
+ { x: 0, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50013
+ { x: 1, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50014
+ { x: 2, y: 0, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
50015
+ { x: 3, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50016
+ { x: 4, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50017
+ { x: 0, y: 1, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50018
+ { x: 1, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
50019
+ { x: 2, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
50020
+ { x: 3, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
50021
+ { x: 4, y: 1, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
50022
+ { x: 0, y: 2, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
50023
+ { x: 1, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
50024
+ { x: 2, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
50025
+ { x: 3, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
50026
+ { x: 4, y: 2, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50027
+ { x: 0, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50028
+ { x: 1, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
50029
+ { x: 2, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
50030
+ { x: 3, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
50031
+ { x: 4, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50032
+ { x: 0, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50033
+ { x: 1, y: 4, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
50034
+ { x: 2, y: 4, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
50035
+ { x: 3, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50036
+ { x: 4, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` }
47496
50037
  ];
47497
50038
  DEFAULT_WORLDMAP_UNITS = [
47498
- { id: "h1", position: { x: 1, y: 1 }, unitType: "hero", name: "Amir", team: "player", health: 10, maxHealth: 10, sprite: `${CDN3}/sprite-sheets/amir-sprite-sheet-se.png` },
47499
- { id: "h2", position: { x: 3, y: 3 }, unitType: "scout", name: "Archivist", team: "player", health: 10, maxHealth: 10, sprite: `${CDN3}/sprite-sheets/archivist-sprite-sheet-se.png` }
50039
+ { id: "h1", position: { x: 1, y: 1 }, unitType: "hero", name: "Amir", team: "player", health: 10, maxHealth: 10, sprite: `${CDN7}/sprite-sheets/amir-sprite-sheet-se.png` },
50040
+ { id: "h2", position: { x: 3, y: 3 }, unitType: "scout", name: "Archivist", team: "player", health: 10, maxHealth: 10, sprite: `${CDN7}/sprite-sheets/archivist-sprite-sheet-se.png` }
47500
50041
  ];
47501
50042
  DEFAULT_WORLDMAP_FEATURES = [
47502
- { id: "f1", x: 2, y: 2, type: "capital", sprite: `${CDN3}/scenes/world/capital.png` },
47503
- { id: "f2", x: 4, y: 2, type: "power_node", sprite: `${CDN3}/world-map/power_node.png` }
50043
+ { id: "f1", x: 2, y: 2, type: "capital", sprite: `${CDN7}/scenes/world/capital.png` },
50044
+ { id: "f2", x: 4, y: 2, type: "power_node", sprite: `${CDN7}/world-map/power_node.png` }
47504
50045
  ];
47505
50046
  DEFAULT_WORLDMAP_MANIFEST = {
47506
- baseUrl: CDN3,
50047
+ baseUrl: CDN7,
47507
50048
  terrains: {
47508
50049
  grass: "/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png",
47509
50050
  water: "/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png",
@@ -47548,7 +50089,7 @@ function lazyThree(name, loader) {
47548
50089
  ThreeWrapper.displayName = `Lazy(${name})`;
47549
50090
  return ThreeWrapper;
47550
50091
  }
47551
- var ThreeBoundary, FeatureRenderer, GameCanvas3D, GameCanvas3DBattleTemplate, GameCanvas3DCastleTemplate, GameCanvas3DWorldMapTemplate, COMPONENT_REGISTRY;
50092
+ var ThreeBoundary, FeatureRenderer, GameCanvas3D2, GameCanvas3DBattleTemplate, GameCanvas3DCastleTemplate, GameCanvas3DWorldMapTemplate, COMPONENT_REGISTRY;
47552
50093
  var init_component_registry_generated = __esm({
47553
50094
  "components/core/organisms/component-registry.generated.ts"() {
47554
50095
  init_AboutPageTemplate();
@@ -47658,6 +50199,7 @@ var init_component_registry_generated = __esm({
47658
50199
  init_FormSectionHeader();
47659
50200
  init_GameAudioProvider();
47660
50201
  init_GameAudioToggle();
50202
+ init_GameBoard3D();
47661
50203
  init_GameCanvas2D();
47662
50204
  init_GameHud();
47663
50205
  init_GameMenu();
@@ -47715,7 +50257,9 @@ var init_component_registry_generated = __esm({
47715
50257
  init_PageHeader();
47716
50258
  init_Pagination();
47717
50259
  init_PatternTile();
50260
+ init_PlatformerBoard();
47718
50261
  init_PlatformerCanvas();
50262
+ init_PlatformerTemplate();
47719
50263
  init_Popover();
47720
50264
  init_PositionedCanvas();
47721
50265
  init_PowerupSlots();
@@ -47739,6 +50283,8 @@ var init_component_registry_generated = __esm({
47739
50283
  init_ResourceBar();
47740
50284
  init_ResourceCounter();
47741
50285
  init_RichBlockEditor();
50286
+ init_RoguelikeBoard();
50287
+ init_RoguelikeTemplate();
47742
50288
  init_RuntimeDebugger2();
47743
50289
  init_ScaledDiagram();
47744
50290
  init_ScoreBoard();
@@ -47811,6 +50357,8 @@ var init_component_registry_generated = __esm({
47811
50357
  init_Toast();
47812
50358
  init_ToastSlot();
47813
50359
  init_Tooltip();
50360
+ init_TowerDefenseBoard();
50361
+ init_TowerDefenseTemplate();
47814
50362
  init_TraitFrame();
47815
50363
  init_TraitSlot();
47816
50364
  init_TrendIndicator();
@@ -47855,7 +50403,7 @@ var init_component_registry_generated = __esm({
47855
50403
  }
47856
50404
  };
47857
50405
  FeatureRenderer = lazyThree("FeatureRenderer", () => import('@almadar/ui/components/molecules/game/three'));
47858
- GameCanvas3D = lazyThree("GameCanvas3D", () => import('@almadar/ui/components/molecules/game/three'));
50406
+ GameCanvas3D2 = lazyThree("GameCanvas3D", () => import('@almadar/ui/components/molecules/game/three'));
47859
50407
  GameCanvas3DBattleTemplate = lazyThree("GameCanvas3DBattleTemplate", () => import('@almadar/ui/components/molecules/game/three'));
47860
50408
  GameCanvas3DCastleTemplate = lazyThree("GameCanvas3DCastleTemplate", () => import('@almadar/ui/components/molecules/game/three'));
47861
50409
  GameCanvas3DWorldMapTemplate = lazyThree("GameCanvas3DWorldMapTemplate", () => import('@almadar/ui/components/molecules/game/three'));
@@ -47974,8 +50522,9 @@ var init_component_registry_generated = __esm({
47974
50522
  "FormSectionHeader": FormSectionHeader,
47975
50523
  "GameAudioProvider": GameAudioProvider,
47976
50524
  "GameAudioToggle": GameAudioToggle,
50525
+ "GameBoard3D": GameBoard3D,
47977
50526
  "GameCanvas2D": GameCanvas2D,
47978
- "GameCanvas3D": GameCanvas3D,
50527
+ "GameCanvas3D": GameCanvas3D2,
47979
50528
  "GameCanvas3DBattleTemplate": GameCanvas3DBattleTemplate,
47980
50529
  "GameCanvas3DCastleTemplate": GameCanvas3DCastleTemplate,
47981
50530
  "GameCanvas3DWorldMapTemplate": GameCanvas3DWorldMapTemplate,
@@ -48037,7 +50586,9 @@ var init_component_registry_generated = __esm({
48037
50586
  "PageHeader": PageHeader,
48038
50587
  "Pagination": Pagination,
48039
50588
  "PatternTile": PatternTile,
50589
+ "PlatformerBoard": PlatformerBoard,
48040
50590
  "PlatformerCanvas": PlatformerCanvas,
50591
+ "PlatformerTemplate": PlatformerTemplate,
48041
50592
  "Popover": Popover,
48042
50593
  "PositionedCanvas": PositionedCanvas,
48043
50594
  "PowerupSlots": PowerupSlots,
@@ -48061,6 +50612,8 @@ var init_component_registry_generated = __esm({
48061
50612
  "ResourceBar": ResourceBar,
48062
50613
  "ResourceCounter": ResourceCounter,
48063
50614
  "RichBlockEditor": RichBlockEditor,
50615
+ "RoguelikeBoard": RoguelikeBoard,
50616
+ "RoguelikeTemplate": RoguelikeTemplate,
48064
50617
  "RuntimeDebugger": RuntimeDebugger,
48065
50618
  "ScaledDiagram": ScaledDiagram,
48066
50619
  "ScoreBoard": ScoreBoard,
@@ -48136,6 +50689,8 @@ var init_component_registry_generated = __esm({
48136
50689
  "Toast": Toast,
48137
50690
  "ToastSlot": ToastSlot,
48138
50691
  "Tooltip": Tooltip,
50692
+ "TowerDefenseBoard": TowerDefenseBoard,
50693
+ "TowerDefenseTemplate": TowerDefenseTemplate,
48139
50694
  "TraitFrame": TraitFrame,
48140
50695
  "TraitSlot": TraitSlot,
48141
50696
  "TrendIndicator": TrendIndicator,
@@ -49307,7 +51862,7 @@ init_UISlotRenderer();
49307
51862
  // providers/VerificationProvider.tsx
49308
51863
  init_useEventBus();
49309
51864
  init_verificationRegistry();
49310
- var log11 = logger.createLogger("almadar:verify");
51865
+ var log13 = logger.createLogger("almadar:verify");
49311
51866
  var DISPATCH_SUFFIX = ":DISPATCH";
49312
51867
  var SUCCESS_SUFFIX = ":SUCCESS";
49313
51868
  var ERROR_SUFFIX = ":ERROR";
@@ -49354,7 +51909,7 @@ function VerificationProvider({
49354
51909
  const verificationProviderLifecycleListener = (evt) => {
49355
51910
  const parsed = parseLifecycleEvent(evt.type);
49356
51911
  if (!parsed) return;
49357
- log11.debug("lifecycle:event", { kind: parsed.kind, traitName: parsed.traitName, event: parsed.event, type: evt.type });
51912
+ log13.debug("lifecycle:event", { kind: parsed.kind, traitName: parsed.traitName, event: parsed.event, type: evt.type });
49358
51913
  const payload = evt.payload ?? {};
49359
51914
  if (parsed.kind === "dispatch") {
49360
51915
  const key = `${parsed.traitName}:${String(payload["event"] ?? "")}`;
@@ -49409,7 +51964,7 @@ function VerificationProvider({
49409
51964
  },
49410
51965
  timestamp: Date.now()
49411
51966
  });
49412
- log11.debug("transition:success", { trait: parsed.traitName, event: parsed.event, from: pending?.from, to: newState, effectCount: effects.length });
51967
+ log13.debug("transition:success", { trait: parsed.traitName, event: parsed.event, from: pending?.from, to: newState, effectCount: effects.length });
49413
51968
  } else if (parsed.kind === "error" && parsed.event) {
49414
51969
  const key = `${parsed.traitName}:${parsed.event}`;
49415
51970
  const pending = pendingRef.current.get(key);
@@ -49439,7 +51994,7 @@ function VerificationProvider({
49439
51994
  },
49440
51995
  timestamp: Date.now()
49441
51996
  });
49442
- log11.warn("transition:error", { trait: parsed.traitName, event: parsed.event, from: fromState, error: errorMsg });
51997
+ log13.warn("transition:error", { trait: parsed.traitName, event: parsed.event, from: fromState, error: errorMsg });
49443
51998
  }
49444
51999
  };
49445
52000
  Object.defineProperty(verificationProviderLifecycleListener, "name", {