@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
@@ -39,6 +39,12 @@ var core = require('@almadar/core');
39
39
  var core$1 = require('@dnd-kit/core');
40
40
  var sortable = require('@dnd-kit/sortable');
41
41
  var utilities = require('@dnd-kit/utilities');
42
+ var THREE = require('three');
43
+ var GLTFLoader_js = require('three/examples/jsm/loaders/GLTFLoader.js');
44
+ var OBJLoader_js = require('three/examples/jsm/loaders/OBJLoader.js');
45
+ var GLTFLoader = require('three/examples/jsm/loaders/GLTFLoader');
46
+ var fiber = require('@react-three/fiber');
47
+ var drei = require('@react-three/drei');
42
48
  var patterns = require('@almadar/patterns');
43
49
  var OrbitalServerRuntime = require('@almadar/runtime/OrbitalServerRuntime');
44
50
  var runtime = require('@almadar/runtime');
@@ -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;
@@ -3219,8 +3226,8 @@ var init_Typography = __esm({
3219
3226
  children
3220
3227
  }) => {
3221
3228
  const variant = variantProp ?? (level ? `h${level}` : "body1");
3222
- const Component = as || defaultElements[variant];
3223
- const Comp = Component;
3229
+ const Component2 = as || defaultElements[variant];
3230
+ const Comp = Component2;
3224
3231
  return /* @__PURE__ */ jsxRuntime.jsx(
3225
3232
  Comp,
3226
3233
  {
@@ -3529,7 +3536,7 @@ var init_Box = __esm({
3529
3536
  position,
3530
3537
  className,
3531
3538
  children,
3532
- as: Component = "div",
3539
+ as: Component2 = "div",
3533
3540
  action,
3534
3541
  actionPayload,
3535
3542
  hoverEvent,
@@ -3561,7 +3568,7 @@ var init_Box = __esm({
3561
3568
  }, [hoverEvent, eventBus, onMouseLeave]);
3562
3569
  const isClickable = action || onClick;
3563
3570
  return React91__namespace.default.createElement(
3564
- Component,
3571
+ Component2,
3565
3572
  {
3566
3573
  ref,
3567
3574
  className: cn(
@@ -3637,7 +3644,7 @@ var init_Stack = __esm({
3637
3644
  className,
3638
3645
  style,
3639
3646
  children,
3640
- as: Component = "div",
3647
+ as: Component2 = "div",
3641
3648
  onClick,
3642
3649
  onKeyDown,
3643
3650
  role,
@@ -3655,7 +3662,7 @@ var init_Stack = __esm({
3655
3662
  };
3656
3663
  const isHorizontal = direction === "horizontal";
3657
3664
  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";
3658
- const Comp = Component;
3665
+ const Comp = Component2;
3659
3666
  return /* @__PURE__ */ jsxRuntime.jsx(
3660
3667
  Comp,
3661
3668
  {
@@ -4569,9 +4576,9 @@ function resolvePhosphor(name, weight, family) {
4569
4576
  (lib) => {
4570
4577
  const PhosphorComp = lib[target];
4571
4578
  if (!PhosphorComp || typeof PhosphorComp !== "object") return null;
4572
- const Component = PhosphorComp;
4579
+ const Component2 = PhosphorComp;
4573
4580
  const Adapter = (props) => /* @__PURE__ */ jsxRuntime.jsx(
4574
- Component,
4581
+ Component2,
4575
4582
  {
4576
4583
  weight,
4577
4584
  className: props.className,
@@ -4595,9 +4602,9 @@ function resolveTabler(name, family) {
4595
4602
  (lib) => {
4596
4603
  const TablerComp = lib[target];
4597
4604
  if (!TablerComp || typeof TablerComp !== "object") return null;
4598
- const Component = TablerComp;
4605
+ const Component2 = TablerComp;
4599
4606
  const Adapter = (props) => /* @__PURE__ */ jsxRuntime.jsx(
4600
- Component,
4607
+ Component2,
4601
4608
  {
4602
4609
  stroke: props.strokeWidth ?? 1.5,
4603
4610
  className: props.className,
@@ -4621,9 +4628,9 @@ function resolveFa(name, family) {
4621
4628
  (lib) => {
4622
4629
  const FaComp = lib[target];
4623
4630
  if (!FaComp || typeof FaComp !== "function") return null;
4624
- const Component = FaComp;
4631
+ const Component2 = FaComp;
4625
4632
  const Adapter = (props) => /* @__PURE__ */ jsxRuntime.jsx(
4626
- Component,
4633
+ Component2,
4627
4634
  {
4628
4635
  className: props.className,
4629
4636
  style: props.style,
@@ -8015,10 +8022,10 @@ var init_Center = __esm({
8015
8022
  className,
8016
8023
  style,
8017
8024
  children,
8018
- as: Component = "div"
8025
+ as: Component2 = "div"
8019
8026
  }) => {
8020
8027
  const mergedStyle = minHeight ? { minHeight, ...style } : style;
8021
- const Comp = Component;
8028
+ const Comp = Component2;
8022
8029
  return /* @__PURE__ */ jsxRuntime.jsx(
8023
8030
  Comp,
8024
8031
  {
@@ -10812,6 +10819,7 @@ var init_HealthBar = __esm({
10812
10819
  }
10813
10820
  });
10814
10821
  function ScoreDisplay({
10822
+ assetUrl = DEFAULT_ASSET_URL,
10815
10823
  value,
10816
10824
  label,
10817
10825
  icon,
@@ -10858,14 +10866,24 @@ function ScoreDisplay({
10858
10866
  className
10859
10867
  ),
10860
10868
  children: [
10861
- icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-shrink-0", children: typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: icon }) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon }) }),
10869
+ assetUrl ? /* @__PURE__ */ jsxRuntime.jsx(
10870
+ "img",
10871
+ {
10872
+ src: assetUrl,
10873
+ alt: "",
10874
+ width: 20,
10875
+ height: 20,
10876
+ style: { imageRendering: "pixelated", objectFit: "contain" },
10877
+ className: "flex-shrink-0"
10878
+ }
10879
+ ) : 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,
10862
10880
  label && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: label }),
10863
10881
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "tabular-nums", children: formattedValue })
10864
10882
  ]
10865
10883
  }
10866
10884
  );
10867
10885
  }
10868
- var sizeMap2;
10886
+ var sizeMap2, DEFAULT_ASSET_URL;
10869
10887
  var init_ScoreDisplay = __esm({
10870
10888
  "components/game/atoms/ScoreDisplay.tsx"() {
10871
10889
  "use client";
@@ -10877,10 +10895,12 @@ var init_ScoreDisplay = __esm({
10877
10895
  lg: "text-2xl",
10878
10896
  xl: "text-4xl"
10879
10897
  };
10898
+ DEFAULT_ASSET_URL = "https://almadar-kflow-assets.web.app/shared/effects/particles/star_01.png";
10880
10899
  ScoreDisplay.displayName = "ScoreDisplay";
10881
10900
  }
10882
10901
  });
10883
10902
  function ControlButton({
10903
+ assetUrl = DEFAULT_ASSET_URL2,
10884
10904
  label,
10885
10905
  icon,
10886
10906
  size = "md",
@@ -10927,7 +10947,7 @@ function ControlButton({
10927
10947
  },
10928
10948
  [isPressed, releaseEvent, eventBus, onRelease]
10929
10949
  );
10930
- return /* @__PURE__ */ jsxRuntime.jsxs(
10950
+ return /* @__PURE__ */ jsxRuntime.jsx(
10931
10951
  "button",
10932
10952
  {
10933
10953
  type: "button",
@@ -10948,20 +10968,27 @@ function ControlButton({
10948
10968
  disabled && "opacity-50 cursor-not-allowed",
10949
10969
  className
10950
10970
  ),
10951
- children: [
10952
- icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-2xl", children: typeof icon === "string" ? /^[a-zA-Z0-9-]+$/.test(icon) ? (() => {
10953
- const I = resolveIcon(icon);
10954
- return I ? /* @__PURE__ */ jsxRuntime.jsx(I, { className: "w-6 h-6" }) : null;
10955
- })() : icon : /* @__PURE__ */ (() => {
10956
- const I = icon;
10957
- return /* @__PURE__ */ jsxRuntime.jsx(I, { className: "w-6 h-6" });
10958
- })() }),
10959
- label && !icon && /* @__PURE__ */ jsxRuntime.jsx("span", { children: label })
10960
- ]
10971
+ children: assetUrl ? /* @__PURE__ */ jsxRuntime.jsx(
10972
+ "img",
10973
+ {
10974
+ src: assetUrl,
10975
+ alt: "",
10976
+ width: 24,
10977
+ height: 24,
10978
+ style: { imageRendering: "pixelated", objectFit: "contain" },
10979
+ className: "flex-shrink-0"
10980
+ }
10981
+ ) : icon ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-2xl", children: typeof icon === "string" ? /^[a-zA-Z0-9-]+$/.test(icon) ? (() => {
10982
+ const I = resolveIcon(icon);
10983
+ return I ? /* @__PURE__ */ jsxRuntime.jsx(I, { className: "w-6 h-6" }) : null;
10984
+ })() : icon : /* @__PURE__ */ (() => {
10985
+ const I = icon;
10986
+ return /* @__PURE__ */ jsxRuntime.jsx(I, { className: "w-6 h-6" });
10987
+ })() }) : label ? /* @__PURE__ */ jsxRuntime.jsx("span", { children: label }) : null
10961
10988
  }
10962
10989
  );
10963
10990
  }
10964
- var sizeMap3, shapeMap, variantMap;
10991
+ var sizeMap3, shapeMap, variantMap, DEFAULT_ASSET_URL2;
10965
10992
  var init_ControlButton = __esm({
10966
10993
  "components/game/atoms/ControlButton.tsx"() {
10967
10994
  "use client";
@@ -10984,6 +11011,7 @@ var init_ControlButton = __esm({
10984
11011
  secondary: "bg-secondary text-secondary-foreground border-border hover:bg-secondary-hover",
10985
11012
  ghost: "bg-transparent text-foreground border-border hover:bg-muted"
10986
11013
  };
11014
+ DEFAULT_ASSET_URL2 = "https://almadar-kflow-assets.web.app/shared/effects/particles/circle_01.png";
10987
11015
  ControlButton.displayName = "ControlButton";
10988
11016
  }
10989
11017
  });
@@ -11063,6 +11091,7 @@ var init_Sprite = __esm({
11063
11091
  }
11064
11092
  });
11065
11093
  function StateIndicator({
11094
+ assetUrl = DEFAULT_ASSET_URL3,
11066
11095
  state = "idle",
11067
11096
  label,
11068
11097
  size = "md",
@@ -11085,18 +11114,29 @@ function StateIndicator({
11085
11114
  className
11086
11115
  ),
11087
11116
  children: [
11088
- /* @__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 }) }),
11117
+ /* @__PURE__ */ jsxRuntime.jsx(Box, { as: "span", children: assetUrl ? /* @__PURE__ */ jsxRuntime.jsx(
11118
+ "img",
11119
+ {
11120
+ src: assetUrl,
11121
+ alt: displayLabel,
11122
+ width: 16,
11123
+ height: 16,
11124
+ style: { imageRendering: "pixelated", objectFit: "contain" },
11125
+ className: "flex-shrink-0"
11126
+ }
11127
+ ) : 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 }) }),
11089
11128
  /* @__PURE__ */ jsxRuntime.jsx(Box, { as: "span", children: displayLabel })
11090
11129
  ]
11091
11130
  }
11092
11131
  );
11093
11132
  }
11094
- var DEFAULT_STATE_STYLES, DEFAULT_STYLE, SIZE_CLASSES;
11133
+ var DEFAULT_ASSET_URL3, DEFAULT_STATE_STYLES, DEFAULT_STYLE, SIZE_CLASSES;
11095
11134
  var init_StateIndicator = __esm({
11096
11135
  "components/game/atoms/StateIndicator.tsx"() {
11097
11136
  init_Box();
11098
11137
  init_Icon();
11099
11138
  init_cn();
11139
+ DEFAULT_ASSET_URL3 = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
11100
11140
  DEFAULT_STATE_STYLES = {
11101
11141
  idle: { icon: "\u23F8", bgClass: "bg-muted" },
11102
11142
  active: { icon: "\u25B6", bgClass: "bg-success" },
@@ -11171,6 +11211,7 @@ var init_TimerDisplay = __esm({
11171
11211
  }
11172
11212
  });
11173
11213
  function ResourceCounter({
11214
+ assetUrl = DEFAULT_ASSET_URL4,
11174
11215
  icon,
11175
11216
  label = "Gold",
11176
11217
  value = 250,
@@ -11190,7 +11231,17 @@ function ResourceCounter({
11190
11231
  className
11191
11232
  ),
11192
11233
  children: [
11193
- 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 }) }),
11234
+ assetUrl ? /* @__PURE__ */ jsxRuntime.jsx(
11235
+ "img",
11236
+ {
11237
+ src: assetUrl,
11238
+ alt: label,
11239
+ width: sizes.img,
11240
+ height: sizes.img,
11241
+ style: { imageRendering: "pixelated", objectFit: "contain" },
11242
+ className: "flex-shrink-0"
11243
+ }
11244
+ ) : 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,
11194
11245
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: label }),
11195
11246
  /* @__PURE__ */ jsxRuntime.jsxs("span", { className: cn("font-bold tabular-nums", color && (color in colorTokenClasses2 ? colorTokenClasses2[color] : color)), children: [
11196
11247
  value,
@@ -11203,7 +11254,7 @@ function ResourceCounter({
11203
11254
  }
11204
11255
  );
11205
11256
  }
11206
- var colorTokenClasses2, sizeMap5;
11257
+ var colorTokenClasses2, DEFAULT_ASSET_URL4, sizeMap5;
11207
11258
  var init_ResourceCounter = __esm({
11208
11259
  "components/game/atoms/ResourceCounter.tsx"() {
11209
11260
  init_cn();
@@ -11216,16 +11267,17 @@ var init_ResourceCounter = __esm({
11216
11267
  error: "text-error",
11217
11268
  muted: "text-muted-foreground"
11218
11269
  };
11270
+ DEFAULT_ASSET_URL4 = "https://almadar-kflow-assets.web.app/shared/world-map/gold_mine.png";
11219
11271
  sizeMap5 = {
11220
- sm: { wrapper: "text-xs gap-1 px-1.5 py-0.5", icon: "text-sm" },
11221
- md: { wrapper: "text-sm gap-1.5 px-2 py-1", icon: "text-base" },
11222
- lg: { wrapper: "text-base gap-2 px-3 py-1.5", icon: "text-lg" }
11272
+ sm: { wrapper: "text-xs gap-1 px-1.5 py-0.5", icon: "text-sm", img: 16 },
11273
+ md: { wrapper: "text-sm gap-1.5 px-2 py-1", icon: "text-base", img: 20 },
11274
+ lg: { wrapper: "text-base gap-2 px-3 py-1.5", icon: "text-lg", img: 28 }
11223
11275
  };
11224
11276
  ResourceCounter.displayName = "ResourceCounter";
11225
11277
  }
11226
11278
  });
11227
11279
  function ItemSlot({
11228
- assetUrl = DEFAULT_ASSET_URL,
11280
+ assetUrl = DEFAULT_ASSET_URL5,
11229
11281
  icon = "sword",
11230
11282
  label = "Iron Sword",
11231
11283
  quantity,
@@ -11284,7 +11336,7 @@ function ItemSlot({
11284
11336
  }
11285
11337
  );
11286
11338
  }
11287
- var sizeMap6, rarityBorderMap, rarityGlowMap, DEFAULT_ASSET_URL, assetSizeMap;
11339
+ var sizeMap6, rarityBorderMap, rarityGlowMap, DEFAULT_ASSET_URL5, assetSizeMap;
11288
11340
  var init_ItemSlot = __esm({
11289
11341
  "components/game/atoms/ItemSlot.tsx"() {
11290
11342
  "use client";
@@ -11309,7 +11361,7 @@ var init_ItemSlot = __esm({
11309
11361
  epic: "shadow-lg",
11310
11362
  legendary: "shadow-lg"
11311
11363
  };
11312
- DEFAULT_ASSET_URL = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
11364
+ DEFAULT_ASSET_URL5 = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
11313
11365
  assetSizeMap = {
11314
11366
  sm: 28,
11315
11367
  md: 40,
@@ -11319,6 +11371,7 @@ var init_ItemSlot = __esm({
11319
11371
  }
11320
11372
  });
11321
11373
  function TurnIndicator({
11374
+ assetUrl = DEFAULT_ASSET_URL6,
11322
11375
  currentTurn = 1,
11323
11376
  maxTurns,
11324
11377
  activeTeam,
@@ -11351,14 +11404,24 @@ function TurnIndicator({
11351
11404
  ] }),
11352
11405
  activeTeam && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
11353
11406
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: "|" }),
11354
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("rounded-full bg-success/20", sizes.dot) }),
11407
+ assetUrl ? /* @__PURE__ */ jsxRuntime.jsx(
11408
+ "img",
11409
+ {
11410
+ src: assetUrl,
11411
+ alt: "",
11412
+ width: 12,
11413
+ height: 12,
11414
+ style: { imageRendering: "pixelated", objectFit: "contain" },
11415
+ className: "flex-shrink-0"
11416
+ }
11417
+ ) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("rounded-full bg-success/20", sizes.dot) }),
11355
11418
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-success", children: activeTeam })
11356
11419
  ] })
11357
11420
  ]
11358
11421
  }
11359
11422
  );
11360
11423
  }
11361
- var sizeMap7;
11424
+ var sizeMap7, DEFAULT_ASSET_URL6;
11362
11425
  var init_TurnIndicator = __esm({
11363
11426
  "components/game/atoms/TurnIndicator.tsx"() {
11364
11427
  init_cn();
@@ -11367,6 +11430,7 @@ var init_TurnIndicator = __esm({
11367
11430
  md: { wrapper: "text-sm gap-2 px-3 py-1", dot: "w-2 h-2" },
11368
11431
  lg: { wrapper: "text-base gap-2.5 px-4 py-1.5", dot: "w-2.5 h-2.5" }
11369
11432
  };
11433
+ DEFAULT_ASSET_URL6 = "https://almadar-kflow-assets.web.app/shared/effects/particles/symbol_01.png";
11370
11434
  TurnIndicator.displayName = "TurnIndicator";
11371
11435
  }
11372
11436
  });
@@ -11382,6 +11446,7 @@ function getComboScale(combo) {
11382
11446
  return "";
11383
11447
  }
11384
11448
  function ComboCounter({
11449
+ assetUrl = DEFAULT_ASSET_URL7,
11385
11450
  combo = 5,
11386
11451
  multiplier,
11387
11452
  streak,
@@ -11401,6 +11466,17 @@ function ComboCounter({
11401
11466
  className
11402
11467
  ),
11403
11468
  children: [
11469
+ assetUrl && /* @__PURE__ */ jsxRuntime.jsx(
11470
+ "img",
11471
+ {
11472
+ src: assetUrl,
11473
+ alt: "combo",
11474
+ width: 24,
11475
+ height: 24,
11476
+ style: { imageRendering: "pixelated", objectFit: "contain" },
11477
+ className: "flex-shrink-0 mb-0.5"
11478
+ }
11479
+ ),
11404
11480
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("font-black tabular-nums leading-none", sizes.combo, getComboIntensity(combo)), children: combo }),
11405
11481
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("font-bold uppercase tracking-wider text-muted-foreground", sizes.label), children: "combo" }),
11406
11482
  multiplier != null && multiplier > 1 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: cn("font-bold text-warning tabular-nums", sizes.multiplier), children: [
@@ -11415,10 +11491,11 @@ function ComboCounter({
11415
11491
  }
11416
11492
  );
11417
11493
  }
11418
- var sizeMap8;
11494
+ var DEFAULT_ASSET_URL7, sizeMap8;
11419
11495
  var init_ComboCounter = __esm({
11420
11496
  "components/game/atoms/ComboCounter.tsx"() {
11421
11497
  init_cn();
11498
+ DEFAULT_ASSET_URL7 = "https://almadar-kflow-assets.web.app/shared/effects/flash/flash00.png";
11422
11499
  sizeMap8 = {
11423
11500
  sm: { combo: "text-lg", label: "text-xs", multiplier: "text-xs" },
11424
11501
  md: { combo: "text-2xl", label: "text-xs", multiplier: "text-sm" },
@@ -11512,6 +11589,7 @@ var init_XPBar = __esm({
11512
11589
  }
11513
11590
  });
11514
11591
  function WaypointMarker({
11592
+ assetUrl = DEFAULT_ASSET_URL8,
11515
11593
  label,
11516
11594
  icon,
11517
11595
  active = true,
@@ -11550,7 +11628,17 @@ function WaypointMarker({
11550
11628
  active && !completed && "bg-info text-foreground",
11551
11629
  !active && !completed && "bg-muted"
11552
11630
  ),
11553
- children: completed ? checkIcon : icon && (typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: icon }) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon }))
11631
+ children: completed ? checkIcon : assetUrl ? /* @__PURE__ */ jsxRuntime.jsx(
11632
+ "img",
11633
+ {
11634
+ src: assetUrl,
11635
+ alt: label,
11636
+ width: sizes.img,
11637
+ height: sizes.img,
11638
+ style: { imageRendering: "pixelated", objectFit: "contain" },
11639
+ className: "flex-shrink-0"
11640
+ }
11641
+ ) : icon ? typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: icon }) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon }) : null
11554
11642
  }
11555
11643
  )
11556
11644
  ] }),
@@ -11567,15 +11655,16 @@ function WaypointMarker({
11567
11655
  )
11568
11656
  ] });
11569
11657
  }
11570
- var sizeMap10, checkIcon;
11658
+ var DEFAULT_ASSET_URL8, sizeMap10, checkIcon;
11571
11659
  var init_WaypointMarker = __esm({
11572
11660
  "components/game/atoms/WaypointMarker.tsx"() {
11573
11661
  init_cn();
11574
11662
  init_Icon();
11663
+ DEFAULT_ASSET_URL8 = "https://almadar-kflow-assets.web.app/shared/world-map/battle_marker.png";
11575
11664
  sizeMap10 = {
11576
- sm: { dot: "w-4 h-4", ring: "w-6 h-6", label: "text-xs mt-1" },
11577
- md: { dot: "w-6 h-6", ring: "w-8 h-8", label: "text-sm mt-1.5" },
11578
- lg: { dot: "w-8 h-8", ring: "w-10 h-10", label: "text-base mt-2" }
11665
+ sm: { dot: "w-4 h-4", ring: "w-6 h-6", label: "text-xs mt-1", img: 12 },
11666
+ md: { dot: "w-6 h-6", ring: "w-8 h-8", label: "text-sm mt-1.5", img: 18 },
11667
+ lg: { dot: "w-8 h-8", ring: "w-10 h-10", label: "text-base mt-2", img: 24 }
11579
11668
  };
11580
11669
  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" }) });
11581
11670
  WaypointMarker.displayName = "WaypointMarker";
@@ -11588,6 +11677,7 @@ function formatDuration(seconds) {
11588
11677
  return `${Math.round(seconds)}s`;
11589
11678
  }
11590
11679
  function StatusEffect({
11680
+ assetUrl = DEFAULT_ASSET_URL9,
11591
11681
  icon = "shield",
11592
11682
  label = "Shield",
11593
11683
  duration = 30,
@@ -11608,7 +11698,17 @@ function StatusEffect({
11608
11698
  ),
11609
11699
  title: label,
11610
11700
  children: [
11611
- /* @__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" }) }),
11701
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("flex items-center justify-center", sizes.icon), children: assetUrl ? /* @__PURE__ */ jsxRuntime.jsx(
11702
+ "img",
11703
+ {
11704
+ src: assetUrl,
11705
+ alt: label,
11706
+ width: sizes.img,
11707
+ height: sizes.img,
11708
+ style: { imageRendering: "pixelated", objectFit: "contain" },
11709
+ className: "flex-shrink-0"
11710
+ }
11711
+ ) : typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: icon, size: "sm" }) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon, size: "sm" }) }),
11612
11712
  duration !== void 0 && /* @__PURE__ */ jsxRuntime.jsx(
11613
11713
  "span",
11614
11714
  {
@@ -11635,15 +11735,16 @@ function StatusEffect({
11635
11735
  label && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground mt-0.5 text-center whitespace-nowrap", children: label })
11636
11736
  ] });
11637
11737
  }
11638
- var sizeMap11, variantStyles7;
11738
+ var DEFAULT_ASSET_URL9, sizeMap11, variantStyles7;
11639
11739
  var init_StatusEffect = __esm({
11640
11740
  "components/game/atoms/StatusEffect.tsx"() {
11641
11741
  init_cn();
11642
11742
  init_Icon();
11743
+ DEFAULT_ASSET_URL9 = "https://almadar-kflow-assets.web.app/shared/effects/particles/flame_01.png";
11643
11744
  sizeMap11 = {
11644
- sm: { container: "w-8 h-8", icon: "text-sm", badge: "text-xs -top-1 -right-1 w-4 h-4", timer: "text-[9px]" },
11645
- md: { container: "w-10 h-10", icon: "text-base", badge: "text-xs -top-1 -right-1 w-5 h-5", timer: "text-xs" },
11646
- 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" }
11745
+ 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 },
11746
+ 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 },
11747
+ 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 }
11647
11748
  };
11648
11749
  variantStyles7 = {
11649
11750
  buff: "border-success bg-success/20",
@@ -11654,6 +11755,7 @@ var init_StatusEffect = __esm({
11654
11755
  }
11655
11756
  });
11656
11757
  function DamageNumber({
11758
+ assetUrl = DEFAULT_ASSET_URL10,
11657
11759
  value = 42,
11658
11760
  type = "damage",
11659
11761
  size = "md",
@@ -11662,22 +11764,35 @@ function DamageNumber({
11662
11764
  const displayText = type === "miss" ? "MISS" : type === "heal" ? `+${value}` : `${value}`;
11663
11765
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
11664
11766
  /* @__PURE__ */ jsxRuntime.jsx("style", { children: floatKeyframes }),
11665
- /* @__PURE__ */ jsxRuntime.jsx(
11767
+ /* @__PURE__ */ jsxRuntime.jsxs(
11666
11768
  "span",
11667
11769
  {
11668
11770
  className: cn(
11669
- "inline-block font-mono select-none pointer-events-none",
11771
+ "inline-flex items-center gap-0.5 font-mono select-none pointer-events-none",
11670
11772
  sizeMap12[size],
11671
11773
  typeStyles[type],
11672
11774
  className
11673
11775
  ),
11674
11776
  style: { animation: "damageFloat 1s ease-out forwards" },
11675
- children: displayText
11777
+ children: [
11778
+ assetUrl && /* @__PURE__ */ jsxRuntime.jsx(
11779
+ "img",
11780
+ {
11781
+ src: assetUrl,
11782
+ alt: "",
11783
+ width: 14,
11784
+ height: 14,
11785
+ style: { imageRendering: "pixelated", objectFit: "contain" },
11786
+ className: "flex-shrink-0"
11787
+ }
11788
+ ),
11789
+ displayText
11790
+ ]
11676
11791
  }
11677
11792
  )
11678
11793
  ] });
11679
11794
  }
11680
- var sizeMap12, typeStyles, floatKeyframes;
11795
+ var sizeMap12, typeStyles, floatKeyframes, DEFAULT_ASSET_URL10;
11681
11796
  var init_DamageNumber = __esm({
11682
11797
  "components/game/atoms/DamageNumber.tsx"() {
11683
11798
  init_cn();
@@ -11699,6 +11814,7 @@ var init_DamageNumber = __esm({
11699
11814
  100% { opacity: 0; transform: translateY(-32px) scale(0.8); }
11700
11815
  }
11701
11816
  `;
11817
+ DEFAULT_ASSET_URL10 = "https://almadar-kflow-assets.web.app/shared/effects/particles/spark_01.png";
11702
11818
  DamageNumber.displayName = "DamageNumber";
11703
11819
  }
11704
11820
  });
@@ -11787,6 +11903,7 @@ var init_ChoiceButton = __esm({
11787
11903
  }
11788
11904
  });
11789
11905
  function ActionButton({
11906
+ assetUrl = DEFAULT_ASSET_URL11,
11790
11907
  label = "Attack",
11791
11908
  icon,
11792
11909
  cooldown = 0,
@@ -11826,13 +11943,23 @@ function ActionButton({
11826
11943
  }
11827
11944
  }
11828
11945
  ),
11829
- icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("flex-shrink-0", sizes.icon), children: typeof icon === "string" ? (() => {
11946
+ assetUrl ? /* @__PURE__ */ jsxRuntime.jsx(
11947
+ "img",
11948
+ {
11949
+ src: assetUrl,
11950
+ alt: "",
11951
+ width: 16,
11952
+ height: 16,
11953
+ style: { imageRendering: "pixelated", objectFit: "contain" },
11954
+ className: cn("flex-shrink-0", sizes.icon)
11955
+ }
11956
+ ) : icon ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("flex-shrink-0", sizes.icon), children: typeof icon === "string" ? (() => {
11830
11957
  const I = resolveIcon(icon);
11831
11958
  return I ? /* @__PURE__ */ jsxRuntime.jsx(I, { className: "w-4 h-4" }) : null;
11832
11959
  })() : /* @__PURE__ */ (() => {
11833
11960
  const I = icon;
11834
11961
  return /* @__PURE__ */ jsxRuntime.jsx(I, { className: "w-4 h-4" });
11835
- })() }),
11962
+ })() }) : null,
11836
11963
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "relative z-10", children: label }),
11837
11964
  hotkey && /* @__PURE__ */ jsxRuntime.jsx(
11838
11965
  "span",
@@ -11848,7 +11975,7 @@ function ActionButton({
11848
11975
  }
11849
11976
  );
11850
11977
  }
11851
- var sizeMap13, variantStyles8;
11978
+ var sizeMap13, variantStyles8, DEFAULT_ASSET_URL11;
11852
11979
  var init_ActionButton = __esm({
11853
11980
  "components/game/atoms/ActionButton.tsx"() {
11854
11981
  init_cn();
@@ -11863,6 +11990,7 @@ var init_ActionButton = __esm({
11863
11990
  secondary: "bg-secondary text-secondary-foreground hover:bg-secondary-hover border-border",
11864
11991
  danger: "bg-error text-error-foreground hover:bg-error/90 border-error"
11865
11992
  };
11993
+ DEFAULT_ASSET_URL11 = "https://almadar-kflow-assets.web.app/shared/effects/particles/slash_01.png";
11866
11994
  ActionButton.displayName = "ActionButton";
11867
11995
  }
11868
11996
  });
@@ -12375,10 +12503,10 @@ var init_Container = __esm({
12375
12503
  center = true,
12376
12504
  className,
12377
12505
  children,
12378
- as: Component = "div"
12506
+ as: Component2 = "div"
12379
12507
  }) => {
12380
12508
  const resolvedSize = maxWidth ?? size ?? "lg";
12381
- const Comp = Component;
12509
+ const Comp = Component2;
12382
12510
  return /* @__PURE__ */ jsxRuntime.jsx(
12383
12511
  Comp,
12384
12512
  {
@@ -15212,15 +15340,15 @@ function BattleBoard({
15212
15340
  const board = boardEntity(entity) ?? {};
15213
15341
  const tiles = propTiles ?? (Array.isArray(board.tiles) ? board.tiles : []);
15214
15342
  const features = propFeatures ?? (Array.isArray(board.features) ? board.features : []);
15215
- const boardWidth = num(board.boardWidth, 8);
15216
- const boardHeight = num(board.boardHeight, 6);
15343
+ const boardWidth = num(board.gridWidth ?? board.boardWidth, 8);
15344
+ const boardHeight = num(board.gridHeight ?? board.boardHeight, 6);
15217
15345
  const assetManifest = propAssetManifest ?? board.assetManifest;
15218
15346
  const backgroundImage = board.backgroundImage;
15219
15347
  const units = rows(board.units);
15220
15348
  const selectedUnitId = board.selectedUnitId ?? null;
15221
15349
  const currentPhase = str(board.phase) || "observation";
15222
15350
  const currentTurn = num(board.turn, 1);
15223
- const gameResult = board.gameResult ?? null;
15351
+ const gameResult = board.result ?? null;
15224
15352
  const eventBus = useEventBus();
15225
15353
  const { t } = hooks.useTranslate();
15226
15354
  const [hoveredTile, setHoveredTile] = React91.useState(null);
@@ -15241,7 +15369,7 @@ function BattleBoard({
15241
15369
  const validMoves = React91.useMemo(() => {
15242
15370
  if (!selectedUnit || currentPhase !== "movement") return [];
15243
15371
  const moves = [];
15244
- const range = num(selectedUnit.movement);
15372
+ const range = num(board.movementRange, 2);
15245
15373
  const origin = unitPosition(selectedUnit);
15246
15374
  for (let dy = -range; dy <= range; dy++) {
15247
15375
  for (let dx = -range; dx <= range; dx++) {
@@ -22697,15 +22825,24 @@ function CastleBoard({
22697
22825
  featureClickEvent,
22698
22826
  unitClickEvent,
22699
22827
  tileClickEvent,
22828
+ playAgainEvent,
22700
22829
  className
22701
22830
  }) {
22702
22831
  const eventBus = useEventBus();
22832
+ const { t } = hooks.useTranslate();
22703
22833
  const resolved = boardEntity(entity);
22704
22834
  const tiles = propTiles ?? (Array.isArray(resolved?.tiles) ? resolved.tiles : []);
22705
22835
  const features = propFeatures ?? (Array.isArray(resolved?.features) ? resolved.features : []);
22706
22836
  const units = propUnits ?? (Array.isArray(resolved?.units) ? resolved.units : []);
22707
22837
  const assetManifest = propAssetManifest ?? resolved?.assetManifest;
22708
22838
  const backgroundImage = resolved?.backgroundImage;
22839
+ const gold = num(resolved?.gold);
22840
+ const health = num(resolved?.health);
22841
+ const maxHealth = num(resolved?.maxHealth);
22842
+ const wave = num(resolved?.wave);
22843
+ const tickCount = num(resolved?.tickCount);
22844
+ const buildings = rows(resolved?.buildings);
22845
+ const result = str(resolved?.result) || "none";
22709
22846
  const [hoveredTile, setHoveredTile] = React91.useState(null);
22710
22847
  const [selectedFeature, setSelectedFeature] = React91.useState(null);
22711
22848
  const hoveredFeature = React91.useMemo(() => {
@@ -22718,7 +22855,7 @@ function CastleBoard({
22718
22855
  (u) => u.position?.x === hoveredTile.x && u.position?.y === hoveredTile.y
22719
22856
  ) ?? null;
22720
22857
  }, [hoveredTile, units]);
22721
- const maxY = Math.max(...tiles.map((t) => t.y), 0);
22858
+ const maxY = Math.max(...tiles.map((t2) => t2.y), 0);
22722
22859
  const baseOffsetX = (maxY + 1) * (TILE_WIDTH * scale / 2);
22723
22860
  const tileToScreen = React91.useCallback(
22724
22861
  (tx, ty) => isoToScreen(tx, ty, scale, baseOffsetX),
@@ -22753,6 +22890,11 @@ function CastleBoard({
22753
22890
  }
22754
22891
  }, [units, onUnitClick, unitClickEvent, eventBus]);
22755
22892
  const clearSelection = React91.useCallback(() => setSelectedFeature(null), []);
22893
+ const handlePlayAgain = React91.useCallback(() => {
22894
+ if (playAgainEvent) {
22895
+ eventBus.emit(`UI:${playAgainEvent}`, {});
22896
+ }
22897
+ }, [playAgainEvent, eventBus]);
22756
22898
  const ctx = React91.useMemo(
22757
22899
  () => ({
22758
22900
  hoveredTile,
@@ -22761,11 +22903,18 @@ function CastleBoard({
22761
22903
  selectedFeature,
22762
22904
  clearSelection,
22763
22905
  tileToScreen,
22764
- scale
22906
+ scale,
22907
+ gold,
22908
+ health,
22909
+ maxHealth,
22910
+ wave,
22911
+ tickCount,
22912
+ buildings,
22913
+ result
22765
22914
  }),
22766
- [hoveredTile, hoveredFeature, hoveredUnit, selectedFeature, clearSelection, tileToScreen, scale]
22915
+ [hoveredTile, hoveredFeature, hoveredUnit, selectedFeature, clearSelection, tileToScreen, scale, gold, health, maxHealth, wave, tickCount, buildings, result]
22767
22916
  );
22768
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("castle-board min-h-screen flex flex-col bg-background", className), children: [
22917
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("castle-board relative min-h-screen flex flex-col bg-background", className), children: [
22769
22918
  header && header(ctx),
22770
22919
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 overflow-hidden", children: [
22771
22920
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 overflow-auto p-4 relative", children: [
@@ -22790,7 +22939,29 @@ function CastleBoard({
22790
22939
  ] }),
22791
22940
  sidePanel && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-96 shrink-0 border-l border-border bg-surface overflow-y-auto", children: sidePanel(ctx) })
22792
22941
  ] }),
22793
- footer && footer(ctx)
22942
+ footer && footer(ctx),
22943
+ 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: [
22944
+ /* @__PURE__ */ jsxRuntime.jsx(
22945
+ Typography,
22946
+ {
22947
+ variant: "h2",
22948
+ className: cn(
22949
+ "text-4xl font-black tracking-widest uppercase",
22950
+ result === "victory" ? "text-warning" : "text-error"
22951
+ ),
22952
+ children: result === "victory" ? t("battle.victory") : t("battle.defeat")
22953
+ }
22954
+ ),
22955
+ /* @__PURE__ */ jsxRuntime.jsx(
22956
+ Button,
22957
+ {
22958
+ variant: "primary",
22959
+ className: "px-8 py-3 font-semibold",
22960
+ onClick: handlePlayAgain,
22961
+ children: t("battle.playAgain")
22962
+ }
22963
+ )
22964
+ ] }) })
22794
22965
  ] });
22795
22966
  }
22796
22967
  var init_CastleBoard = __esm({
@@ -22798,6 +22969,10 @@ var init_CastleBoard = __esm({
22798
22969
  "use client";
22799
22970
  init_cn();
22800
22971
  init_useEventBus();
22972
+ init_Box();
22973
+ init_Button();
22974
+ init_Typography();
22975
+ init_Stack();
22801
22976
  init_IsometricCanvas();
22802
22977
  init_boardEntity();
22803
22978
  init_isometric();
@@ -23674,22 +23849,16 @@ function ClassifierBoard({
23674
23849
  const { emit } = useEventBus();
23675
23850
  const { t } = hooks.useTranslate();
23676
23851
  const resolved = boardEntity(entity);
23677
- const [localAssignments, setLocalAssignments] = React91.useState({});
23678
23852
  const [headerError, setHeaderError] = React91.useState(false);
23679
- const [localSubmitted, setLocalSubmitted] = React91.useState(false);
23680
- const [localAttempts, setLocalAttempts] = React91.useState(0);
23681
- const [showHint, setShowHint] = React91.useState(false);
23682
23853
  const items = Array.isArray(resolved?.items) ? resolved.items : [];
23683
23854
  const categories = Array.isArray(resolved?.categories) ? resolved.categories : [];
23684
- const entityResult = str(resolved?.result);
23685
- const entityDrivesResult = entityResult.length > 0;
23686
- const entityHasAssignments = items.some((item) => item.assignedCategory != null);
23687
- const assignments = entityHasAssignments ? items.reduce((acc, item) => {
23688
- if (item.assignedCategory != null) acc[item.id] = item.assignedCategory;
23855
+ const result = str(resolved?.result);
23856
+ const submitted = result === "win";
23857
+ const attempts = num(resolved?.attempts);
23858
+ const assignments = items.reduce((acc, item) => {
23859
+ if (item.assignedCategory != null && item.assignedCategory !== "") acc[item.id] = item.assignedCategory;
23689
23860
  return acc;
23690
- }, {}) : localAssignments;
23691
- const attempts = entityDrivesResult ? num(resolved?.attempts) : localAttempts;
23692
- const submitted = entityDrivesResult || localSubmitted;
23861
+ }, {});
23693
23862
  const unassignedItems = items.filter((item) => !assignments[item.id]);
23694
23863
  const allAssigned = items.length > 0 && Object.keys(assignments).length === items.length;
23695
23864
  const results = submitted ? items.map((item) => ({
@@ -23697,54 +23866,25 @@ function ClassifierBoard({
23697
23866
  assigned: assignments[item.id],
23698
23867
  correct: assignments[item.id] === item.correctCategory
23699
23868
  })) : [];
23700
- const allCorrect = entityDrivesResult ? entityResult === "success" : results.length > 0 && results.every((r2) => r2.correct);
23869
+ const allCorrect = result === "win";
23701
23870
  const correctCount = results.filter((r2) => r2.correct).length;
23871
+ const showHint = attempts >= 2 && Boolean(str(resolved?.hint));
23702
23872
  const handleAssign = (itemId, categoryId) => {
23703
23873
  if (submitted) return;
23704
- if (assignEvent) {
23705
- emit(`UI:${assignEvent}`, { itemId, categoryId });
23706
- }
23707
- if (!entityHasAssignments) {
23708
- setLocalAssignments((prev) => ({ ...prev, [itemId]: categoryId }));
23709
- }
23874
+ if (assignEvent) emit(`UI:${assignEvent}`, { itemId, categoryId });
23710
23875
  };
23711
23876
  const handleUnassign = (itemId) => {
23712
23877
  if (submitted) return;
23713
- if (!entityHasAssignments) {
23714
- setLocalAssignments((prev) => {
23715
- const next = { ...prev };
23716
- delete next[itemId];
23717
- return next;
23718
- });
23719
- }
23878
+ if (assignEvent) emit(`UI:${assignEvent}`, { itemId, categoryId: "" });
23720
23879
  };
23721
- const handleSubmit = React91.useCallback(() => {
23722
- if (checkEvent) {
23723
- emit(`UI:${checkEvent}`, {});
23724
- }
23725
- if (!entityDrivesResult) {
23726
- setLocalSubmitted(true);
23727
- setLocalAttempts((a) => a + 1);
23728
- const correct = items.every((item) => assignments[item.id] === item.correctCategory);
23729
- if (correct) {
23730
- emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
23731
- }
23732
- }
23733
- }, [checkEvent, entityDrivesResult, items, assignments, attempts, completeEvent, emit]);
23734
- const handleReset = () => {
23735
- if (!entityDrivesResult) setLocalSubmitted(false);
23736
- if (attempts >= 2 && str(resolved?.hint)) {
23737
- setShowHint(true);
23880
+ const handleSubmit = () => {
23881
+ if (checkEvent) emit(`UI:${checkEvent}`, {});
23882
+ if (allAssigned && items.every((item) => assignments[item.id] === item.correctCategory)) {
23883
+ emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
23738
23884
  }
23739
23885
  };
23740
23886
  const handleFullReset = () => {
23741
- if (playAgainEvent) {
23742
- emit(`UI:${playAgainEvent}`, {});
23743
- }
23744
- setLocalAssignments({});
23745
- setLocalSubmitted(false);
23746
- setLocalAttempts(0);
23747
- setShowHint(false);
23887
+ if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
23748
23888
  };
23749
23889
  if (!resolved) return null;
23750
23890
  const theme = resolved.theme ?? void 0;
@@ -23783,17 +23923,17 @@ function ClassifierBoard({
23783
23923
  /* @__PURE__ */ jsxRuntime.jsx(Badge, { size: "sm", children: catItems.length })
23784
23924
  ] }),
23785
23925
  /* @__PURE__ */ jsxRuntime.jsx(HStack, { gap: "xs", className: "flex-wrap min-h-[2rem]", children: catItems.map((item) => {
23786
- const result = results.find((r2) => r2.item.id === item.id);
23926
+ const result2 = results.find((r2) => r2.item.id === item.id);
23787
23927
  return /* @__PURE__ */ jsxRuntime.jsxs(
23788
23928
  Badge,
23789
23929
  {
23790
23930
  size: "sm",
23791
- className: `cursor-pointer ${result ? result.correct ? "border-success bg-success/10" : "border-error bg-error/10" : ""}`,
23931
+ className: `cursor-pointer ${result2 ? result2.correct ? "border-success bg-success/10" : "border-error bg-error/10" : ""}`,
23792
23932
  onClick: () => handleUnassign(item.id),
23793
23933
  children: [
23794
23934
  item.iconUrl && /* @__PURE__ */ jsxRuntime.jsx("img", { src: item.iconUrl, alt: "", className: "w-3 h-3 object-contain inline-block" }),
23795
23935
  item.label,
23796
- result && /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: result.correct ? LucideIcons2.CheckCircle : LucideIcons2.XCircle, size: "xs", className: result.correct ? "text-success" : "text-error" })
23936
+ result2 && /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: result2.correct ? LucideIcons2.CheckCircle : LucideIcons2.XCircle, size: "xs", className: result2.correct ? "text-success" : "text-error" })
23797
23937
  ]
23798
23938
  },
23799
23939
  item.id
@@ -23822,10 +23962,10 @@ function ClassifierBoard({
23822
23962
  ] }) }),
23823
23963
  showHint && hint && /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: hint }) }),
23824
23964
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", justify: "center", children: [
23825
- !submitted ? /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: !allAssigned, children: [
23965
+ !submitted && /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: !allAssigned, children: [
23826
23966
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: LucideIcons2.Send, size: "sm" }),
23827
23967
  t("classifier.check")
23828
- ] }) : !allCorrect ? /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "primary", onClick: handleReset, children: t("classifier.tryAgain") }) : null,
23968
+ ] }),
23829
23969
  /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "secondary", onClick: handleFullReset, children: [
23830
23970
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: LucideIcons2.RotateCcw, size: "sm" }),
23831
23971
  t("classifier.reset")
@@ -27543,7 +27683,7 @@ var init_Flex = __esm({
27543
27683
  basis,
27544
27684
  className,
27545
27685
  children,
27546
- as: Component = "div"
27686
+ as: Component2 = "div"
27547
27687
  }) => {
27548
27688
  const flexStyle = {};
27549
27689
  if (grow !== void 0 || shrink !== void 0 || basis !== void 0) {
@@ -27555,7 +27695,7 @@ var init_Flex = __esm({
27555
27695
  flexStyle.flexBasis = typeof basis === "number" ? `${basis}px` : basis;
27556
27696
  }
27557
27697
  }
27558
- const Comp = Component;
27698
+ const Comp = Component2;
27559
27699
  return /* @__PURE__ */ jsxRuntime.jsx(
27560
27700
  Comp,
27561
27701
  {
@@ -27675,11 +27815,11 @@ var init_Grid = __esm({
27675
27815
  className,
27676
27816
  style,
27677
27817
  children,
27678
- as: Component = "div"
27818
+ as: Component2 = "div"
27679
27819
  }) => {
27680
27820
  const mergedStyle = rows2 ? { gridTemplateRows: `repeat(${rows2}, minmax(0, 1fr))`, ...style } : style;
27681
27821
  return React91__namespace.default.createElement(
27682
- Component,
27822
+ Component2,
27683
27823
  {
27684
27824
  className: cn(
27685
27825
  "grid",
@@ -29505,6 +29645,7 @@ var init_ProgressDots = __esm({
29505
29645
  }
29506
29646
  });
29507
29647
  function StatBadge({
29648
+ assetUrl = DEFAULT_ASSET_URL12,
29508
29649
  label,
29509
29650
  value = 0,
29510
29651
  max,
@@ -29528,7 +29669,17 @@ function StatBadge({
29528
29669
  className
29529
29670
  ),
29530
29671
  children: [
29531
- 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" }) }),
29672
+ assetUrl ? /* @__PURE__ */ jsxRuntime.jsx(
29673
+ "img",
29674
+ {
29675
+ src: assetUrl,
29676
+ alt: "",
29677
+ width: 16,
29678
+ height: 16,
29679
+ style: { imageRendering: "pixelated", objectFit: "contain" },
29680
+ className: "flex-shrink-0"
29681
+ }
29682
+ ) : 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,
29532
29683
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground font-medium", children: label }),
29533
29684
  format === "hearts" && max && /* @__PURE__ */ jsxRuntime.jsx(
29534
29685
  HealthBar,
@@ -29561,7 +29712,7 @@ function StatBadge({
29561
29712
  }
29562
29713
  );
29563
29714
  }
29564
- var sizeMap17, variantMap2;
29715
+ var sizeMap17, variantMap2, DEFAULT_ASSET_URL12;
29565
29716
  var init_StatBadge = __esm({
29566
29717
  "components/game/molecules/StatBadge.tsx"() {
29567
29718
  init_cn();
@@ -29580,6 +29731,7 @@ var init_StatBadge = __esm({
29580
29731
  warning: "bg-warning/15 border-warning/40 text-foreground",
29581
29732
  danger: "bg-error/15 border-error/40 text-foreground"
29582
29733
  };
29734
+ DEFAULT_ASSET_URL12 = "https://almadar-kflow-assets.web.app/shared/effects/particles/light_01.png";
29583
29735
  StatBadge.displayName = "StatBadge";
29584
29736
  }
29585
29737
  });
@@ -30236,6 +30388,7 @@ var init_TurnPanel = __esm({
30236
30388
  }
30237
30389
  });
30238
30390
  function EnemyPlate({
30391
+ assetUrl = DEFAULT_ASSET_URL13,
30239
30392
  name = "Shadow Guard",
30240
30393
  health = 80,
30241
30394
  maxHealth = 100,
@@ -30253,15 +30406,28 @@ function EnemyPlate({
30253
30406
  ),
30254
30407
  children: [
30255
30408
  /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "flex items-center justify-between gap-2", children: [
30256
- /* @__PURE__ */ jsxRuntime.jsx(
30257
- Typography,
30258
- {
30259
- variant: "caption",
30260
- weight: "bold",
30261
- className: "text-[var(--color-foreground)] truncate",
30262
- children: name
30263
- }
30264
- ),
30409
+ /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "flex items-center gap-1.5 min-w-0", children: [
30410
+ assetUrl && /* @__PURE__ */ jsxRuntime.jsx(
30411
+ "img",
30412
+ {
30413
+ src: assetUrl,
30414
+ alt: name,
30415
+ width: 24,
30416
+ height: 24,
30417
+ style: { objectFit: "cover", borderRadius: "50%" },
30418
+ className: "flex-shrink-0"
30419
+ }
30420
+ ),
30421
+ /* @__PURE__ */ jsxRuntime.jsx(
30422
+ Typography,
30423
+ {
30424
+ variant: "caption",
30425
+ weight: "bold",
30426
+ className: "text-[var(--color-foreground)] truncate",
30427
+ children: name
30428
+ }
30429
+ )
30430
+ ] }),
30265
30431
  level != null && /* @__PURE__ */ jsxRuntime.jsxs(Badge, { variant: "neutral", size: "sm", children: [
30266
30432
  "Lv.",
30267
30433
  level
@@ -30306,7 +30472,7 @@ function EnemyPlate({
30306
30472
  }
30307
30473
  );
30308
30474
  }
30309
- var effectVariantMap2, DEFAULT_ENEMY_EFFECTS;
30475
+ var effectVariantMap2, DEFAULT_ENEMY_EFFECTS, DEFAULT_ASSET_URL13;
30310
30476
  var init_EnemyPlate = __esm({
30311
30477
  "components/game/molecules/EnemyPlate.tsx"() {
30312
30478
  "use client";
@@ -30323,6 +30489,7 @@ var init_EnemyPlate = __esm({
30323
30489
  DEFAULT_ENEMY_EFFECTS = [
30324
30490
  { icon: "flame", label: "Burn", variant: "debuff" }
30325
30491
  ];
30492
+ DEFAULT_ASSET_URL13 = "https://almadar-kflow-assets.web.app/shared/portraits/shadow-legion.png";
30326
30493
  EnemyPlate.displayName = "EnemyPlate";
30327
30494
  }
30328
30495
  });
@@ -31854,13 +32021,13 @@ var init_MapView = __esm({
31854
32021
  shadowSize: [41, 41]
31855
32022
  });
31856
32023
  L.Marker.prototype.options.icon = defaultIcon;
31857
- const { useEffect: useEffect78, useRef: useRef70, useCallback: useCallback118, useState: useState115 } = React91__namespace.default;
32024
+ const { useEffect: useEffect84, useRef: useRef76, useCallback: useCallback124, useState: useState120 } = React91__namespace.default;
31858
32025
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
31859
- const { useEventBus: useEventBus3 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
32026
+ const { useEventBus: useEventBus4 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
31860
32027
  function MapUpdater({ centerLat, centerLng, zoom }) {
31861
32028
  const map = useMap();
31862
- const prevRef = useRef70({ centerLat, centerLng, zoom });
31863
- useEffect78(() => {
32029
+ const prevRef = useRef76({ centerLat, centerLng, zoom });
32030
+ useEffect84(() => {
31864
32031
  const prev = prevRef.current;
31865
32032
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
31866
32033
  map.setView([centerLat, centerLng], zoom);
@@ -31871,7 +32038,7 @@ var init_MapView = __esm({
31871
32038
  }
31872
32039
  function MapClickHandler({ onMapClick }) {
31873
32040
  const map = useMap();
31874
- useEffect78(() => {
32041
+ useEffect84(() => {
31875
32042
  if (!onMapClick) return;
31876
32043
  const handler = (e) => {
31877
32044
  onMapClick(e.latlng.lat, e.latlng.lng);
@@ -31898,9 +32065,9 @@ var init_MapView = __esm({
31898
32065
  className,
31899
32066
  showAttribution = true
31900
32067
  }) {
31901
- const eventBus = useEventBus3();
31902
- const [clickedPosition, setClickedPosition] = useState115(null);
31903
- const handleMapClick = useCallback118((lat, lng) => {
32068
+ const eventBus = useEventBus4();
32069
+ const [clickedPosition, setClickedPosition] = useState120(null);
32070
+ const handleMapClick = useCallback124((lat, lng) => {
31904
32071
  if (showClickedPin) {
31905
32072
  setClickedPosition({ lat, lng });
31906
32073
  }
@@ -31909,7 +32076,7 @@ var init_MapView = __esm({
31909
32076
  eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
31910
32077
  }
31911
32078
  }, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
31912
- const handleMarkerClick = useCallback118((marker) => {
32079
+ const handleMarkerClick = useCallback124((marker) => {
31913
32080
  onMarkerClick?.(marker);
31914
32081
  if (markerClickEvent) {
31915
32082
  eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
@@ -38627,10 +38794,10 @@ var init_Section = __esm({
38627
38794
  children,
38628
38795
  headerClassName,
38629
38796
  contentClassName,
38630
- as: Component = "section"
38797
+ as: Component2 = "section"
38631
38798
  }) => {
38632
38799
  const hasHeader = title || description || action;
38633
- const Comp = Component;
38800
+ const Comp = Component2;
38634
38801
  return /* @__PURE__ */ jsxRuntime.jsxs(
38635
38802
  Comp,
38636
38803
  {
@@ -40731,44 +40898,27 @@ function DebuggerBoard({
40731
40898
  const { t } = hooks.useTranslate();
40732
40899
  const resolved = boardEntity(entity);
40733
40900
  const [headerError, setHeaderError] = React91.useState(false);
40734
- const [showHint, setShowHint] = React91.useState(false);
40735
40901
  const lines = Array.isArray(resolved?.lines) ? resolved.lines : [];
40736
- const result = resolved?.result ?? null;
40902
+ const result = str(resolved?.result) || "none";
40737
40903
  const attempts = num(resolved?.attempts);
40738
- const submitted = result != null;
40904
+ const submitted = result === "win";
40905
+ const showHint = !submitted && attempts >= 2 && Boolean(str(resolved?.hint));
40739
40906
  const bugLines = lines.filter((l) => l.isBug);
40740
40907
  const flaggedLines = lines.filter((l) => l.isFlagged);
40741
40908
  const correctFlags = lines.filter((l) => l.isBug && l.isFlagged);
40742
40909
  const falseFlags = lines.filter((l) => !l.isBug && l.isFlagged);
40743
- const allCorrect = result === "win";
40910
+ const allCorrect = submitted;
40744
40911
  const toggleLine = (lineId) => {
40745
40912
  if (submitted) return;
40746
- if (toggleFlagEvent) {
40747
- emit(`UI:${toggleFlagEvent}`, { lineId });
40748
- }
40913
+ if (toggleFlagEvent) emit(`UI:${toggleFlagEvent}`, { lineId });
40749
40914
  };
40750
40915
  const handleSubmit = React91.useCallback(() => {
40751
- if (checkEvent) {
40752
- emit(`UI:${checkEvent}`, {});
40753
- }
40916
+ if (checkEvent) emit(`UI:${checkEvent}`, {});
40754
40917
  const correct = correctFlags.length === bugLines.length && falseFlags.length === 0;
40755
- if (correct) {
40756
- emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
40757
- }
40918
+ if (correct) emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
40758
40919
  }, [checkEvent, correctFlags.length, bugLines.length, falseFlags.length, attempts, completeEvent, emit]);
40759
40920
  const handleReset = () => {
40760
- if (playAgainEvent) {
40761
- emit(`UI:${playAgainEvent}`, {});
40762
- }
40763
- if (attempts >= 2 && str(resolved?.hint)) {
40764
- setShowHint(true);
40765
- }
40766
- };
40767
- const handleFullReset = () => {
40768
- if (playAgainEvent) {
40769
- emit(`UI:${playAgainEvent}`, {});
40770
- }
40771
- setShowHint(false);
40921
+ if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
40772
40922
  };
40773
40923
  if (!resolved) return null;
40774
40924
  const theme = resolved.theme ?? void 0;
@@ -40792,7 +40942,7 @@ function DebuggerBoard({
40792
40942
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", weight: "bold", children: str(resolved.title) })
40793
40943
  ] }),
40794
40944
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: str(resolved.description) }),
40795
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "text-muted-foreground", children: t("debugger.findBugs", { count: String(num(resolved.bugCount)) }) })
40945
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "text-muted-foreground", children: t("debugger.findBugs", { count: String(lines.filter((l) => l.isBug).length) }) })
40796
40946
  ] }) }),
40797
40947
  /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-0 overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx(VStack, { gap: "none", children: lines.map((line, i) => {
40798
40948
  const isFlagged = !!line.isFlagged;
@@ -40842,11 +40992,11 @@ function DebuggerBoard({
40842
40992
  ] }) }),
40843
40993
  showHint && hint && /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: hint }) }),
40844
40994
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", justify: "center", children: [
40845
- !submitted ? /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: flaggedLines.length === 0, children: [
40995
+ !submitted && /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: flaggedLines.length === 0, children: [
40846
40996
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: LucideIcons2.Send, size: "sm" }),
40847
40997
  t("debugger.submit")
40848
- ] }) : !allCorrect ? /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "primary", onClick: handleReset, children: t("debugger.tryAgain") }) : null,
40849
- /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "secondary", onClick: handleFullReset, children: [
40998
+ ] }),
40999
+ /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "secondary", onClick: handleReset, children: [
40850
41000
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: LucideIcons2.RotateCcw, size: "sm" }),
40851
41001
  t("debugger.reset")
40852
41002
  ] })
@@ -41847,39 +41997,40 @@ function EventHandlerBoard({
41847
41997
  stepDurationMs = 800,
41848
41998
  playEvent,
41849
41999
  completeEvent,
42000
+ editRuleEvent,
42001
+ playAgainEvent,
41850
42002
  className
41851
42003
  }) {
41852
42004
  const { emit } = useEventBus();
41853
42005
  const { t } = hooks.useTranslate();
41854
42006
  const resolved = boardEntity(entity);
41855
- const entityObjects = rows(resolved?.objects);
41856
- const [objects, setObjects] = React91.useState(() => [...entityObjects]);
42007
+ const objects = rows(resolved?.objects);
42008
+ const entityResult = str(resolved?.result) || "none";
42009
+ const isSuccess = entityResult === "win";
42010
+ const attempts = typeof resolved?.attempts === "number" ? resolved.attempts : 0;
41857
42011
  const [selectedObjectId, setSelectedObjectId] = React91.useState(
41858
- entityObjects[0] ? objId(entityObjects[0]) : null
42012
+ objects[0] ? objId(objects[0]) : null
41859
42013
  );
41860
42014
  const [headerError, setHeaderError] = React91.useState(false);
41861
- const [playState, setPlayState] = React91.useState("editing");
42015
+ const [isPlaying, setIsPlaying] = React91.useState(false);
41862
42016
  const [eventLog, setEventLog] = React91.useState([]);
41863
- const [attempts, setAttempts] = React91.useState(0);
41864
42017
  const timerRef = React91.useRef(null);
41865
42018
  const logIdCounter = React91.useRef(0);
41866
42019
  React91.useEffect(() => () => {
41867
42020
  if (timerRef.current) clearTimeout(timerRef.current);
41868
42021
  }, []);
41869
- const selectedObject = objects.find((o) => objId(o) === selectedObjectId) || null;
42022
+ const selectedObject = objects.find((o) => objId(o) === selectedObjectId) ?? null;
41870
42023
  const handleRulesChange = React91.useCallback((objectId, rules) => {
41871
- setObjects((prev) => prev.map(
41872
- (o) => objId(o) === objectId ? { ...o, rules } : o
41873
- ));
41874
- }, []);
42024
+ if (editRuleEvent) emit(`UI:${editRuleEvent}`, { objectId, rules });
42025
+ }, [editRuleEvent, emit]);
41875
42026
  const addLogEntry = React91.useCallback((icon, message, status = "done") => {
41876
42027
  const id = `log-${logIdCounter.current++}`;
41877
42028
  setEventLog((prev) => [...prev, { id, timestamp: Date.now(), icon, message, status }]);
41878
42029
  }, []);
41879
42030
  const handlePlay = React91.useCallback(() => {
41880
- if (playState !== "editing") return;
42031
+ if (isPlaying || isSuccess) return;
41881
42032
  if (playEvent) emit(`UI:${playEvent}`, {});
41882
- setPlayState("playing");
42033
+ setIsPlaying(true);
41883
42034
  setEventLog([]);
41884
42035
  const allRules = [];
41885
42036
  objects.forEach((obj) => {
@@ -41895,15 +42046,8 @@ function EventHandlerBoard({
41895
42046
  let goalReached = false;
41896
42047
  const processNext = () => {
41897
42048
  if (eventQueue.length === 0 || stepIdx > 20) {
41898
- if (goalReached) {
41899
- setPlayState("success");
41900
- if (completeEvent) {
41901
- emit(`UI:${completeEvent}`, { success: true });
41902
- }
41903
- } else {
41904
- setAttempts((prev) => prev + 1);
41905
- setPlayState("fail");
41906
- }
42049
+ setIsPlaying(false);
42050
+ if (goalReached && completeEvent) emit(`UI:${completeEvent}`, { success: true });
41907
42051
  return;
41908
42052
  }
41909
42053
  const currentEvent = eventQueue.shift();
@@ -41934,21 +42078,19 @@ function EventHandlerBoard({
41934
42078
  addLogEntry("\u{1F3AC}", t("eventHandler.simulationStarted", { events: triggers.join(", ") }), "active");
41935
42079
  }
41936
42080
  timerRef.current = setTimeout(processNext, stepDurationMs);
41937
- }, [playState, objects, resolved, stepDurationMs, playEvent, completeEvent, emit, addLogEntry, t]);
42081
+ }, [isPlaying, isSuccess, objects, resolved, stepDurationMs, playEvent, completeEvent, emit, addLogEntry, t]);
41938
42082
  const handleTryAgain = React91.useCallback(() => {
41939
42083
  if (timerRef.current) clearTimeout(timerRef.current);
41940
- setPlayState("editing");
42084
+ setIsPlaying(false);
41941
42085
  setEventLog([]);
41942
42086
  }, []);
41943
42087
  const handleReset = React91.useCallback(() => {
41944
42088
  if (timerRef.current) clearTimeout(timerRef.current);
41945
- const resetObjects = rows(resolved?.objects);
41946
- setObjects([...resetObjects]);
41947
- setPlayState("editing");
42089
+ setIsPlaying(false);
41948
42090
  setEventLog([]);
41949
- setSelectedObjectId(resetObjects[0] ? objId(resetObjects[0]) : null);
41950
- setAttempts(0);
41951
- }, [resolved?.objects]);
42091
+ setSelectedObjectId(objects[0] ? objId(objects[0]) : null);
42092
+ if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
42093
+ }, [objects, playAgainEvent, emit]);
41952
42094
  if (!resolved) return null;
41953
42095
  const objectViewers = objects.map((obj) => {
41954
42096
  const states = objStates(obj);
@@ -42017,12 +42159,12 @@ function EventHandlerBoard({
42017
42159
  {
42018
42160
  object: selectedObject,
42019
42161
  onRulesChange: handleRulesChange,
42020
- disabled: playState !== "editing"
42162
+ disabled: isPlaying
42021
42163
  }
42022
42164
  ),
42023
42165
  eventLog.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(EventLog, { entries: eventLog }),
42024
- 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") }) }),
42025
- playState === "fail" && /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
42166
+ 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") }) }),
42167
+ !isPlaying && !isSuccess && attempts > 0 && /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
42026
42168
  /* @__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) }) }),
42027
42169
  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: [
42028
42170
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-accent font-bold shrink-0", children: "\u{1F4A1} " + t("game.hint") + ":" }),
@@ -42030,12 +42172,12 @@ function EventHandlerBoard({
42030
42172
  ] }) })
42031
42173
  ] }),
42032
42174
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", children: [
42033
- playState === "fail" ? /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "primary", onClick: handleTryAgain, children: "\u{1F504} " + t("puzzle.tryAgainButton") }) : /* @__PURE__ */ jsxRuntime.jsx(
42175
+ !isPlaying && !isSuccess && attempts > 0 ? /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "primary", onClick: handleTryAgain, children: "\u{1F504} " + t("puzzle.tryAgainButton") }) : /* @__PURE__ */ jsxRuntime.jsx(
42034
42176
  Button,
42035
42177
  {
42036
42178
  variant: "primary",
42037
42179
  onClick: handlePlay,
42038
- disabled: playState !== "editing",
42180
+ disabled: isPlaying || isSuccess,
42039
42181
  children: "\u25B6 " + t("game.play")
42040
42182
  }
42041
42183
  ),
@@ -43220,6 +43362,1568 @@ var init_GameAudioToggle = __esm({
43220
43362
  GameAudioToggle.displayName = "GameAudioToggle";
43221
43363
  }
43222
43364
  });
43365
+ function detectAssetRoot(modelUrl) {
43366
+ const idx = modelUrl.indexOf("/3d/");
43367
+ if (idx !== -1) {
43368
+ return modelUrl.substring(0, idx + 4);
43369
+ }
43370
+ return modelUrl.substring(0, modelUrl.lastIndexOf("/") + 1);
43371
+ }
43372
+ function createGLTFLoaderForUrl(url) {
43373
+ const loader = new GLTFLoader_js.GLTFLoader();
43374
+ loader.setResourcePath(detectAssetRoot(url));
43375
+ return loader;
43376
+ }
43377
+ var AssetLoader;
43378
+ var init_AssetLoader = __esm({
43379
+ "components/game/molecules/three/loaders/AssetLoader.ts"() {
43380
+ AssetLoader = class {
43381
+ constructor() {
43382
+ __publicField(this, "objLoader");
43383
+ __publicField(this, "textureLoader");
43384
+ __publicField(this, "modelCache");
43385
+ __publicField(this, "textureCache");
43386
+ __publicField(this, "loadingPromises");
43387
+ this.objLoader = new OBJLoader_js.OBJLoader();
43388
+ this.textureLoader = new THREE__namespace.TextureLoader();
43389
+ this.modelCache = /* @__PURE__ */ new Map();
43390
+ this.textureCache = /* @__PURE__ */ new Map();
43391
+ this.loadingPromises = /* @__PURE__ */ new Map();
43392
+ }
43393
+ /**
43394
+ * Load a GLB/GLTF model
43395
+ * @param url - URL to the .glb or .gltf file
43396
+ * @returns Promise with loaded model scene and animations
43397
+ */
43398
+ async loadModel(url) {
43399
+ if (this.modelCache.has(url)) {
43400
+ return this.modelCache.get(url);
43401
+ }
43402
+ if (this.loadingPromises.has(url)) {
43403
+ return this.loadingPromises.get(url);
43404
+ }
43405
+ const loader = createGLTFLoaderForUrl(url);
43406
+ const loadPromise = loader.loadAsync(url).then((gltf) => {
43407
+ const result = {
43408
+ scene: gltf.scene,
43409
+ animations: gltf.animations || []
43410
+ };
43411
+ this.modelCache.set(url, result);
43412
+ this.loadingPromises.delete(url);
43413
+ return result;
43414
+ }).catch((error) => {
43415
+ this.loadingPromises.delete(url);
43416
+ throw new Error(`Failed to load model ${url}: ${error.message}`);
43417
+ });
43418
+ this.loadingPromises.set(url, loadPromise);
43419
+ return loadPromise;
43420
+ }
43421
+ /**
43422
+ * Load an OBJ model (fallback for non-GLB assets)
43423
+ * @param url - URL to the .obj file
43424
+ * @returns Promise with loaded object group
43425
+ */
43426
+ async loadOBJ(url) {
43427
+ if (this.modelCache.has(url)) {
43428
+ return this.modelCache.get(url).scene;
43429
+ }
43430
+ if (this.loadingPromises.has(url)) {
43431
+ const result = await this.loadingPromises.get(url);
43432
+ return result.scene;
43433
+ }
43434
+ const loadPromise = this.objLoader.loadAsync(url).then((group) => {
43435
+ const result = {
43436
+ scene: group,
43437
+ animations: []
43438
+ };
43439
+ this.modelCache.set(url, result);
43440
+ this.loadingPromises.delete(url);
43441
+ return result;
43442
+ }).catch((error) => {
43443
+ this.loadingPromises.delete(url);
43444
+ throw new Error(`Failed to load OBJ ${url}: ${error.message}`);
43445
+ });
43446
+ this.loadingPromises.set(url, loadPromise);
43447
+ return (await loadPromise).scene;
43448
+ }
43449
+ /**
43450
+ * Load a texture
43451
+ * @param url - URL to the texture image
43452
+ * @returns Promise with loaded texture
43453
+ */
43454
+ async loadTexture(url) {
43455
+ if (this.textureCache.has(url)) {
43456
+ return this.textureCache.get(url);
43457
+ }
43458
+ if (this.loadingPromises.has(`texture:${url}`)) {
43459
+ return this.loadingPromises.get(`texture:${url}`);
43460
+ }
43461
+ const loadPromise = this.textureLoader.loadAsync(url).then((texture) => {
43462
+ texture.colorSpace = THREE__namespace.SRGBColorSpace;
43463
+ this.textureCache.set(url, texture);
43464
+ this.loadingPromises.delete(`texture:${url}`);
43465
+ return texture;
43466
+ }).catch((error) => {
43467
+ this.loadingPromises.delete(`texture:${url}`);
43468
+ throw new Error(`Failed to load texture ${url}: ${error.message}`);
43469
+ });
43470
+ this.loadingPromises.set(`texture:${url}`, loadPromise);
43471
+ return loadPromise;
43472
+ }
43473
+ /**
43474
+ * Preload multiple assets
43475
+ * @param urls - Array of asset URLs to preload
43476
+ * @returns Promise that resolves when all assets are loaded
43477
+ */
43478
+ async preload(urls) {
43479
+ const promises = urls.map((url) => {
43480
+ if (url.endsWith(".glb") || url.endsWith(".gltf")) {
43481
+ return this.loadModel(url).catch(() => null);
43482
+ } else if (url.endsWith(".obj")) {
43483
+ return this.loadOBJ(url).catch(() => null);
43484
+ } else if (/\.(png|jpg|jpeg|webp)$/i.test(url)) {
43485
+ return this.loadTexture(url).catch(() => null);
43486
+ }
43487
+ return Promise.resolve(null);
43488
+ });
43489
+ await Promise.all(promises);
43490
+ }
43491
+ /**
43492
+ * Check if a model is cached
43493
+ * @param url - Model URL
43494
+ */
43495
+ hasModel(url) {
43496
+ return this.modelCache.has(url);
43497
+ }
43498
+ /**
43499
+ * Check if a texture is cached
43500
+ * @param url - Texture URL
43501
+ */
43502
+ hasTexture(url) {
43503
+ return this.textureCache.has(url);
43504
+ }
43505
+ /**
43506
+ * Get cached model (throws if not cached)
43507
+ * @param url - Model URL
43508
+ */
43509
+ getModel(url) {
43510
+ const model = this.modelCache.get(url);
43511
+ if (!model) {
43512
+ throw new Error(`Model ${url} not in cache`);
43513
+ }
43514
+ return model;
43515
+ }
43516
+ /**
43517
+ * Get cached texture (throws if not cached)
43518
+ * @param url - Texture URL
43519
+ */
43520
+ getTexture(url) {
43521
+ const texture = this.textureCache.get(url);
43522
+ if (!texture) {
43523
+ throw new Error(`Texture ${url} not in cache`);
43524
+ }
43525
+ return texture;
43526
+ }
43527
+ /**
43528
+ * Clear all caches
43529
+ */
43530
+ clearCache() {
43531
+ this.textureCache.forEach((texture) => {
43532
+ texture.dispose();
43533
+ });
43534
+ this.modelCache.forEach((model) => {
43535
+ model.scene.traverse((child) => {
43536
+ if (child instanceof THREE__namespace.Mesh) {
43537
+ child.geometry.dispose();
43538
+ if (Array.isArray(child.material)) {
43539
+ child.material.forEach((m) => m.dispose());
43540
+ } else {
43541
+ child.material.dispose();
43542
+ }
43543
+ }
43544
+ });
43545
+ });
43546
+ this.modelCache.clear();
43547
+ this.textureCache.clear();
43548
+ this.loadingPromises.clear();
43549
+ }
43550
+ /**
43551
+ * Get cache statistics
43552
+ */
43553
+ getStats() {
43554
+ return {
43555
+ models: this.modelCache.size,
43556
+ textures: this.textureCache.size,
43557
+ loading: this.loadingPromises.size
43558
+ };
43559
+ }
43560
+ };
43561
+ new AssetLoader();
43562
+ }
43563
+ });
43564
+ function useAssetLoader(options = {}) {
43565
+ const { preloadUrls = [], loader: customLoader } = options;
43566
+ const loaderRef = React91.useRef(customLoader || new AssetLoader());
43567
+ const [state, setState] = React91.useState({
43568
+ isLoading: false,
43569
+ progress: 0,
43570
+ loaded: 0,
43571
+ total: 0,
43572
+ errors: []
43573
+ });
43574
+ React91.useEffect(() => {
43575
+ if (preloadUrls.length > 0) {
43576
+ preload(preloadUrls);
43577
+ }
43578
+ }, []);
43579
+ const updateProgress = React91.useCallback((loaded, total) => {
43580
+ setState((prev) => ({
43581
+ ...prev,
43582
+ loaded,
43583
+ total,
43584
+ progress: total > 0 ? Math.round(loaded / total * 100) : 0
43585
+ }));
43586
+ }, []);
43587
+ const loadModel = React91.useCallback(
43588
+ async (url) => {
43589
+ setState((prev) => ({ ...prev, isLoading: true }));
43590
+ try {
43591
+ const model = await loaderRef.current.loadModel(url);
43592
+ setState((prev) => ({
43593
+ ...prev,
43594
+ isLoading: false,
43595
+ loaded: prev.loaded + 1
43596
+ }));
43597
+ return model;
43598
+ } catch (error) {
43599
+ const errorMsg = error instanceof Error ? error.message : String(error);
43600
+ setState((prev) => ({
43601
+ ...prev,
43602
+ isLoading: false,
43603
+ errors: [...prev.errors, errorMsg]
43604
+ }));
43605
+ throw error;
43606
+ }
43607
+ },
43608
+ []
43609
+ );
43610
+ const loadOBJ = React91.useCallback(
43611
+ async (url) => {
43612
+ setState((prev) => ({ ...prev, isLoading: true }));
43613
+ try {
43614
+ const model = await loaderRef.current.loadOBJ(url);
43615
+ setState((prev) => ({
43616
+ ...prev,
43617
+ isLoading: false,
43618
+ loaded: prev.loaded + 1
43619
+ }));
43620
+ return model;
43621
+ } catch (error) {
43622
+ const errorMsg = error instanceof Error ? error.message : String(error);
43623
+ setState((prev) => ({
43624
+ ...prev,
43625
+ isLoading: false,
43626
+ errors: [...prev.errors, errorMsg]
43627
+ }));
43628
+ throw error;
43629
+ }
43630
+ },
43631
+ []
43632
+ );
43633
+ const loadTexture = React91.useCallback(
43634
+ async (url) => {
43635
+ setState((prev) => ({ ...prev, isLoading: true }));
43636
+ try {
43637
+ const texture = await loaderRef.current.loadTexture(url);
43638
+ setState((prev) => ({
43639
+ ...prev,
43640
+ isLoading: false,
43641
+ loaded: prev.loaded + 1
43642
+ }));
43643
+ return texture;
43644
+ } catch (error) {
43645
+ const errorMsg = error instanceof Error ? error.message : String(error);
43646
+ setState((prev) => ({
43647
+ ...prev,
43648
+ isLoading: false,
43649
+ errors: [...prev.errors, errorMsg]
43650
+ }));
43651
+ throw error;
43652
+ }
43653
+ },
43654
+ []
43655
+ );
43656
+ const preload = React91.useCallback(
43657
+ async (urls) => {
43658
+ setState((prev) => ({
43659
+ ...prev,
43660
+ isLoading: true,
43661
+ total: urls.length,
43662
+ loaded: 0,
43663
+ errors: []
43664
+ }));
43665
+ let completed = 0;
43666
+ const errors = [];
43667
+ await Promise.all(
43668
+ urls.map(async (url) => {
43669
+ try {
43670
+ if (url.endsWith(".glb") || url.endsWith(".gltf")) {
43671
+ await loaderRef.current.loadModel(url);
43672
+ } else if (url.endsWith(".obj")) {
43673
+ await loaderRef.current.loadOBJ(url);
43674
+ } else if (/\.(png|jpg|jpeg|webp)$/i.test(url)) {
43675
+ await loaderRef.current.loadTexture(url);
43676
+ }
43677
+ completed++;
43678
+ updateProgress(completed, urls.length);
43679
+ } catch (error) {
43680
+ const errorMsg = error instanceof Error ? error.message : String(error);
43681
+ errors.push(`${url}: ${errorMsg}`);
43682
+ completed++;
43683
+ updateProgress(completed, urls.length);
43684
+ }
43685
+ })
43686
+ );
43687
+ setState((prev) => ({
43688
+ ...prev,
43689
+ isLoading: false,
43690
+ errors
43691
+ }));
43692
+ },
43693
+ [updateProgress]
43694
+ );
43695
+ const hasModel = React91.useCallback((url) => {
43696
+ return loaderRef.current.hasModel(url);
43697
+ }, []);
43698
+ const hasTexture = React91.useCallback((url) => {
43699
+ return loaderRef.current.hasTexture(url);
43700
+ }, []);
43701
+ const getModel = React91.useCallback((url) => {
43702
+ try {
43703
+ return loaderRef.current.getModel(url);
43704
+ } catch {
43705
+ return void 0;
43706
+ }
43707
+ }, []);
43708
+ const getTexture = React91.useCallback((url) => {
43709
+ try {
43710
+ return loaderRef.current.getTexture(url);
43711
+ } catch {
43712
+ return void 0;
43713
+ }
43714
+ }, []);
43715
+ const clearCache = React91.useCallback(() => {
43716
+ loaderRef.current.clearCache();
43717
+ setState({
43718
+ isLoading: false,
43719
+ progress: 0,
43720
+ loaded: 0,
43721
+ total: 0,
43722
+ errors: []
43723
+ });
43724
+ }, []);
43725
+ return {
43726
+ ...state,
43727
+ loadModel,
43728
+ loadOBJ,
43729
+ loadTexture,
43730
+ preload,
43731
+ hasModel,
43732
+ hasTexture,
43733
+ getModel,
43734
+ getTexture,
43735
+ clearCache
43736
+ };
43737
+ }
43738
+ var init_useAssetLoader = __esm({
43739
+ "components/game/molecules/three/hooks/useAssetLoader.ts"() {
43740
+ "use client";
43741
+ init_AssetLoader();
43742
+ }
43743
+ });
43744
+ function useGameCanvas3DEvents(options) {
43745
+ const {
43746
+ tileClickEvent,
43747
+ unitClickEvent,
43748
+ featureClickEvent,
43749
+ canvasClickEvent,
43750
+ tileHoverEvent,
43751
+ tileLeaveEvent,
43752
+ unitAnimationEvent,
43753
+ cameraChangeEvent,
43754
+ onTileClick,
43755
+ onUnitClick,
43756
+ onFeatureClick,
43757
+ onCanvasClick,
43758
+ onTileHover,
43759
+ onUnitAnimation
43760
+ } = options;
43761
+ const emit = useEmitEvent();
43762
+ const optionsRef = React91.useRef(options);
43763
+ optionsRef.current = options;
43764
+ const handleTileClick = React91.useCallback(
43765
+ (tile, event) => {
43766
+ if (tileClickEvent) {
43767
+ emit(tileClickEvent, {
43768
+ tileId: tile.id,
43769
+ x: tile.x,
43770
+ z: tile.z ?? tile.y ?? 0,
43771
+ type: tile.type,
43772
+ terrain: tile.terrain,
43773
+ elevation: tile.elevation
43774
+ });
43775
+ }
43776
+ optionsRef.current.onTileClick?.(tile, event);
43777
+ },
43778
+ [tileClickEvent, emit]
43779
+ );
43780
+ const handleUnitClick = React91.useCallback(
43781
+ (unit, event) => {
43782
+ if (unitClickEvent) {
43783
+ emit(unitClickEvent, {
43784
+ unitId: unit.id,
43785
+ x: unit.x,
43786
+ z: unit.z ?? unit.y ?? 0,
43787
+ unitType: unit.unitType,
43788
+ name: unit.name,
43789
+ team: unit.team,
43790
+ faction: unit.faction,
43791
+ health: unit.health,
43792
+ maxHealth: unit.maxHealth
43793
+ });
43794
+ }
43795
+ optionsRef.current.onUnitClick?.(unit, event);
43796
+ },
43797
+ [unitClickEvent, emit]
43798
+ );
43799
+ const handleFeatureClick = React91.useCallback(
43800
+ (feature, event) => {
43801
+ if (featureClickEvent) {
43802
+ emit(featureClickEvent, {
43803
+ featureId: feature.id,
43804
+ x: feature.x,
43805
+ z: feature.z ?? feature.y ?? 0,
43806
+ type: feature.type,
43807
+ elevation: feature.elevation
43808
+ });
43809
+ }
43810
+ optionsRef.current.onFeatureClick?.(feature, event);
43811
+ },
43812
+ [featureClickEvent, emit]
43813
+ );
43814
+ const handleCanvasClick = React91.useCallback(
43815
+ (event) => {
43816
+ if (canvasClickEvent) {
43817
+ emit(canvasClickEvent, {
43818
+ clientX: event.clientX,
43819
+ clientY: event.clientY,
43820
+ button: event.button
43821
+ });
43822
+ }
43823
+ optionsRef.current.onCanvasClick?.(event);
43824
+ },
43825
+ [canvasClickEvent, emit]
43826
+ );
43827
+ const handleTileHover = React91.useCallback(
43828
+ (tile, event) => {
43829
+ if (tile) {
43830
+ if (tileHoverEvent) {
43831
+ emit(tileHoverEvent, {
43832
+ tileId: tile.id,
43833
+ x: tile.x,
43834
+ z: tile.z ?? tile.y ?? 0,
43835
+ type: tile.type
43836
+ });
43837
+ }
43838
+ } else {
43839
+ if (tileLeaveEvent) {
43840
+ emit(tileLeaveEvent, {});
43841
+ }
43842
+ }
43843
+ optionsRef.current.onTileHover?.(tile, event);
43844
+ },
43845
+ [tileHoverEvent, tileLeaveEvent, emit]
43846
+ );
43847
+ const handleUnitAnimation = React91.useCallback(
43848
+ (unitId, state) => {
43849
+ if (unitAnimationEvent) {
43850
+ emit(unitAnimationEvent, {
43851
+ unitId,
43852
+ state,
43853
+ timestamp: Date.now()
43854
+ });
43855
+ }
43856
+ optionsRef.current.onUnitAnimation?.(unitId, state);
43857
+ },
43858
+ [unitAnimationEvent, emit]
43859
+ );
43860
+ const handleCameraChange = React91.useCallback(
43861
+ (position) => {
43862
+ if (cameraChangeEvent) {
43863
+ emit(cameraChangeEvent, {
43864
+ position,
43865
+ timestamp: Date.now()
43866
+ });
43867
+ }
43868
+ },
43869
+ [cameraChangeEvent, emit]
43870
+ );
43871
+ return {
43872
+ handleTileClick,
43873
+ handleUnitClick,
43874
+ handleFeatureClick,
43875
+ handleCanvasClick,
43876
+ handleTileHover,
43877
+ handleUnitAnimation,
43878
+ handleCameraChange
43879
+ };
43880
+ }
43881
+ var init_useGameCanvas3DEvents = __esm({
43882
+ "components/game/molecules/three/hooks/useGameCanvas3DEvents.ts"() {
43883
+ "use client";
43884
+ init_useEventBus();
43885
+ }
43886
+ });
43887
+
43888
+ // components/game/molecules/three/components/Canvas3DLoadingState.css
43889
+ var init_Canvas3DLoadingState = __esm({
43890
+ "components/game/molecules/three/components/Canvas3DLoadingState.css"() {
43891
+ }
43892
+ });
43893
+ function Canvas3DLoadingState({
43894
+ progress = 0,
43895
+ loaded = 0,
43896
+ total = 0,
43897
+ message = "Loading 3D Scene...",
43898
+ details,
43899
+ showSpinner = true,
43900
+ className
43901
+ }) {
43902
+ const clampedProgress = Math.max(0, Math.min(100, progress));
43903
+ const hasProgress = total > 0;
43904
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `canvas-3d-loading ${className || ""}`, children: [
43905
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "canvas-3d-loading__content", children: [
43906
+ showSpinner && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "canvas-3d-loading__spinner", children: [
43907
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "spinner__ring" }),
43908
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "spinner__ring spinner__ring--secondary" })
43909
+ ] }),
43910
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "canvas-3d-loading__message", children: message }),
43911
+ details && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "canvas-3d-loading__details", children: details }),
43912
+ hasProgress && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "canvas-3d-loading__progress", children: [
43913
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "progress__bar", children: /* @__PURE__ */ jsxRuntime.jsx(
43914
+ "div",
43915
+ {
43916
+ className: "progress__fill",
43917
+ style: { width: `${clampedProgress}%` }
43918
+ }
43919
+ ) }),
43920
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "progress__text", children: [
43921
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "progress__percentage", children: [
43922
+ clampedProgress,
43923
+ "%"
43924
+ ] }),
43925
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "progress__count", children: [
43926
+ "(",
43927
+ loaded,
43928
+ "/",
43929
+ total,
43930
+ ")"
43931
+ ] })
43932
+ ] })
43933
+ ] })
43934
+ ] }),
43935
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "canvas-3d-loading__background", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg__grid" }) })
43936
+ ] });
43937
+ }
43938
+ var init_Canvas3DLoadingState2 = __esm({
43939
+ "components/game/molecules/three/components/Canvas3DLoadingState.tsx"() {
43940
+ init_Canvas3DLoadingState();
43941
+ }
43942
+ });
43943
+
43944
+ // components/game/molecules/three/components/Canvas3DErrorBoundary.css
43945
+ var init_Canvas3DErrorBoundary = __esm({
43946
+ "components/game/molecules/three/components/Canvas3DErrorBoundary.css"() {
43947
+ }
43948
+ });
43949
+ var log9, Canvas3DErrorBoundary;
43950
+ var init_Canvas3DErrorBoundary2 = __esm({
43951
+ "components/game/molecules/three/components/Canvas3DErrorBoundary.tsx"() {
43952
+ init_Canvas3DErrorBoundary();
43953
+ log9 = logger.createLogger("almadar:ui:game:canvas3d:error-boundary");
43954
+ Canvas3DErrorBoundary = class extends React91.Component {
43955
+ constructor(props) {
43956
+ super(props);
43957
+ __publicField(this, "handleReset", () => {
43958
+ this.setState({
43959
+ hasError: false,
43960
+ error: null,
43961
+ errorInfo: null
43962
+ });
43963
+ this.props.onReset?.();
43964
+ });
43965
+ this.state = {
43966
+ hasError: false,
43967
+ error: null,
43968
+ errorInfo: null
43969
+ };
43970
+ }
43971
+ static getDerivedStateFromError(error) {
43972
+ return {
43973
+ hasError: true,
43974
+ error,
43975
+ errorInfo: null
43976
+ };
43977
+ }
43978
+ componentDidCatch(error, errorInfo) {
43979
+ this.setState({ errorInfo });
43980
+ this.props.onError?.(error, errorInfo);
43981
+ log9.error("Error caught", { error });
43982
+ log9.error("Component stack", { componentStack: errorInfo.componentStack ?? "<none>" });
43983
+ }
43984
+ render() {
43985
+ if (this.state.hasError) {
43986
+ if (this.props.fallback) {
43987
+ return this.props.fallback;
43988
+ }
43989
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "canvas-3d-error", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "canvas-3d-error__content", children: [
43990
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "canvas-3d-error__icon", children: "\u26A0\uFE0F" }),
43991
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "canvas-3d-error__title", children: "3D Scene Error" }),
43992
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "canvas-3d-error__message", children: "Something went wrong while rendering the 3D scene." }),
43993
+ this.state.error && /* @__PURE__ */ jsxRuntime.jsxs("details", { className: "canvas-3d-error__details", children: [
43994
+ /* @__PURE__ */ jsxRuntime.jsx("summary", { children: "Error Details" }),
43995
+ /* @__PURE__ */ jsxRuntime.jsxs("pre", { className: "error__stack", children: [
43996
+ this.state.error.message,
43997
+ "\n",
43998
+ this.state.error.stack
43999
+ ] }),
44000
+ this.state.errorInfo && /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "error__component-stack", children: this.state.errorInfo.componentStack })
44001
+ ] }),
44002
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "canvas-3d-error__actions", children: [
44003
+ /* @__PURE__ */ jsxRuntime.jsx(
44004
+ "button",
44005
+ {
44006
+ className: "error__button error__button--primary",
44007
+ onClick: this.handleReset,
44008
+ children: "Try Again"
44009
+ }
44010
+ ),
44011
+ /* @__PURE__ */ jsxRuntime.jsx(
44012
+ "button",
44013
+ {
44014
+ className: "error__button error__button--secondary",
44015
+ onClick: () => window.location.reload(),
44016
+ children: "Reload Page"
44017
+ }
44018
+ )
44019
+ ] })
44020
+ ] }) });
44021
+ }
44022
+ return this.props.children;
44023
+ }
44024
+ };
44025
+ }
44026
+ });
44027
+ function detectAssetRoot2(modelUrl) {
44028
+ const idx = modelUrl.indexOf("/3d/");
44029
+ if (idx !== -1) {
44030
+ return modelUrl.substring(0, idx + 4);
44031
+ }
44032
+ return modelUrl.substring(0, modelUrl.lastIndexOf("/") + 1);
44033
+ }
44034
+ function useGLTFModel(url, resourceBasePath) {
44035
+ const [state, setState] = React91.useState({
44036
+ model: null,
44037
+ isLoading: false,
44038
+ error: null
44039
+ });
44040
+ React91.useEffect(() => {
44041
+ if (!url) {
44042
+ setState({ model: null, isLoading: false, error: null });
44043
+ return;
44044
+ }
44045
+ log10.debug("Loading", { url });
44046
+ setState((prev) => ({ ...prev, isLoading: true, error: null }));
44047
+ const assetRoot = resourceBasePath || detectAssetRoot2(url);
44048
+ const loader = new GLTFLoader.GLTFLoader();
44049
+ loader.setResourcePath(assetRoot);
44050
+ loader.load(
44051
+ url,
44052
+ (gltf) => {
44053
+ log10.debug("Loaded", { url });
44054
+ setState({
44055
+ model: gltf.scene,
44056
+ isLoading: false,
44057
+ error: null
44058
+ });
44059
+ },
44060
+ void 0,
44061
+ (err) => {
44062
+ log10.warn("Failed", { url, error: err instanceof Error ? err : String(err) });
44063
+ setState({
44064
+ model: null,
44065
+ isLoading: false,
44066
+ error: err instanceof Error ? err : new Error(String(err))
44067
+ });
44068
+ }
44069
+ );
44070
+ }, [url, resourceBasePath]);
44071
+ return state;
44072
+ }
44073
+ function ModelLoader({
44074
+ url,
44075
+ position = [0, 0, 0],
44076
+ scale = 1,
44077
+ rotation = [0, 0, 0],
44078
+ isSelected = false,
44079
+ isHovered = false,
44080
+ onClick,
44081
+ onHover,
44082
+ fallbackGeometry = "box",
44083
+ castShadow = true,
44084
+ receiveShadow = true,
44085
+ resourceBasePath
44086
+ }) {
44087
+ const { model: loadedModel, isLoading, error } = useGLTFModel(url, resourceBasePath);
44088
+ const model = React91.useMemo(() => {
44089
+ if (!loadedModel) return null;
44090
+ const cloned = loadedModel.clone();
44091
+ cloned.traverse((child) => {
44092
+ if (child instanceof THREE__namespace.Mesh) {
44093
+ child.castShadow = castShadow;
44094
+ child.receiveShadow = receiveShadow;
44095
+ }
44096
+ });
44097
+ return cloned;
44098
+ }, [loadedModel, castShadow, receiveShadow]);
44099
+ const scaleArray = React91.useMemo(() => {
44100
+ if (typeof scale === "number") {
44101
+ return [scale, scale, scale];
44102
+ }
44103
+ return scale;
44104
+ }, [scale]);
44105
+ const rotationRad = React91.useMemo(() => {
44106
+ return [
44107
+ rotation[0] * Math.PI / 180,
44108
+ rotation[1] * Math.PI / 180,
44109
+ rotation[2] * Math.PI / 180
44110
+ ];
44111
+ }, [rotation]);
44112
+ if (isLoading) {
44113
+ return /* @__PURE__ */ jsxRuntime.jsx("group", { position, children: /* @__PURE__ */ jsxRuntime.jsxs("mesh", { rotation: [Math.PI / 2, 0, 0], children: [
44114
+ /* @__PURE__ */ jsxRuntime.jsx("ringGeometry", { args: [0.3, 0.35, 16] }),
44115
+ /* @__PURE__ */ jsxRuntime.jsx("meshBasicMaterial", { color: "#4a90d9", transparent: true, opacity: 0.8 })
44116
+ ] }) });
44117
+ }
44118
+ if (error || !model) {
44119
+ if (fallbackGeometry === "none") {
44120
+ return /* @__PURE__ */ jsxRuntime.jsx("group", { position });
44121
+ }
44122
+ const fallbackProps = {
44123
+ onClick,
44124
+ onPointerOver: () => onHover?.(true),
44125
+ onPointerOut: () => onHover?.(false)
44126
+ };
44127
+ return /* @__PURE__ */ jsxRuntime.jsxs("group", { position, children: [
44128
+ (isSelected || isHovered) && /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, 0.02, 0], rotation: [-Math.PI / 2, 0, 0], children: [
44129
+ /* @__PURE__ */ jsxRuntime.jsx("ringGeometry", { args: [0.6, 0.7, 32] }),
44130
+ /* @__PURE__ */ jsxRuntime.jsx(
44131
+ "meshBasicMaterial",
44132
+ {
44133
+ color: isSelected ? 16755200 : 16777215,
44134
+ transparent: true,
44135
+ opacity: 0.5
44136
+ }
44137
+ )
44138
+ ] }),
44139
+ fallbackGeometry === "box" && /* @__PURE__ */ jsxRuntime.jsxs("mesh", { ...fallbackProps, position: [0, 0.5, 0], children: [
44140
+ /* @__PURE__ */ jsxRuntime.jsx("boxGeometry", { args: [0.8, 0.8, 0.8] }),
44141
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color: error ? 16729156 : 8947848 })
44142
+ ] }),
44143
+ fallbackGeometry === "sphere" && /* @__PURE__ */ jsxRuntime.jsxs("mesh", { ...fallbackProps, position: [0, 0.5, 0], children: [
44144
+ /* @__PURE__ */ jsxRuntime.jsx("sphereGeometry", { args: [0.4, 16, 16] }),
44145
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color: error ? 16729156 : 8947848 })
44146
+ ] }),
44147
+ fallbackGeometry === "cylinder" && /* @__PURE__ */ jsxRuntime.jsxs("mesh", { ...fallbackProps, position: [0, 0.5, 0], children: [
44148
+ /* @__PURE__ */ jsxRuntime.jsx("cylinderGeometry", { args: [0.3, 0.3, 0.8, 16] }),
44149
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color: error ? 16729156 : 8947848 })
44150
+ ] })
44151
+ ] });
44152
+ }
44153
+ return /* @__PURE__ */ jsxRuntime.jsxs(
44154
+ "group",
44155
+ {
44156
+ position,
44157
+ rotation: rotationRad,
44158
+ onClick,
44159
+ onPointerOver: () => onHover?.(true),
44160
+ onPointerOut: () => onHover?.(false),
44161
+ children: [
44162
+ (isSelected || isHovered) && /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, 0.02, 0], rotation: [-Math.PI / 2, 0, 0], children: [
44163
+ /* @__PURE__ */ jsxRuntime.jsx("ringGeometry", { args: [0.6, 0.7, 32] }),
44164
+ /* @__PURE__ */ jsxRuntime.jsx(
44165
+ "meshBasicMaterial",
44166
+ {
44167
+ color: isSelected ? 16755200 : 16777215,
44168
+ transparent: true,
44169
+ opacity: 0.5
44170
+ }
44171
+ )
44172
+ ] }),
44173
+ /* @__PURE__ */ jsxRuntime.jsx("primitive", { object: model, scale: scaleArray })
44174
+ ]
44175
+ }
44176
+ );
44177
+ }
44178
+ var log10;
44179
+ var init_ModelLoader = __esm({
44180
+ "components/game/molecules/three/components/ModelLoader.tsx"() {
44181
+ "use client";
44182
+ log10 = logger.createLogger("almadar:ui:game:model-loader");
44183
+ }
44184
+ });
44185
+
44186
+ // components/game/molecules/GameCanvas3D.css
44187
+ var init_GameCanvas3D = __esm({
44188
+ "components/game/molecules/GameCanvas3D.css"() {
44189
+ }
44190
+ });
44191
+ function CameraController({
44192
+ onCameraChange
44193
+ }) {
44194
+ const { camera } = fiber.useThree();
44195
+ React91.useEffect(() => {
44196
+ if (onCameraChange) {
44197
+ onCameraChange({
44198
+ x: camera.position.x,
44199
+ y: camera.position.y,
44200
+ z: camera.position.z
44201
+ });
44202
+ }
44203
+ }, [camera.position, onCameraChange]);
44204
+ return null;
44205
+ }
44206
+ var DEFAULT_GRID_CONFIG, GameCanvas3D;
44207
+ var init_GameCanvas3D2 = __esm({
44208
+ "components/game/molecules/GameCanvas3D.tsx"() {
44209
+ "use client";
44210
+ init_AssetLoader();
44211
+ init_useAssetLoader();
44212
+ init_useGameCanvas3DEvents();
44213
+ init_Canvas3DLoadingState2();
44214
+ init_Canvas3DErrorBoundary2();
44215
+ init_ModelLoader();
44216
+ init_cn();
44217
+ init_GameCanvas3D();
44218
+ DEFAULT_GRID_CONFIG = {
44219
+ cellSize: 1,
44220
+ offsetX: 0,
44221
+ offsetZ: 0
44222
+ };
44223
+ GameCanvas3D = React91.forwardRef(
44224
+ ({
44225
+ tiles = [],
44226
+ units = [],
44227
+ features = [],
44228
+ events: events2 = [],
44229
+ orientation = "standard",
44230
+ cameraMode = "isometric",
44231
+ showGrid = true,
44232
+ showCoordinates = false,
44233
+ showTileInfo = false,
44234
+ overlay = "default",
44235
+ shadows = true,
44236
+ backgroundColor = "#1a1a2e",
44237
+ onTileClick,
44238
+ onUnitClick,
44239
+ onFeatureClick,
44240
+ onCanvasClick,
44241
+ onTileHover,
44242
+ onUnitAnimation,
44243
+ assetLoader: customAssetLoader,
44244
+ tileRenderer: CustomTileRenderer,
44245
+ unitRenderer: CustomUnitRenderer,
44246
+ featureRenderer: CustomFeatureRenderer,
44247
+ className,
44248
+ isLoading: externalLoading,
44249
+ error: externalError,
44250
+ entity,
44251
+ preloadAssets = [],
44252
+ tileClickEvent,
44253
+ unitClickEvent,
44254
+ featureClickEvent,
44255
+ canvasClickEvent,
44256
+ tileHoverEvent,
44257
+ tileLeaveEvent,
44258
+ unitAnimationEvent,
44259
+ cameraChangeEvent,
44260
+ loadingMessage = "Loading 3D Scene...",
44261
+ useInstancing = true,
44262
+ validMoves = [],
44263
+ attackTargets = [],
44264
+ selectedTileIds = [],
44265
+ selectedUnitId = null,
44266
+ children
44267
+ }, ref) => {
44268
+ const containerRef = React91.useRef(null);
44269
+ const controlsRef = React91.useRef(null);
44270
+ const [hoveredTile, setHoveredTile] = React91.useState(null);
44271
+ const [internalError, setInternalError] = React91.useState(null);
44272
+ const { isLoading: assetsLoading, progress, loaded, total } = useAssetLoader({
44273
+ preloadUrls: preloadAssets,
44274
+ loader: customAssetLoader
44275
+ });
44276
+ const eventHandlers = useGameCanvas3DEvents({
44277
+ tileClickEvent,
44278
+ unitClickEvent,
44279
+ featureClickEvent,
44280
+ canvasClickEvent,
44281
+ tileHoverEvent,
44282
+ tileLeaveEvent,
44283
+ unitAnimationEvent,
44284
+ cameraChangeEvent,
44285
+ onTileClick,
44286
+ onUnitClick,
44287
+ onFeatureClick,
44288
+ onCanvasClick,
44289
+ onTileHover,
44290
+ onUnitAnimation
44291
+ });
44292
+ const gridBounds = React91.useMemo(() => {
44293
+ if (tiles.length === 0) {
44294
+ return { minX: 0, maxX: 10, minZ: 0, maxZ: 10 };
44295
+ }
44296
+ const xs = tiles.map((t) => t.x);
44297
+ const zs = tiles.map((t) => t.z || t.y || 0);
44298
+ return {
44299
+ minX: Math.min(...xs),
44300
+ maxX: Math.max(...xs),
44301
+ minZ: Math.min(...zs),
44302
+ maxZ: Math.max(...zs)
44303
+ };
44304
+ }, [tiles]);
44305
+ const cameraTarget = React91.useMemo(() => {
44306
+ return [
44307
+ (gridBounds.minX + gridBounds.maxX) / 2,
44308
+ 0,
44309
+ (gridBounds.minZ + gridBounds.maxZ) / 2
44310
+ ];
44311
+ }, [gridBounds]);
44312
+ const gridConfig = React91.useMemo(
44313
+ () => ({
44314
+ ...DEFAULT_GRID_CONFIG,
44315
+ offsetX: -(gridBounds.maxX - gridBounds.minX) / 2,
44316
+ offsetZ: -(gridBounds.maxZ - gridBounds.minZ) / 2
44317
+ }),
44318
+ [gridBounds]
44319
+ );
44320
+ const gridToWorld = React91.useCallback(
44321
+ (x, z, y = 0) => {
44322
+ const worldX = (x - gridBounds.minX) * gridConfig.cellSize;
44323
+ const worldZ = (z - gridBounds.minZ) * gridConfig.cellSize;
44324
+ return [worldX, y * gridConfig.cellSize, worldZ];
44325
+ },
44326
+ [gridBounds, gridConfig]
44327
+ );
44328
+ React91.useImperativeHandle(ref, () => ({
44329
+ getCameraPosition: () => {
44330
+ if (controlsRef.current) {
44331
+ const pos = controlsRef.current.object.position;
44332
+ return new THREE__namespace.Vector3(pos.x, pos.y, pos.z);
44333
+ }
44334
+ return null;
44335
+ },
44336
+ setCameraPosition: (x, y, z) => {
44337
+ if (controlsRef.current) {
44338
+ controlsRef.current.object.position.set(x, y, z);
44339
+ controlsRef.current.update();
44340
+ }
44341
+ },
44342
+ lookAt: (x, y, z) => {
44343
+ if (controlsRef.current) {
44344
+ controlsRef.current.target.set(x, y, z);
44345
+ controlsRef.current.update();
44346
+ }
44347
+ },
44348
+ resetCamera: () => {
44349
+ if (controlsRef.current) {
44350
+ controlsRef.current.reset();
44351
+ }
44352
+ },
44353
+ screenshot: () => {
44354
+ const canvas = containerRef.current?.querySelector("canvas");
44355
+ if (canvas) {
44356
+ return canvas.toDataURL("image/png");
44357
+ }
44358
+ return null;
44359
+ },
44360
+ export: () => ({
44361
+ tiles,
44362
+ units,
44363
+ features
44364
+ })
44365
+ }));
44366
+ const handleTileClick = React91.useCallback(
44367
+ (tile, event) => {
44368
+ eventHandlers.handleTileClick(tile, event);
44369
+ },
44370
+ [eventHandlers]
44371
+ );
44372
+ const handleUnitClick = React91.useCallback(
44373
+ (unit, event) => {
44374
+ eventHandlers.handleUnitClick(unit, event);
44375
+ },
44376
+ [eventHandlers]
44377
+ );
44378
+ const handleFeatureClick = React91.useCallback(
44379
+ (feature, event) => {
44380
+ if (event) {
44381
+ eventHandlers.handleFeatureClick(feature, event);
44382
+ }
44383
+ },
44384
+ [eventHandlers]
44385
+ );
44386
+ const handleTileHover = React91.useCallback(
44387
+ (tile, event) => {
44388
+ setHoveredTile(tile);
44389
+ if (event) {
44390
+ eventHandlers.handleTileHover(tile, event);
44391
+ }
44392
+ },
44393
+ [eventHandlers]
44394
+ );
44395
+ const cameraConfig = React91.useMemo(() => {
44396
+ const size = Math.max(
44397
+ gridBounds.maxX - gridBounds.minX,
44398
+ gridBounds.maxZ - gridBounds.minZ
44399
+ );
44400
+ const distance = size * 1.5;
44401
+ switch (cameraMode) {
44402
+ case "isometric":
44403
+ return {
44404
+ position: [distance, distance * 0.8, distance],
44405
+ fov: 45
44406
+ };
44407
+ case "top-down":
44408
+ return {
44409
+ position: [0, distance * 2, 0],
44410
+ fov: 45
44411
+ };
44412
+ case "perspective":
44413
+ default:
44414
+ return {
44415
+ position: [distance, distance, distance],
44416
+ fov: 45
44417
+ };
44418
+ }
44419
+ }, [cameraMode, gridBounds]);
44420
+ const DefaultTileRenderer = React91.useCallback(
44421
+ ({ tile, position }) => {
44422
+ const isSelected = tile.id ? selectedTileIds.includes(tile.id) : false;
44423
+ const isHovered = hoveredTile?.id === tile.id;
44424
+ const isValidMove = validMoves.some(
44425
+ (m) => m.x === tile.x && m.z === (tile.z ?? tile.y ?? 0)
44426
+ );
44427
+ const isAttackTarget = attackTargets.some(
44428
+ (m) => m.x === tile.x && m.z === (tile.z ?? tile.y ?? 0)
44429
+ );
44430
+ let color = 8421504;
44431
+ if (tile.type === "water") color = 4491468;
44432
+ else if (tile.type === "grass") color = 4500036;
44433
+ else if (tile.type === "sand") color = 14535816;
44434
+ else if (tile.type === "rock") color = 8947848;
44435
+ else if (tile.type === "snow") color = 15658734;
44436
+ let emissive = 0;
44437
+ if (isSelected) emissive = 4473924;
44438
+ else if (isAttackTarget) emissive = 4456448;
44439
+ else if (isValidMove) emissive = 17408;
44440
+ else if (isHovered) emissive = 2236962;
44441
+ if (tile.modelUrl) {
44442
+ return /* @__PURE__ */ jsxRuntime.jsx(
44443
+ "group",
44444
+ {
44445
+ position,
44446
+ onClick: (e) => handleTileClick(tile, e),
44447
+ onPointerEnter: (e) => handleTileHover(tile, e),
44448
+ onPointerLeave: (e) => handleTileHover(null, e),
44449
+ userData: { type: "tile", tileId: tile.id, gridX: tile.x, gridZ: tile.z ?? tile.y },
44450
+ children: /* @__PURE__ */ jsxRuntime.jsx(
44451
+ ModelLoader,
44452
+ {
44453
+ url: tile.modelUrl,
44454
+ scale: 0.95,
44455
+ fallbackGeometry: "box",
44456
+ castShadow: true,
44457
+ receiveShadow: true
44458
+ }
44459
+ )
44460
+ }
44461
+ );
44462
+ }
44463
+ return /* @__PURE__ */ jsxRuntime.jsxs(
44464
+ "mesh",
44465
+ {
44466
+ position,
44467
+ onClick: (e) => handleTileClick(tile, e),
44468
+ onPointerEnter: (e) => handleTileHover(tile, e),
44469
+ onPointerLeave: (e) => handleTileHover(null, e),
44470
+ userData: { type: "tile", tileId: tile.id, gridX: tile.x, gridZ: tile.z ?? tile.y },
44471
+ children: [
44472
+ /* @__PURE__ */ jsxRuntime.jsx("boxGeometry", { args: [0.95, 0.2, 0.95] }),
44473
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color, emissive })
44474
+ ]
44475
+ }
44476
+ );
44477
+ },
44478
+ [selectedTileIds, hoveredTile, validMoves, attackTargets, handleTileClick, handleTileHover]
44479
+ );
44480
+ const DefaultUnitRenderer = React91.useCallback(
44481
+ ({ unit, position }) => {
44482
+ const isSelected = selectedUnitId === unit.id;
44483
+ const color = unit.faction === "player" ? 4491519 : unit.faction === "enemy" ? 16729156 : 16777028;
44484
+ return /* @__PURE__ */ jsxRuntime.jsxs(
44485
+ "group",
44486
+ {
44487
+ position,
44488
+ onClick: (e) => handleUnitClick(unit, e),
44489
+ userData: { type: "unit", unitId: unit.id },
44490
+ children: [
44491
+ isSelected && /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, 0.05, 0], rotation: [-Math.PI / 2, 0, 0], children: [
44492
+ /* @__PURE__ */ jsxRuntime.jsx("ringGeometry", { args: [0.4, 0.5, 32] }),
44493
+ /* @__PURE__ */ jsxRuntime.jsx("meshBasicMaterial", { color: "#ffff00", transparent: true, opacity: 0.8 })
44494
+ ] }),
44495
+ unit.modelUrl ? (
44496
+ /* GLB unit model (box fallback while loading / on error) */
44497
+ /* @__PURE__ */ jsxRuntime.jsx(
44498
+ ModelLoader,
44499
+ {
44500
+ url: unit.modelUrl,
44501
+ scale: 0.5,
44502
+ fallbackGeometry: "box",
44503
+ castShadow: true
44504
+ }
44505
+ )
44506
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
44507
+ /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, 0.3, 0], children: [
44508
+ /* @__PURE__ */ jsxRuntime.jsx("cylinderGeometry", { args: [0.3, 0.3, 0.1, 8] }),
44509
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color })
44510
+ ] }),
44511
+ /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, 0.6, 0], children: [
44512
+ /* @__PURE__ */ jsxRuntime.jsx("capsuleGeometry", { args: [0.2, 0.4, 4, 8] }),
44513
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color })
44514
+ ] }),
44515
+ /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, 0.9, 0], children: [
44516
+ /* @__PURE__ */ jsxRuntime.jsx("sphereGeometry", { args: [0.12, 8, 8] }),
44517
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color })
44518
+ ] })
44519
+ ] }),
44520
+ unit.health !== void 0 && unit.maxHealth !== void 0 && /* @__PURE__ */ jsxRuntime.jsxs("group", { position: [0, 1.2, 0], children: [
44521
+ /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [-0.25, 0, 0], children: [
44522
+ /* @__PURE__ */ jsxRuntime.jsx("planeGeometry", { args: [0.5, 0.05] }),
44523
+ /* @__PURE__ */ jsxRuntime.jsx("meshBasicMaterial", { color: 3355443 })
44524
+ ] }),
44525
+ /* @__PURE__ */ jsxRuntime.jsxs(
44526
+ "mesh",
44527
+ {
44528
+ position: [
44529
+ -0.25 + 0.5 * (unit.health / unit.maxHealth) / 2,
44530
+ 0,
44531
+ 0.01
44532
+ ],
44533
+ children: [
44534
+ /* @__PURE__ */ jsxRuntime.jsx("planeGeometry", { args: [0.5 * (unit.health / unit.maxHealth), 0.05] }),
44535
+ /* @__PURE__ */ jsxRuntime.jsx(
44536
+ "meshBasicMaterial",
44537
+ {
44538
+ color: unit.health / unit.maxHealth > 0.5 ? 4500036 : unit.health / unit.maxHealth > 0.25 ? 11184708 : 16729156
44539
+ }
44540
+ )
44541
+ ]
44542
+ }
44543
+ )
44544
+ ] })
44545
+ ]
44546
+ }
44547
+ );
44548
+ },
44549
+ [selectedUnitId, handleUnitClick]
44550
+ );
44551
+ const DefaultFeatureRenderer = React91.useCallback(
44552
+ ({
44553
+ feature,
44554
+ position
44555
+ }) => {
44556
+ if (feature.assetUrl) {
44557
+ return /* @__PURE__ */ jsxRuntime.jsx(
44558
+ ModelLoader,
44559
+ {
44560
+ url: feature.assetUrl,
44561
+ position,
44562
+ scale: 0.5,
44563
+ rotation: [0, feature.rotation ?? 0, 0],
44564
+ onClick: () => handleFeatureClick(feature, null),
44565
+ fallbackGeometry: "box"
44566
+ },
44567
+ feature.id
44568
+ );
44569
+ }
44570
+ if (feature.type === "tree") {
44571
+ return /* @__PURE__ */ jsxRuntime.jsxs(
44572
+ "group",
44573
+ {
44574
+ position,
44575
+ onClick: (e) => handleFeatureClick(feature, e),
44576
+ userData: { type: "feature", featureId: feature.id },
44577
+ children: [
44578
+ /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, 0.4, 0], children: [
44579
+ /* @__PURE__ */ jsxRuntime.jsx("cylinderGeometry", { args: [0.1, 0.15, 0.8, 6] }),
44580
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color: 9127187 })
44581
+ ] }),
44582
+ /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, 0.9, 0], children: [
44583
+ /* @__PURE__ */ jsxRuntime.jsx("coneGeometry", { args: [0.5, 0.8, 8] }),
44584
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color: 2263842 })
44585
+ ] })
44586
+ ]
44587
+ }
44588
+ );
44589
+ }
44590
+ if (feature.type === "rock") {
44591
+ return /* @__PURE__ */ jsxRuntime.jsxs(
44592
+ "mesh",
44593
+ {
44594
+ position: [position[0], position[1] + 0.3, position[2]],
44595
+ onClick: (e) => handleFeatureClick(feature, e),
44596
+ userData: { type: "feature", featureId: feature.id },
44597
+ children: [
44598
+ /* @__PURE__ */ jsxRuntime.jsx("dodecahedronGeometry", { args: [0.3, 0] }),
44599
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color: 8421504 })
44600
+ ]
44601
+ }
44602
+ );
44603
+ }
44604
+ return null;
44605
+ },
44606
+ [handleFeatureClick]
44607
+ );
44608
+ if (externalLoading || assetsLoading && preloadAssets.length > 0) {
44609
+ return /* @__PURE__ */ jsxRuntime.jsx(
44610
+ Canvas3DLoadingState,
44611
+ {
44612
+ progress,
44613
+ loaded,
44614
+ total,
44615
+ message: loadingMessage,
44616
+ className
44617
+ }
44618
+ );
44619
+ }
44620
+ const displayError = externalError || internalError;
44621
+ if (displayError) {
44622
+ 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: [
44623
+ "Error: ",
44624
+ displayError
44625
+ ] }) }) });
44626
+ }
44627
+ return /* @__PURE__ */ jsxRuntime.jsx(
44628
+ Canvas3DErrorBoundary,
44629
+ {
44630
+ onError: (err) => setInternalError(err.message),
44631
+ onReset: () => setInternalError(null),
44632
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
44633
+ "div",
44634
+ {
44635
+ ref: containerRef,
44636
+ className: cn("game-canvas-3d relative w-full h-full min-h-[85vh] overflow-hidden", className),
44637
+ "data-orientation": orientation,
44638
+ "data-camera-mode": cameraMode,
44639
+ "data-overlay": overlay,
44640
+ children: [
44641
+ /* @__PURE__ */ jsxRuntime.jsxs(
44642
+ fiber.Canvas,
44643
+ {
44644
+ shadows,
44645
+ camera: {
44646
+ position: cameraConfig.position,
44647
+ fov: cameraConfig.fov,
44648
+ near: 0.1,
44649
+ far: 1e3
44650
+ },
44651
+ style: { background: backgroundColor },
44652
+ onClick: (e) => {
44653
+ if (e.target === e.currentTarget) {
44654
+ eventHandlers.handleCanvasClick(e);
44655
+ }
44656
+ },
44657
+ children: [
44658
+ /* @__PURE__ */ jsxRuntime.jsx(CameraController, { onCameraChange: eventHandlers.handleCameraChange }),
44659
+ /* @__PURE__ */ jsxRuntime.jsx("ambientLight", { intensity: 0.6 }),
44660
+ /* @__PURE__ */ jsxRuntime.jsx(
44661
+ "directionalLight",
44662
+ {
44663
+ position: [10, 20, 10],
44664
+ intensity: 0.8,
44665
+ castShadow: shadows,
44666
+ "shadow-mapSize": [2048, 2048]
44667
+ }
44668
+ ),
44669
+ /* @__PURE__ */ jsxRuntime.jsx("hemisphereLight", { intensity: 0.3, color: "#87ceeb", groundColor: "#362d1d" }),
44670
+ showGrid && /* @__PURE__ */ jsxRuntime.jsx(
44671
+ drei.Grid,
44672
+ {
44673
+ args: [
44674
+ Math.max(gridBounds.maxX - gridBounds.minX + 2, 10),
44675
+ Math.max(gridBounds.maxZ - gridBounds.minZ + 2, 10)
44676
+ ],
44677
+ position: [
44678
+ (gridBounds.maxX - gridBounds.minX) / 2 - 0.5,
44679
+ 0,
44680
+ (gridBounds.maxZ - gridBounds.minZ) / 2 - 0.5
44681
+ ],
44682
+ cellSize: 1,
44683
+ cellThickness: 1,
44684
+ cellColor: "#444444",
44685
+ sectionSize: 5,
44686
+ sectionThickness: 1.5,
44687
+ sectionColor: "#666666",
44688
+ fadeDistance: 50,
44689
+ fadeStrength: 1
44690
+ }
44691
+ ),
44692
+ tiles.map((tile, index) => {
44693
+ const position = gridToWorld(
44694
+ tile.x,
44695
+ tile.z ?? tile.y ?? 0,
44696
+ tile.elevation ?? 0
44697
+ );
44698
+ const Renderer = CustomTileRenderer || DefaultTileRenderer;
44699
+ return /* @__PURE__ */ jsxRuntime.jsx(Renderer, { tile, position }, tile.id ?? `tile-${index}`);
44700
+ }),
44701
+ features.map((feature, index) => {
44702
+ const position = gridToWorld(
44703
+ feature.x,
44704
+ feature.z ?? feature.y ?? 0,
44705
+ (feature.elevation ?? 0) + 0.5
44706
+ );
44707
+ const Renderer = CustomFeatureRenderer || DefaultFeatureRenderer;
44708
+ return /* @__PURE__ */ jsxRuntime.jsx(Renderer, { feature, position }, feature.id ?? `feature-${index}`);
44709
+ }),
44710
+ units.map((unit) => {
44711
+ const position = gridToWorld(
44712
+ unit.x ?? 0,
44713
+ unit.z ?? unit.y ?? 0,
44714
+ (unit.elevation ?? 0) + 0.5
44715
+ );
44716
+ const Renderer = CustomUnitRenderer || DefaultUnitRenderer;
44717
+ return /* @__PURE__ */ jsxRuntime.jsx(Renderer, { unit, position }, unit.id);
44718
+ }),
44719
+ children,
44720
+ /* @__PURE__ */ jsxRuntime.jsx(
44721
+ drei.OrbitControls,
44722
+ {
44723
+ ref: controlsRef,
44724
+ target: cameraTarget,
44725
+ enableDamping: true,
44726
+ dampingFactor: 0.05,
44727
+ minDistance: 2,
44728
+ maxDistance: 100,
44729
+ maxPolarAngle: Math.PI / 2 - 0.1
44730
+ }
44731
+ )
44732
+ ]
44733
+ }
44734
+ ),
44735
+ showCoordinates && hoveredTile && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "game-canvas-3d__coordinates", children: [
44736
+ "X: ",
44737
+ hoveredTile.x,
44738
+ ", Z: ",
44739
+ hoveredTile.z ?? hoveredTile.y ?? 0
44740
+ ] }),
44741
+ showTileInfo && hoveredTile && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "game-canvas-3d__tile-info", children: [
44742
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "tile-info__type", children: hoveredTile.type }),
44743
+ hoveredTile.terrain && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "tile-info__terrain", children: hoveredTile.terrain })
44744
+ ] })
44745
+ ]
44746
+ }
44747
+ )
44748
+ }
44749
+ );
44750
+ }
44751
+ );
44752
+ GameCanvas3D.displayName = "GameCanvas3D";
44753
+ }
44754
+ });
44755
+ function GameBoard3D({
44756
+ entity,
44757
+ tiles = [],
44758
+ features = [],
44759
+ cameraMode = "perspective",
44760
+ backgroundColor = "#2a1a1a",
44761
+ tileClickEvent,
44762
+ unitClickEvent,
44763
+ attackEvent,
44764
+ endTurnEvent,
44765
+ cancelEvent,
44766
+ playAgainEvent,
44767
+ gameEndEvent,
44768
+ className
44769
+ }) {
44770
+ const row = boardEntity(entity);
44771
+ const eventBus = useEventBus();
44772
+ const entityUnits = row ? rows(row.units) : [];
44773
+ const units = entityUnits;
44774
+ const selectedUnitId = row ? str(row.selectedUnitId) || null : null;
44775
+ const phase = row ? str(row.phase) : "observation";
44776
+ const result = row ? str(row.result) : "none";
44777
+ const validMoves = row && Array.isArray(row.validMoves) ? row.validMoves : [];
44778
+ const attackTargets = row && Array.isArray(row.attackTargets) ? row.attackTargets : [];
44779
+ const turn = row ? num(row.turn) : 0;
44780
+ const currentTeam = row ? str(row.currentTeam) : "player";
44781
+ const isGameOver = result !== "none";
44782
+ const gameEndEmittedRef = React91.useRef(false);
44783
+ React91.useEffect(() => {
44784
+ if ((result === "victory" || result === "defeat") && gameEndEvent) {
44785
+ if (!gameEndEmittedRef.current) {
44786
+ gameEndEmittedRef.current = true;
44787
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
44788
+ }
44789
+ } else {
44790
+ gameEndEmittedRef.current = false;
44791
+ }
44792
+ }, [result, gameEndEvent, eventBus]);
44793
+ const checkGameEnd = React91.useCallback(() => {
44794
+ const alivePlayer = entityUnits.filter((u) => unitTeam(u) === "player" && unitHealth(u) > 0);
44795
+ const aliveEnemy = entityUnits.filter((u) => unitTeam(u) === "enemy" && unitHealth(u) > 0);
44796
+ if (alivePlayer.length === 0 && gameEndEvent) {
44797
+ eventBus.emit(`UI:${gameEndEvent}`, { result: "defeat" });
44798
+ } else if (aliveEnemy.length === 0 && gameEndEvent) {
44799
+ eventBus.emit(`UI:${gameEndEvent}`, { result: "victory" });
44800
+ }
44801
+ }, [entityUnits, gameEndEvent, eventBus]);
44802
+ const handleUnitClickCallback = React91.useCallback((isoUnit) => {
44803
+ if (phase !== "action" || !selectedUnitId || !attackEvent) return;
44804
+ const attackerRow = entityUnits.find((u) => str(u.id) === selectedUnitId);
44805
+ const targetRow = entityUnits.find((u) => str(u.id) === isoUnit.id);
44806
+ if (!attackerRow || !targetRow) return;
44807
+ if (unitTeam(targetRow) !== "enemy" || unitHealth(targetRow) <= 0) return;
44808
+ const ap = unitPosition(attackerRow);
44809
+ const tp = unitPosition(targetRow);
44810
+ const dx = Math.abs(ap.x - tp.x);
44811
+ const dy = Math.abs(ap.y - tp.y);
44812
+ if (dx <= 1 && dy <= 1 && dx + dy > 0) {
44813
+ const damage = Math.max(1, num(attackerRow.attack) - num(targetRow.defense));
44814
+ eventBus.emit(`UI:${attackEvent}`, {
44815
+ attackerId: str(attackerRow.id),
44816
+ targetId: str(targetRow.id),
44817
+ damage
44818
+ });
44819
+ setTimeout(checkGameEnd, 100);
44820
+ }
44821
+ }, [phase, selectedUnitId, entityUnits, attackEvent, eventBus, checkGameEnd]);
44822
+ const handleEndTurn = React91.useCallback(() => {
44823
+ if (endTurnEvent) eventBus.emit(`UI:${endTurnEvent}`, {});
44824
+ }, [endTurnEvent, eventBus]);
44825
+ const handleCancel = React91.useCallback(() => {
44826
+ if (cancelEvent) eventBus.emit(`UI:${cancelEvent}`, {});
44827
+ }, [cancelEvent, eventBus]);
44828
+ const handlePlayAgain = React91.useCallback(() => {
44829
+ if (playAgainEvent) eventBus.emit(`UI:${playAgainEvent}`, {});
44830
+ }, [playAgainEvent, eventBus]);
44831
+ return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { className: cn("game-board-3d block w-full min-h-[85vh] relative", className), children: [
44832
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "game-board-3d__status absolute top-3 left-3 z-10 flex gap-2 items-center", children: [
44833
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", className: "status__phase capitalize", children: phase.replace("_", " ") }),
44834
+ currentTeam && !isGameOver && /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "small", color: "muted", className: "status__team", children: [
44835
+ "\u2014 ",
44836
+ currentTeam === "player" ? "Your Turn" : "Enemy's Turn"
44837
+ ] }),
44838
+ /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "small", color: "muted", className: "status__turn", children: [
44839
+ "Turn ",
44840
+ turn
44841
+ ] })
44842
+ ] }),
44843
+ /* @__PURE__ */ jsxRuntime.jsx(
44844
+ GameCanvas3D,
44845
+ {
44846
+ tiles,
44847
+ units,
44848
+ features,
44849
+ cameraMode,
44850
+ showGrid: true,
44851
+ showCoordinates: false,
44852
+ showTileInfo: false,
44853
+ shadows: true,
44854
+ backgroundColor,
44855
+ tileClickEvent,
44856
+ unitClickEvent,
44857
+ onUnitClick: handleUnitClickCallback,
44858
+ selectedUnitId,
44859
+ validMoves,
44860
+ attackTargets,
44861
+ className: "game-board-3d__canvas w-full min-h-[85vh]"
44862
+ }
44863
+ ),
44864
+ !isGameOver && /* @__PURE__ */ jsxRuntime.jsxs(HStack, { className: "fixed bottom-6 right-6 z-50", gap: "sm", children: [
44865
+ (phase === "selection" || phase === "movement" || phase === "action") && /* @__PURE__ */ jsxRuntime.jsx(
44866
+ Button,
44867
+ {
44868
+ variant: "secondary",
44869
+ className: "shadow-xl",
44870
+ onClick: handleCancel,
44871
+ children: "Cancel"
44872
+ }
44873
+ ),
44874
+ phase !== "enemy_turn" && /* @__PURE__ */ jsxRuntime.jsx(
44875
+ Button,
44876
+ {
44877
+ variant: "primary",
44878
+ className: "shadow-xl",
44879
+ onClick: handleEndTurn,
44880
+ children: "End Turn"
44881
+ }
44882
+ )
44883
+ ] }),
44884
+ 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: [
44885
+ /* @__PURE__ */ jsxRuntime.jsx(
44886
+ Typography,
44887
+ {
44888
+ variant: "h2",
44889
+ className: cn(
44890
+ "overlay__result",
44891
+ result === "victory" ? "text-yellow-400" : "text-red-500"
44892
+ ),
44893
+ children: result === "victory" ? "Victory!" : "Defeat"
44894
+ }
44895
+ ),
44896
+ /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "body", color: "muted", className: "overlay__turn-count", children: [
44897
+ "Completed in ",
44898
+ turn,
44899
+ " turn",
44900
+ turn !== 1 ? "s" : ""
44901
+ ] }),
44902
+ /* @__PURE__ */ jsxRuntime.jsx(
44903
+ Button,
44904
+ {
44905
+ variant: "primary",
44906
+ className: "px-8 py-3 font-semibold",
44907
+ onClick: handlePlayAgain,
44908
+ children: "Play Again"
44909
+ }
44910
+ )
44911
+ ] }) })
44912
+ ] });
44913
+ }
44914
+ var init_GameBoard3D = __esm({
44915
+ "components/game/organisms/GameBoard3D.tsx"() {
44916
+ "use client";
44917
+ init_cn();
44918
+ init_Button();
44919
+ init_Typography();
44920
+ init_Stack();
44921
+ init_useEventBus();
44922
+ init_GameCanvas3D2();
44923
+ init_boardEntity();
44924
+ GameBoard3D.displayName = "GameBoard3D";
44925
+ }
44926
+ });
43223
44927
  var GameShell;
43224
44928
  var init_GameShell = __esm({
43225
44929
  "components/game/templates/GameShell.tsx"() {
@@ -44588,21 +46292,6 @@ var init_ModalSlot = __esm({
44588
46292
  ModalSlot.displayName = "ModalSlot";
44589
46293
  }
44590
46294
  });
44591
- function getOpponentAction(strategy, actions, history) {
44592
- const actionIds = actions.map((a) => a.id);
44593
- switch (strategy) {
44594
- case "always-cooperate":
44595
- return actionIds[0];
44596
- case "always-defect":
44597
- return actionIds[actionIds.length - 1];
44598
- case "tit-for-tat":
44599
- if (history.length === 0) return actionIds[0];
44600
- return history[history.length - 1].playerAction;
44601
- case "random":
44602
- default:
44603
- return actionIds[Math.floor(Math.random() * actionIds.length)];
44604
- }
44605
- }
44606
46295
  function NegotiatorBoard({
44607
46296
  entity,
44608
46297
  completeEvent = "PUZZLE_COMPLETE",
@@ -44624,28 +46313,35 @@ function NegotiatorBoard({
44624
46313
  const playerTotal = num(resolved?.score);
44625
46314
  const isComplete = result !== "none" || totalRounds > 0 && currentRound >= totalRounds;
44626
46315
  const won = result === "win";
44627
- const opponentTotal = history.reduce((s, r2) => s + r2.opponentPayoff, 0);
46316
+ const lastPlayerAction = str(resolved?.lastPlayerAction);
46317
+ const lastOpponentAction = str(resolved?.lastOpponentAction);
46318
+ const lastPayoff = num(resolved?.lastPayoff);
44628
46319
  const actions = Array.isArray(resolved?.actions) ? resolved.actions : [];
44629
46320
  const payoffMatrix = Array.isArray(resolved?.payoffMatrix) ? resolved.payoffMatrix : [];
46321
+ const prevRoundRef = React91.useRef(currentRound);
46322
+ React91.useEffect(() => {
46323
+ if (currentRound > prevRoundRef.current && lastPlayerAction) {
46324
+ const opponentPayoffEntry = payoffMatrix.find(
46325
+ (p2) => p2.playerAction === lastPlayerAction && p2.opponentAction === lastOpponentAction
46326
+ );
46327
+ setHistory((prev) => [
46328
+ ...prev,
46329
+ {
46330
+ round: currentRound,
46331
+ playerAction: lastPlayerAction,
46332
+ opponentAction: lastOpponentAction,
46333
+ playerPayoff: lastPayoff,
46334
+ opponentPayoff: opponentPayoffEntry?.opponentPayoff ?? 0
46335
+ }
46336
+ ]);
46337
+ }
46338
+ prevRoundRef.current = currentRound;
46339
+ }, [currentRound, lastPlayerAction, lastOpponentAction, lastPayoff, payoffMatrix]);
46340
+ const opponentTotal = React91.useMemo(() => history.reduce((s, r2) => s + r2.opponentPayoff, 0), [history]);
44630
46341
  const handleAction = React91.useCallback((actionId) => {
44631
46342
  if (isComplete) return;
44632
- const opponentAction = getOpponentAction(str(resolved?.opponentStrategy) || "random", actions, history);
44633
- const payoff = payoffMatrix.find(
44634
- (p2) => p2.playerAction === actionId && p2.opponentAction === opponentAction
44635
- );
44636
- const playerPayoff = payoff?.playerPayoff ?? 0;
44637
- setHistory((prev) => [
44638
- ...prev,
44639
- {
44640
- round: prev.length + 1,
44641
- playerAction: actionId,
44642
- opponentAction,
44643
- playerPayoff,
44644
- opponentPayoff: payoff?.opponentPayoff ?? 0
44645
- }
44646
- ]);
44647
46343
  if (playRoundEvent) {
44648
- emit(`UI:${playRoundEvent}`, { playerAction: actionId, payoff: playerPayoff });
46344
+ emit(`UI:${playRoundEvent}`, { playerAction: actionId, payoff: 0 });
44649
46345
  }
44650
46346
  if (totalRounds > 0 && currentRound + 1 >= totalRounds) {
44651
46347
  if (finishEvent) {
@@ -44655,10 +46351,11 @@ function NegotiatorBoard({
44655
46351
  setShowHint(true);
44656
46352
  }
44657
46353
  }
44658
- }, [isComplete, resolved, totalRounds, currentRound, actions, payoffMatrix, history, playRoundEvent, finishEvent, emit]);
46354
+ }, [isComplete, resolved, totalRounds, currentRound, playRoundEvent, finishEvent, emit]);
44659
46355
  const handleReset = React91.useCallback(() => {
44660
46356
  setHistory([]);
44661
46357
  setShowHint(false);
46358
+ prevRoundRef.current = 0;
44662
46359
  if (playAgainEvent) {
44663
46360
  emit(`UI:${playAgainEvent}`, {});
44664
46361
  }
@@ -44769,6 +46466,236 @@ var init_NegotiatorBoard = __esm({
44769
46466
  NegotiatorBoard.displayName = "NegotiatorBoard";
44770
46467
  }
44771
46468
  });
46469
+ function resolveEntity(entity) {
46470
+ if (!entity) return {};
46471
+ if (Array.isArray(entity)) return entity[0] ?? {};
46472
+ return entity;
46473
+ }
46474
+ function numField(row, key, fallback) {
46475
+ const v = row[key];
46476
+ return typeof v === "number" ? v : fallback;
46477
+ }
46478
+ function strField(row, key, fallback) {
46479
+ const v = row[key];
46480
+ return typeof v === "string" ? v : fallback;
46481
+ }
46482
+ function PlatformerBoard({
46483
+ entity,
46484
+ player: propPlayer,
46485
+ platforms: propPlatforms,
46486
+ result: propResult,
46487
+ lives: propLives,
46488
+ score: propScore,
46489
+ level: propLevel,
46490
+ worldWidth = 800,
46491
+ worldHeight = 400,
46492
+ canvasWidth = 800,
46493
+ canvasHeight = 400,
46494
+ playerSprite,
46495
+ tileSprites,
46496
+ bgColor,
46497
+ leftEvent = "LEFT",
46498
+ rightEvent = "RIGHT",
46499
+ jumpEvent = "JUMP",
46500
+ stopEvent = "STOP",
46501
+ playAgainEvent = "PLAY_AGAIN",
46502
+ gameEndEvent = "GAME_END",
46503
+ className
46504
+ }) {
46505
+ const row = resolveEntity(entity);
46506
+ const eventBus = useEventBus();
46507
+ const { t } = hooks.useTranslate();
46508
+ const result = propResult ?? strField(row, "result", "none");
46509
+ propLives ?? numField(row, "lives", 3);
46510
+ const score = propScore ?? numField(row, "score", 0);
46511
+ const level = propLevel ?? numField(row, "level", 1);
46512
+ const entityPlayer = row["player"];
46513
+ const player = propPlayer ?? entityPlayer ?? {
46514
+ x: 80,
46515
+ y: 320,
46516
+ width: 32,
46517
+ height: 48,
46518
+ vx: 0,
46519
+ vy: 0,
46520
+ grounded: false,
46521
+ facingRight: true
46522
+ };
46523
+ const entityPlatforms = Array.isArray(row["platforms"]) ? row["platforms"] : void 0;
46524
+ const platforms = propPlatforms ?? entityPlatforms ?? [
46525
+ { x: 0, y: 368, width: 800, height: 32, type: "ground" },
46526
+ { x: 150, y: 280, width: 160, height: 16, type: "platform" },
46527
+ { x: 420, y: 220, width: 160, height: 16, type: "platform" },
46528
+ { x: 580, y: 300, width: 80, height: 16, type: "hazard" },
46529
+ { x: 700, y: 340, width: 64, height: 28, type: "goal" }
46530
+ ];
46531
+ const handleRestart = React91.useCallback(() => {
46532
+ if (playAgainEvent) {
46533
+ eventBus.emit(`UI:${playAgainEvent}`, {});
46534
+ }
46535
+ }, [playAgainEvent, eventBus]);
46536
+ const isGameOver = result === "won" || result === "lost";
46537
+ const gameEndEmittedRef = React91.useRef(false);
46538
+ React91.useEffect(() => {
46539
+ if ((result === "won" || result === "lost") && gameEndEvent) {
46540
+ if (!gameEndEmittedRef.current) {
46541
+ gameEndEmittedRef.current = true;
46542
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
46543
+ }
46544
+ } else {
46545
+ gameEndEmittedRef.current = false;
46546
+ }
46547
+ }, [result, gameEndEvent, eventBus]);
46548
+ return /* @__PURE__ */ jsxRuntime.jsx(
46549
+ VStack,
46550
+ {
46551
+ className: cn("platformer-board relative bg-background", className),
46552
+ gap: "none",
46553
+ children: /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "relative", children: [
46554
+ /* @__PURE__ */ jsxRuntime.jsx(
46555
+ PlatformerCanvas,
46556
+ {
46557
+ player,
46558
+ platforms,
46559
+ worldWidth,
46560
+ worldHeight,
46561
+ canvasWidth,
46562
+ canvasHeight,
46563
+ followCamera: true,
46564
+ bgColor,
46565
+ playerSprite,
46566
+ tileSprites,
46567
+ leftEvent,
46568
+ rightEvent,
46569
+ jumpEvent,
46570
+ stopEvent
46571
+ }
46572
+ ),
46573
+ 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: [
46574
+ /* @__PURE__ */ jsxRuntime.jsx(
46575
+ Typography,
46576
+ {
46577
+ variant: "h2",
46578
+ className: cn(
46579
+ "text-4xl font-black tracking-widest uppercase",
46580
+ result === "won" ? "text-warning" : "text-error"
46581
+ ),
46582
+ children: result === "won" ? t("platformer.won") : t("platformer.lost")
46583
+ }
46584
+ ),
46585
+ result === "won" && /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "body1", className: "text-muted-foreground", children: [
46586
+ t("platformer.level"),
46587
+ " ",
46588
+ level,
46589
+ " \u2014 ",
46590
+ t("platformer.score"),
46591
+ ": ",
46592
+ score
46593
+ ] }),
46594
+ result === "lost" && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body1", className: "text-muted-foreground", children: t("platformer.noLives") }),
46595
+ /* @__PURE__ */ jsxRuntime.jsx(
46596
+ Button,
46597
+ {
46598
+ variant: "primary",
46599
+ className: "px-8 py-3 font-semibold",
46600
+ onClick: handleRestart,
46601
+ children: result === "won" ? t("platformer.nextLevel") : t("platformer.playAgain")
46602
+ }
46603
+ )
46604
+ ] }) })
46605
+ ] })
46606
+ }
46607
+ );
46608
+ }
46609
+ var init_PlatformerBoard = __esm({
46610
+ "components/game/organisms/PlatformerBoard.tsx"() {
46611
+ "use client";
46612
+ init_cn();
46613
+ init_useEventBus();
46614
+ init_Box();
46615
+ init_Button();
46616
+ init_Typography();
46617
+ init_Stack();
46618
+ init_PlatformerCanvas();
46619
+ PlatformerBoard.displayName = "PlatformerBoard";
46620
+ }
46621
+ });
46622
+ function PlatformerTemplate({
46623
+ entity,
46624
+ platforms = DEFAULT_PLATFORMS,
46625
+ worldWidth = 800,
46626
+ worldHeight = 400,
46627
+ canvasWidth = 800,
46628
+ canvasHeight = 400,
46629
+ playerSprite = `${CDN3}/characters/platformChar_idle.png`,
46630
+ tileSprites = DEFAULT_TILE_SPRITES,
46631
+ bgColor = "#5c94fc",
46632
+ gameEndEvent = "GAME_END",
46633
+ className
46634
+ }) {
46635
+ const row = entity && !Array.isArray(entity) ? entity : void 0;
46636
+ const lives = typeof row?.["lives"] === "number" ? row["lives"] : 3;
46637
+ const score = typeof row?.["score"] === "number" ? row["score"] : 0;
46638
+ const level = typeof row?.["level"] === "number" ? row["level"] : 1;
46639
+ return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { className: cn("platformer-template w-full", className), gap: "none", children: [
46640
+ /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "relative", children: /* @__PURE__ */ jsxRuntime.jsx(
46641
+ GameHud,
46642
+ {
46643
+ position: "top",
46644
+ stats: [
46645
+ { label: "Lives", value: lives, format: "number", variant: "danger" },
46646
+ { label: "Level", value: level, format: "number", variant: "default" }
46647
+ ]
46648
+ }
46649
+ ) }),
46650
+ /* @__PURE__ */ jsxRuntime.jsx(
46651
+ PlatformerBoard,
46652
+ {
46653
+ entity,
46654
+ platforms,
46655
+ worldWidth,
46656
+ worldHeight,
46657
+ canvasWidth,
46658
+ canvasHeight,
46659
+ playerSprite,
46660
+ tileSprites,
46661
+ bgColor,
46662
+ leftEvent: "LEFT",
46663
+ rightEvent: "RIGHT",
46664
+ jumpEvent: "JUMP",
46665
+ stopEvent: "STOP",
46666
+ playAgainEvent: "PLAY_AGAIN",
46667
+ gameEndEvent
46668
+ }
46669
+ ),
46670
+ /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "px-4 py-2", children: /* @__PURE__ */ jsxRuntime.jsx(ScoreBoard, { score, level }) })
46671
+ ] });
46672
+ }
46673
+ var CDN3, DEFAULT_TILE_SPRITES, DEFAULT_PLATFORMS;
46674
+ var init_PlatformerTemplate = __esm({
46675
+ "components/game/templates/PlatformerTemplate.tsx"() {
46676
+ init_cn();
46677
+ init_Box();
46678
+ init_Stack();
46679
+ init_PlatformerBoard();
46680
+ init_GameHud();
46681
+ init_ScoreBoard();
46682
+ CDN3 = "https://almadar-kflow-assets.web.app/shared/platformer";
46683
+ DEFAULT_TILE_SPRITES = {
46684
+ ground: `${CDN3}/tiles/platformPack_tile001.png`,
46685
+ platform: `${CDN3}/tiles/platformPack_tile004.png`,
46686
+ hazard: `${CDN3}/tiles/platformPack_tile017.png`,
46687
+ goal: `${CDN3}/tiles/platformPack_tile007.png`
46688
+ };
46689
+ DEFAULT_PLATFORMS = [
46690
+ { x: 0, y: 368, width: 800, height: 32, type: "ground" },
46691
+ { x: 150, y: 280, width: 160, height: 16, type: "platform" },
46692
+ { x: 420, y: 220, width: 160, height: 16, type: "platform" },
46693
+ { x: 580, y: 300, width: 80, height: 16, type: "hazard" },
46694
+ { x: 700, y: 340, width: 64, height: 28, type: "goal" }
46695
+ ];
46696
+ PlatformerTemplate.displayName = "PlatformerTemplate";
46697
+ }
46698
+ });
44772
46699
  var PricingOrganism;
44773
46700
  var init_PricingOrganism = __esm({
44774
46701
  "components/marketing/organisms/PricingOrganism.tsx"() {
@@ -44891,6 +46818,356 @@ var init_PricingPageTemplate = __esm({
44891
46818
  PricingPageTemplate.displayName = "PricingPageTemplate";
44892
46819
  }
44893
46820
  });
46821
+ function RoguelikeBoard({
46822
+ entity,
46823
+ tiles: propTiles,
46824
+ enemies: propEnemies,
46825
+ items: propItems,
46826
+ player: propPlayer,
46827
+ playerHp: propPlayerHp,
46828
+ playerMaxHp: propPlayerMaxHp,
46829
+ playerAttack: propPlayerAttack,
46830
+ depth: propDepth,
46831
+ result: propResult,
46832
+ phase: propPhase,
46833
+ stairsX: propStairsX,
46834
+ stairsY: propStairsY,
46835
+ assetManifest: propAssetManifest,
46836
+ scale = 0.45,
46837
+ unitScale = 1,
46838
+ moveEvent,
46839
+ playAgainEvent,
46840
+ gameEndEvent,
46841
+ className
46842
+ }) {
46843
+ const board = boardEntity(entity) ?? {};
46844
+ const tiles = propTiles ?? (Array.isArray(board.tiles) ? board.tiles : DEFAULT_TILES2);
46845
+ const enemies = propEnemies ?? (Array.isArray(board.enemies) ? board.enemies : DEFAULT_ENEMIES);
46846
+ const items = propItems ?? (Array.isArray(board.items) ? board.items : DEFAULT_ITEMS);
46847
+ const player = propPlayer ?? board.player ?? { x: 1, y: 1 };
46848
+ const playerHp = propPlayerHp ?? num(board.playerHp, 10);
46849
+ const playerMaxHp = propPlayerMaxHp ?? num(board.playerMaxHp, 10);
46850
+ const playerAttack = propPlayerAttack ?? num(board.playerAttack, 3);
46851
+ const depth = propDepth ?? num(board.depth, 1);
46852
+ const result = propResult ?? (str(board.result) || "none");
46853
+ const phase = propPhase ?? (str(board.phase) || "player_turn");
46854
+ propStairsX ?? num(board.stairsX, 3);
46855
+ propStairsY ?? num(board.stairsY, 3);
46856
+ const eventBus = useEventBus();
46857
+ const { t } = hooks.useTranslate();
46858
+ const [hoveredTile, setHoveredTile] = React91.useState(null);
46859
+ const emitMove = React91.useCallback((dx, dy) => {
46860
+ if (!moveEvent) return;
46861
+ eventBus.emit(`UI:${moveEvent}`, { dx, dy });
46862
+ }, [moveEvent, eventBus]);
46863
+ React91.useEffect(() => {
46864
+ const handler = (e) => {
46865
+ if (result !== "none" || phase !== "player_turn") return;
46866
+ if (e.key === "ArrowUp" || e.key === "w") emitMove(0, -1);
46867
+ if (e.key === "ArrowDown" || e.key === "s") emitMove(0, 1);
46868
+ if (e.key === "ArrowLeft" || e.key === "a") emitMove(-1, 0);
46869
+ if (e.key === "ArrowRight" || e.key === "d") emitMove(1, 0);
46870
+ };
46871
+ window.addEventListener("keydown", handler);
46872
+ return () => window.removeEventListener("keydown", handler);
46873
+ }, [result, phase, emitMove]);
46874
+ React91.useEffect(() => {
46875
+ if ((result === "won" || result === "lost") && gameEndEvent) {
46876
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
46877
+ }
46878
+ }, [result, gameEndEvent, eventBus]);
46879
+ const handleTileClick = React91.useCallback((x, y) => {
46880
+ if (result !== "none" || phase !== "player_turn") return;
46881
+ const dx = x - player.x;
46882
+ const dy = y - player.y;
46883
+ if (Math.abs(dx) + Math.abs(dy) === 1) {
46884
+ emitMove(dx, dy);
46885
+ }
46886
+ }, [result, phase, player, emitMove]);
46887
+ const handleReset = React91.useCallback(() => {
46888
+ if (!playAgainEvent) return;
46889
+ eventBus.emit(`UI:${playAgainEvent}`, {});
46890
+ }, [playAgainEvent, eventBus]);
46891
+ const isoUnits = React91.useMemo(() => {
46892
+ const liveEnemies = enemies.filter((e) => e.hp > 0);
46893
+ return [
46894
+ {
46895
+ id: "player",
46896
+ position: player,
46897
+ name: t("roguelike.player") || "Hero",
46898
+ team: "player",
46899
+ health: playerHp,
46900
+ maxHealth: playerMaxHp,
46901
+ unitType: "player",
46902
+ sprite: `${CDN4}/isometric-dungeon/Characters/Male/Male_0_Idle0.png`
46903
+ },
46904
+ ...liveEnemies.map((e) => ({
46905
+ id: e.id,
46906
+ position: { x: e.x, y: e.y },
46907
+ name: t("roguelike.enemy") || "Enemy",
46908
+ team: "enemy",
46909
+ health: e.hp,
46910
+ maxHealth: 6,
46911
+ unitType: "enemy",
46912
+ sprite: `${CDN4}/sprite-sheets/shadow-legion-sprite-sheet-sw.png`
46913
+ }))
46914
+ ];
46915
+ }, [player, enemies, playerHp, playerMaxHp, t]);
46916
+ const isoFeatures = React91.useMemo(
46917
+ () => items.map((it) => ({
46918
+ id: it.id,
46919
+ x: it.x,
46920
+ y: it.y,
46921
+ type: it.kind,
46922
+ sprite: FEATURE_SPRITE[it.kind] ?? FEATURE_SPRITE.health_potion
46923
+ })),
46924
+ [items]
46925
+ );
46926
+ const validMoves = React91.useMemo(() => {
46927
+ if (result !== "none" || phase !== "player_turn") return [];
46928
+ const dirs = [{ dx: 0, dy: -1 }, { dx: 0, dy: 1 }, { dx: -1, dy: 0 }, { dx: 1, dy: 0 }];
46929
+ return dirs.map(({ dx, dy }) => ({ x: player.x + dx, y: player.y + dy })).filter(({ x, y }) => {
46930
+ const tile = tiles.find((t2) => t2.x === x && t2.y === y);
46931
+ return tile?.passable ?? false;
46932
+ });
46933
+ }, [result, phase, player, tiles]);
46934
+ const isGameOver = result === "won" || result === "lost";
46935
+ return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { className: cn("roguelike-board relative min-h-[600px] bg-background", className), gap: "none", children: [
46936
+ /* @__PURE__ */ jsxRuntime.jsxs(HStack, { className: "items-center justify-between px-4 py-2 border-b border-border", gap: "md", children: [
46937
+ /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", className: "items-center", children: [
46938
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: t("roguelike.depth") || "Depth" }),
46939
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body1", className: "font-bold tabular-nums", children: depth })
46940
+ ] }),
46941
+ /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", className: "items-center", children: [
46942
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: t("roguelike.hp") || "HP" }),
46943
+ /* @__PURE__ */ jsxRuntime.jsxs(
46944
+ Typography,
46945
+ {
46946
+ variant: "body1",
46947
+ className: cn(
46948
+ "font-bold tabular-nums",
46949
+ playerHp <= Math.floor(playerMaxHp * 0.25) ? "text-error" : "text-success"
46950
+ ),
46951
+ children: [
46952
+ playerHp,
46953
+ " / ",
46954
+ playerMaxHp
46955
+ ]
46956
+ }
46957
+ )
46958
+ ] }),
46959
+ /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", className: "items-center", children: [
46960
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: t("roguelike.atk") || "ATK" }),
46961
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body1", className: "font-bold tabular-nums text-foreground", children: playerAttack })
46962
+ ] }),
46963
+ /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", className: "items-center", children: [
46964
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: t("roguelike.phase") || "Phase" }),
46965
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "capitalize text-foreground", children: phase.replace("_", " ") })
46966
+ ] })
46967
+ ] }),
46968
+ /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "relative flex-1", children: /* @__PURE__ */ jsxRuntime.jsx(
46969
+ IsometricCanvas_default,
46970
+ {
46971
+ tiles,
46972
+ units: isoUnits,
46973
+ features: isoFeatures,
46974
+ selectedUnitId: null,
46975
+ validMoves,
46976
+ attackTargets: [],
46977
+ hoveredTile,
46978
+ onTileClick: handleTileClick,
46979
+ onUnitClick: () => void 0,
46980
+ onTileHover: (x, y) => setHoveredTile({ x, y }),
46981
+ onTileLeave: () => setHoveredTile(null),
46982
+ scale,
46983
+ assetBaseUrl: propAssetManifest?.baseUrl ?? CDN4,
46984
+ assetManifest: propAssetManifest,
46985
+ unitScale
46986
+ }
46987
+ ) }),
46988
+ !isGameOver && /* @__PURE__ */ jsxRuntime.jsx(HStack, { className: "justify-center gap-2 p-3", gap: "none", children: [
46989
+ { label: "\u2191", dx: 0, dy: -1 },
46990
+ { label: "\u2193", dx: 0, dy: 1 },
46991
+ { label: "\u2190", dx: -1, dy: 0 },
46992
+ { label: "\u2192", dx: 1, dy: 0 }
46993
+ ].map(({ label, dx, dy }) => /* @__PURE__ */ jsxRuntime.jsx(
46994
+ Button,
46995
+ {
46996
+ variant: "secondary",
46997
+ className: "w-9 h-9 p-0 text-base font-mono",
46998
+ onClick: () => emitMove(dx, dy),
46999
+ disabled: phase !== "player_turn",
47000
+ children: label
47001
+ },
47002
+ label
47003
+ )) }),
47004
+ 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: [
47005
+ /* @__PURE__ */ jsxRuntime.jsx(
47006
+ Typography,
47007
+ {
47008
+ variant: "h2",
47009
+ className: cn(
47010
+ "text-4xl font-black tracking-widest uppercase",
47011
+ result === "won" ? "text-warning" : "text-error"
47012
+ ),
47013
+ children: result === "won" ? t("roguelike.victory") || "Victory!" : t("roguelike.defeat") || "Defeated!"
47014
+ }
47015
+ ),
47016
+ result === "won" && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body1", className: "text-muted-foreground", children: t("roguelike.cleared") || `Dungeon cleared! Depth reached: ${depth}` }),
47017
+ /* @__PURE__ */ jsxRuntime.jsx(
47018
+ Button,
47019
+ {
47020
+ variant: "primary",
47021
+ className: "px-8 py-3 font-semibold",
47022
+ onClick: handleReset,
47023
+ children: t("roguelike.playAgain") || "Play Again"
47024
+ }
47025
+ )
47026
+ ] }) })
47027
+ ] });
47028
+ }
47029
+ var CDN4, DEFAULT_TILES2, DEFAULT_ENEMIES, DEFAULT_ITEMS, FEATURE_SPRITE;
47030
+ var init_RoguelikeBoard = __esm({
47031
+ "components/game/organisms/RoguelikeBoard.tsx"() {
47032
+ "use client";
47033
+ init_cn();
47034
+ init_useEventBus();
47035
+ init_Box();
47036
+ init_Button();
47037
+ init_Typography();
47038
+ init_Stack();
47039
+ init_IsometricCanvas();
47040
+ init_boardEntity();
47041
+ CDN4 = "https://almadar-kflow-assets.web.app/shared";
47042
+ DEFAULT_TILES2 = [
47043
+ { x: 0, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
47044
+ { x: 1, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
47045
+ { x: 2, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
47046
+ { x: 3, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
47047
+ { x: 4, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
47048
+ { x: 0, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
47049
+ { x: 1, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
47050
+ { x: 2, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
47051
+ { x: 3, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
47052
+ { x: 4, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
47053
+ { x: 0, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
47054
+ { x: 1, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirtTiles_E.png` },
47055
+ { x: 2, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirtTiles_E.png` },
47056
+ { x: 3, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
47057
+ { x: 4, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
47058
+ { x: 0, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
47059
+ { x: 1, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
47060
+ { x: 2, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirtTiles_E.png` },
47061
+ { x: 3, y: 3, terrain: "stairs", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stairs_E.png` },
47062
+ { x: 4, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
47063
+ { x: 0, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
47064
+ { x: 1, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
47065
+ { x: 2, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
47066
+ { x: 3, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
47067
+ { x: 4, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` }
47068
+ ];
47069
+ DEFAULT_ENEMIES = [
47070
+ { id: "e1", x: 3, y: 1, hp: 5, attack: 2 },
47071
+ { id: "e2", x: 1, y: 3, hp: 4, attack: 1 }
47072
+ ];
47073
+ DEFAULT_ITEMS = [
47074
+ { id: "i1", x: 2, y: 2, kind: "health_potion" }
47075
+ ];
47076
+ FEATURE_SPRITE = {
47077
+ health_potion: `${CDN4}/isometric-dungeon/Isometric/chestClosed_E.png`,
47078
+ sword: `${CDN4}/isometric-dungeon/Isometric/barrel_E.png`,
47079
+ shield: `${CDN4}/isometric-dungeon/Isometric/barrel_E.png`
47080
+ };
47081
+ RoguelikeBoard.displayName = "RoguelikeBoard";
47082
+ }
47083
+ });
47084
+ function RoguelikeTemplate({
47085
+ entity,
47086
+ scale = 0.45,
47087
+ unitScale = 1,
47088
+ tiles = DEFAULT_ROGUELIKE_TILES,
47089
+ enemies,
47090
+ items,
47091
+ stairsX,
47092
+ stairsY,
47093
+ assetManifest = DEFAULT_ROGUELIKE_MANIFEST,
47094
+ gameEndEvent,
47095
+ className
47096
+ }) {
47097
+ const resolved = entity && typeof entity === "object" && !Array.isArray(entity) ? entity : void 0;
47098
+ if (!resolved && !tiles && !assetManifest) return null;
47099
+ return /* @__PURE__ */ jsxRuntime.jsx(
47100
+ RoguelikeBoard,
47101
+ {
47102
+ entity: resolved,
47103
+ tiles,
47104
+ enemies,
47105
+ items,
47106
+ stairsX,
47107
+ stairsY,
47108
+ assetManifest,
47109
+ scale,
47110
+ unitScale,
47111
+ moveEvent: "MOVE",
47112
+ playAgainEvent: "PLAY_AGAIN",
47113
+ gameEndEvent: gameEndEvent ?? "GAME_END",
47114
+ className
47115
+ }
47116
+ );
47117
+ }
47118
+ var CDN5, DEFAULT_ROGUELIKE_TILES, DEFAULT_ROGUELIKE_MANIFEST;
47119
+ var init_RoguelikeTemplate = __esm({
47120
+ "components/game/templates/RoguelikeTemplate.tsx"() {
47121
+ init_RoguelikeBoard();
47122
+ CDN5 = "https://almadar-kflow-assets.web.app/shared";
47123
+ DEFAULT_ROGUELIKE_TILES = [
47124
+ { x: 0, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47125
+ { x: 1, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47126
+ { x: 2, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47127
+ { x: 3, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47128
+ { x: 4, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47129
+ { x: 0, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47130
+ { x: 1, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
47131
+ { x: 2, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
47132
+ { x: 3, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
47133
+ { x: 4, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47134
+ { x: 0, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47135
+ { x: 1, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirtTiles_E.png` },
47136
+ { x: 2, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirtTiles_E.png` },
47137
+ { x: 3, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
47138
+ { x: 4, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47139
+ { x: 0, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47140
+ { x: 1, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
47141
+ { x: 2, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirtTiles_E.png` },
47142
+ { x: 3, y: 3, terrain: "stairs", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stairs_E.png` },
47143
+ { x: 4, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47144
+ { x: 0, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47145
+ { x: 1, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47146
+ { x: 2, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47147
+ { x: 3, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47148
+ { x: 4, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` }
47149
+ ];
47150
+ DEFAULT_ROGUELIKE_MANIFEST = {
47151
+ baseUrl: CDN5,
47152
+ terrains: {
47153
+ floor: "/isometric-dungeon/Isometric/dirt_E.png",
47154
+ wall: "/isometric-dungeon/Isometric/stoneInset_E.png",
47155
+ stairs: "/isometric-dungeon/Isometric/stairs_E.png",
47156
+ planks: "/isometric-dungeon/Isometric/planks_E.png"
47157
+ },
47158
+ units: {
47159
+ player: "/isometric-dungeon/Characters/Male/Male_0_Idle0.png",
47160
+ enemy: "/sprite-sheets/shadow-legion-sprite-sheet-sw.png"
47161
+ },
47162
+ features: {
47163
+ health_potion: "/isometric-dungeon/Isometric/chestClosed_E.png",
47164
+ sword: "/isometric-dungeon/Isometric/barrel_E.png",
47165
+ shield: "/isometric-dungeon/Isometric/barrel_E.png"
47166
+ }
47167
+ };
47168
+ RoguelikeTemplate.displayName = "RoguelikeTemplate";
47169
+ }
47170
+ });
44894
47171
 
44895
47172
  // lib/traitRegistry.ts
44896
47173
  function notifyListeners2() {
@@ -46144,7 +48421,7 @@ function getAllEvents(traits2) {
46144
48421
  function EventDispatcherTab({ traits: traits2, schema }) {
46145
48422
  const eventBus = useEventBus();
46146
48423
  const { t } = hooks.useTranslate();
46147
- const [log13, setLog] = React91__namespace.useState([]);
48424
+ const [log15, setLog] = React91__namespace.useState([]);
46148
48425
  const prevStatesRef = React91__namespace.useRef(/* @__PURE__ */ new Map());
46149
48426
  React91__namespace.useEffect(() => {
46150
48427
  for (const trait of traits2) {
@@ -46208,9 +48485,9 @@ function EventDispatcherTab({ traits: traits2, schema }) {
46208
48485
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", weight: "medium", className: "text-gray-500 mb-1", children: t("debug.otherEvents") }),
46209
48486
  /* @__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)) })
46210
48487
  ] }),
46211
- log13.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
48488
+ log15.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
46212
48489
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", weight: "medium", className: "text-gray-500 mb-1", children: t("debug.recentTransitions") }),
46213
- /* @__PURE__ */ jsxRuntime.jsx(Stack, { gap: "xs", children: log13.map((entry, i) => /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "small", className: "font-mono text-xs", children: [
48490
+ /* @__PURE__ */ jsxRuntime.jsx(Stack, { gap: "xs", children: log15.map((entry, i) => /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "small", className: "font-mono text-xs", children: [
46214
48491
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-purple-400", children: entry.traitName }),
46215
48492
  " ",
46216
48493
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-500", children: entry.from }),
@@ -47013,17 +49290,24 @@ function SequencerBoard({
47013
49290
  const { emit } = useEventBus();
47014
49291
  const { t } = hooks.useTranslate();
47015
49292
  const resolved = boardEntity(entity);
47016
- const maxSlots = num(resolved?.maxSlots);
49293
+ const maxSlots = num(resolved?.maxSlots) || 3;
47017
49294
  const solutions = Array.isArray(resolved?.solutions) ? resolved.solutions : [];
47018
49295
  const availableActions = Array.isArray(resolved?.availableActions) ? resolved.availableActions : [];
47019
49296
  const allowDuplicates = resolved?.allowDuplicates !== false;
49297
+ const entitySlots = Array.isArray(resolved?.slots) ? resolved.slots : [];
49298
+ const entityResult = str(resolved?.result);
49299
+ const entityAttempts = num(resolved?.attempts);
49300
+ const entityCurrentStep = typeof resolved?.currentStep === "number" ? resolved.currentStep : -1;
49301
+ const slots = Array.from({ length: maxSlots }, (_, i) => {
49302
+ const entitySlot = entitySlots.find((s) => s.index === i);
49303
+ if (!entitySlot?.placedActionId) return void 0;
49304
+ return availableActions.find((a) => a.id === entitySlot.placedActionId);
49305
+ });
49306
+ const isSuccess = entityResult === "win";
49307
+ const attempts = entityAttempts;
49308
+ const isPlayingBack = entityCurrentStep >= 0 && !isSuccess;
49309
+ const currentStep = entityCurrentStep;
47020
49310
  const [headerError, setHeaderError] = React91.useState(false);
47021
- const [slots, setSlots] = React91.useState(
47022
- () => Array.from({ length: maxSlots }, () => void 0)
47023
- );
47024
- const [playState, setPlayState] = React91.useState("idle");
47025
- const [currentStep, setCurrentStep] = React91.useState(-1);
47026
- const [attempts, setAttempts] = React91.useState(0);
47027
49311
  const [slotFeedback, setSlotFeedback] = React91.useState(
47028
49312
  () => Array.from({ length: maxSlots }, () => null)
47029
49313
  );
@@ -47032,11 +49316,6 @@ function SequencerBoard({
47032
49316
  if (timerRef.current) clearTimeout(timerRef.current);
47033
49317
  }, []);
47034
49318
  const handleSlotDrop = React91.useCallback((index, item) => {
47035
- setSlots((prev) => {
47036
- const next = [...prev];
47037
- next[index] = item;
47038
- return next;
47039
- });
47040
49319
  setSlotFeedback((prev) => {
47041
49320
  const next = [...prev];
47042
49321
  next[index] = null;
@@ -47046,11 +49325,6 @@ function SequencerBoard({
47046
49325
  if (placeEvent) emit(`UI:${placeEvent}`, { slotIndex: index, actionId: item.id });
47047
49326
  }, [emit, placeEvent]);
47048
49327
  const handleSlotRemove = React91.useCallback((index) => {
47049
- setSlots((prev) => {
47050
- const next = [...prev];
47051
- next[index] = void 0;
47052
- return next;
47053
- });
47054
49328
  setSlotFeedback((prev) => {
47055
49329
  const next = [...prev];
47056
49330
  next[index] = null;
@@ -47061,48 +49335,34 @@ function SequencerBoard({
47061
49335
  }, [emit, removeEvent]);
47062
49336
  const handleReset = React91.useCallback(() => {
47063
49337
  if (timerRef.current) clearTimeout(timerRef.current);
47064
- setSlots(Array.from({ length: maxSlots }, () => void 0));
47065
- setPlayState("idle");
47066
- setCurrentStep(-1);
47067
- setAttempts(0);
47068
49338
  setSlotFeedback(Array.from({ length: maxSlots }, () => null));
47069
49339
  if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
47070
49340
  }, [maxSlots, playAgainEvent, emit]);
47071
49341
  const filledSlots = slots.filter((s) => !!s);
47072
- const canPlay = filledSlots.length > 0 && playState === "idle";
49342
+ const canPlay = filledSlots.length > 0 && !isPlayingBack && !isSuccess;
47073
49343
  const handlePlay = React91.useCallback(() => {
47074
49344
  if (!canPlay) return;
47075
49345
  setSlotFeedback(Array.from({ length: maxSlots }, () => null));
47076
49346
  emit("UI:PLAY_SOUND", { key: "confirm" });
47077
49347
  const sequence = slots.map((s) => s?.id || "");
47078
- if (playEvent) {
47079
- emit(`UI:${playEvent}`, { sequence });
47080
- }
47081
- setPlayState("playing");
47082
- setCurrentStep(0);
49348
+ const playerIds = slots.filter(Boolean).map((s) => s?.id || "");
49349
+ if (playEvent) emit(`UI:${playEvent}`, { sequence });
47083
49350
  let step = 0;
47084
49351
  const advance = () => {
47085
49352
  step++;
49353
+ emit("UI:STEP", { step });
47086
49354
  if (step >= maxSlots) {
49355
+ if (checkEvent) emit(`UI:${checkEvent}`, { sequence: playerIds });
47087
49356
  const playerSeq = slots.map((s) => s?.id);
47088
- const playerIds = slots.filter(Boolean).map((s) => s?.id || "");
47089
49357
  const success = solutions.some(
47090
49358
  (sol) => sol.length === playerIds.length && sol.every((id, i) => id === playerIds[i])
47091
49359
  );
47092
- if (checkEvent) emit(`UI:${checkEvent}`, { sequence: playerIds });
47093
49360
  if (success) {
47094
- setPlayState("success");
47095
- setCurrentStep(-1);
47096
49361
  emit("UI:PLAY_SOUND", { key: "levelComplete" });
47097
- if (completeEvent) {
47098
- emit(`UI:${completeEvent}`, { success: true, sequence: playerIds });
47099
- }
49362
+ if (completeEvent) emit(`UI:${completeEvent}`, { success: true, sequence: playerIds });
47100
49363
  } else {
47101
- setAttempts((prev) => prev + 1);
47102
49364
  const feedback = computeSlotFeedback(playerSeq, solutions);
47103
49365
  setSlotFeedback(feedback);
47104
- setPlayState("idle");
47105
- setCurrentStep(-1);
47106
49366
  emit("UI:PLAY_SOUND", { key: "fail" });
47107
49367
  const correctCount2 = feedback.filter((f3) => f3 === "correct").length;
47108
49368
  for (let ci = 0; ci < correctCount2; ci++) {
@@ -47112,7 +49372,6 @@ function SequencerBoard({
47112
49372
  }
47113
49373
  }
47114
49374
  } else {
47115
- setCurrentStep(step);
47116
49375
  timerRef.current = setTimeout(advance, stepDurationMs);
47117
49376
  }
47118
49377
  };
@@ -47162,7 +49421,7 @@ function SequencerBoard({
47162
49421
  /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "xs", children: [
47163
49422
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { className: "items-center justify-between", children: [
47164
49423
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("sequencer.yourSequence") + ":" }),
47165
- hasFeedback && playState === "idle" && /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "caption", className: "text-muted-foreground", children: [
49424
+ hasFeedback && !isPlayingBack && !isSuccess && /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "caption", className: "text-muted-foreground", children: [
47166
49425
  `${correctCount}/${maxSlots} `,
47167
49426
  "\u2705"
47168
49427
  ] })
@@ -47174,7 +49433,7 @@ function SequencerBoard({
47174
49433
  maxSlots,
47175
49434
  onSlotDrop: handleSlotDrop,
47176
49435
  onSlotRemove: handleSlotRemove,
47177
- playing: playState === "playing",
49436
+ playing: isPlayingBack,
47178
49437
  currentStep,
47179
49438
  categoryColors,
47180
49439
  slotFeedback,
@@ -47182,7 +49441,7 @@ function SequencerBoard({
47182
49441
  }
47183
49442
  )
47184
49443
  ] }),
47185
- playState !== "playing" && /* @__PURE__ */ jsxRuntime.jsx(
49444
+ !isPlayingBack && /* @__PURE__ */ jsxRuntime.jsx(
47186
49445
  ActionPalette,
47187
49446
  {
47188
49447
  actions: availableActions,
@@ -47192,8 +49451,8 @@ function SequencerBoard({
47192
49451
  label: t("sequencer.dragActions")
47193
49452
  }
47194
49453
  ),
47195
- 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) }) }),
47196
- 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") }) }),
49454
+ 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) }) }),
49455
+ 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") }) }),
47197
49456
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", children: [
47198
49457
  /* @__PURE__ */ jsxRuntime.jsx(
47199
49458
  Button,
@@ -48149,9 +50408,9 @@ function VariablePanel({
48149
50408
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("stateArchitect.variables", { name: entityName }) }),
48150
50409
  variables.map((v) => {
48151
50410
  const name = v.name == null ? "" : String(v.name);
48152
- const value = numField(v.value);
48153
- const max = numField(v.max, 100);
48154
- const min = numField(v.min, 0);
50411
+ const value = numField2(v.value);
50412
+ const max = numField2(v.max, 100);
50413
+ const min = numField2(v.min, 0);
48155
50414
  const unit = v.unit == null ? "" : String(v.unit);
48156
50415
  const pct = Math.round((value - min) / (max - min) * 100);
48157
50416
  const isHigh = pct > 80;
@@ -48181,12 +50440,12 @@ function VariablePanel({
48181
50440
  })
48182
50441
  ] });
48183
50442
  }
48184
- var numField;
50443
+ var numField2;
48185
50444
  var init_VariablePanel = __esm({
48186
50445
  "components/game/organisms/puzzles/state-architect/VariablePanel.tsx"() {
48187
50446
  init_atoms2();
48188
50447
  init_cn();
48189
- numField = (v, fallback = 0) => {
50448
+ numField2 = (v, fallback = 0) => {
48190
50449
  const n = Number(v);
48191
50450
  return Number.isFinite(n) ? n : fallback;
48192
50451
  };
@@ -48243,6 +50502,9 @@ function StateArchitectBoard({
48243
50502
  stepDurationMs = 600,
48244
50503
  testEvent,
48245
50504
  completeEvent,
50505
+ addTransitionEvent,
50506
+ removeTransitionEvent,
50507
+ playAgainEvent,
48246
50508
  className
48247
50509
  }) {
48248
50510
  const { emit } = useEventBus();
@@ -48255,14 +50517,16 @@ function StateArchitectBoard({
48255
50517
  const testCases = Array.isArray(resolved?.testCases) ? resolved.testCases : [];
48256
50518
  const entityTransitions = Array.isArray(resolved?.transitions) ? resolved.transitions : [];
48257
50519
  const entityVariables = rows(resolved?.variables);
48258
- const [transitions, setTransitions] = React91.useState(entityTransitions);
50520
+ const transitions = entityTransitions;
50521
+ const attempts = typeof resolved?.attempts === "number" ? resolved.attempts : 0;
50522
+ const entityResult = str(resolved?.result) || "none";
50523
+ const isSuccess = entityResult === "win";
50524
+ const [isTesting, setIsTesting] = React91.useState(false);
48259
50525
  const [headerError, setHeaderError] = React91.useState(false);
48260
- const [playState, setPlayState] = React91.useState("editing");
48261
50526
  const [currentState, setCurrentState] = React91.useState(initialState);
48262
50527
  const [selectedState, setSelectedState] = React91.useState(null);
48263
50528
  const [testResults, setTestResults] = React91.useState([]);
48264
50529
  const [variables, setVariables] = React91.useState(() => [...entityVariables]);
48265
- const [attempts, setAttempts] = React91.useState(0);
48266
50530
  const timerRef = React91.useRef(null);
48267
50531
  const [addingFrom, setAddingFrom] = React91.useState(null);
48268
50532
  React91.useEffect(() => () => {
@@ -48272,7 +50536,7 @@ function StateArchitectBoard({
48272
50536
  const GRAPH_H = 400;
48273
50537
  const positions = React91.useMemo(() => layoutStates(entityStates, GRAPH_W, GRAPH_H), [entityStates]);
48274
50538
  const handleStateClick = React91.useCallback((state) => {
48275
- if (playState !== "editing") return;
50539
+ if (isTesting) return;
48276
50540
  if (addingFrom) {
48277
50541
  if (addingFrom !== state) {
48278
50542
  const event = availableEvents[0] || "EVENT";
@@ -48282,20 +50546,20 @@ function StateArchitectBoard({
48282
50546
  to: state,
48283
50547
  event
48284
50548
  };
48285
- setTransitions((prev) => [...prev, newTrans]);
50549
+ if (addTransitionEvent) emit(`UI:${addTransitionEvent}`, { id: newTrans.id, from: newTrans.from, to: newTrans.to, event: newTrans.event });
48286
50550
  }
48287
50551
  setAddingFrom(null);
48288
50552
  } else {
48289
50553
  setSelectedState(state);
48290
50554
  }
48291
- }, [playState, addingFrom, availableEvents]);
50555
+ }, [isTesting, addingFrom, availableEvents, addTransitionEvent, emit]);
48292
50556
  const handleStartAddTransition = React91.useCallback(() => {
48293
50557
  if (!selectedState) return;
48294
50558
  setAddingFrom(selectedState);
48295
50559
  }, [selectedState]);
48296
50560
  const handleRemoveTransition = React91.useCallback((transId) => {
48297
- setTransitions((prev) => prev.filter((t2) => t2.id !== transId));
48298
- }, []);
50561
+ if (removeTransitionEvent) emit(`UI:${removeTransitionEvent}`, { id: transId });
50562
+ }, [removeTransitionEvent, emit]);
48299
50563
  const machine = React91.useMemo(() => ({
48300
50564
  name: entityName,
48301
50565
  description: str(resolved?.description),
@@ -48309,16 +50573,16 @@ function StateArchitectBoard({
48309
50573
  }))
48310
50574
  }), [entityName, resolved, entityStates, currentState, transitions]);
48311
50575
  const handleTest = React91.useCallback(() => {
48312
- if (playState !== "editing") return;
50576
+ if (isTesting) return;
48313
50577
  if (testEvent) emit(`UI:${testEvent}`, {});
48314
- setPlayState("testing");
50578
+ setIsTesting(true);
48315
50579
  setTestResults([]);
48316
50580
  const results = [];
48317
50581
  let testIdx = 0;
48318
50582
  const runNextTest = () => {
48319
50583
  if (testIdx >= testCases.length) {
48320
50584
  const allPassed = results.every((r2) => r2.passed);
48321
- setPlayState(allPassed ? "success" : "fail");
50585
+ setIsTesting(false);
48322
50586
  setTestResults(results);
48323
50587
  if (allPassed && completeEvent) {
48324
50588
  emit(`UI:${completeEvent}`, {
@@ -48326,9 +50590,6 @@ function StateArchitectBoard({
48326
50590
  passedTests: results.filter((r2) => r2.passed).length
48327
50591
  });
48328
50592
  }
48329
- if (!allPassed) {
48330
- setAttempts((prev) => prev + 1);
48331
- }
48332
50593
  return;
48333
50594
  }
48334
50595
  const testCase = testCases[testIdx];
@@ -48351,24 +50612,23 @@ function StateArchitectBoard({
48351
50612
  timerRef.current = setTimeout(runNextTest, stepDurationMs);
48352
50613
  };
48353
50614
  timerRef.current = setTimeout(runNextTest, stepDurationMs);
48354
- }, [playState, transitions, testCases, initialState, stepDurationMs, testEvent, completeEvent, emit]);
50615
+ }, [isTesting, transitions, testCases, initialState, stepDurationMs, testEvent, completeEvent, emit]);
48355
50616
  const handleTryAgain = React91.useCallback(() => {
48356
50617
  if (timerRef.current) clearTimeout(timerRef.current);
48357
- setPlayState("editing");
50618
+ setIsTesting(false);
48358
50619
  setCurrentState(initialState);
48359
50620
  setTestResults([]);
48360
50621
  }, [initialState]);
48361
50622
  const handleReset = React91.useCallback(() => {
48362
50623
  if (timerRef.current) clearTimeout(timerRef.current);
48363
- setTransitions(entityTransitions);
48364
- setPlayState("editing");
50624
+ setIsTesting(false);
48365
50625
  setCurrentState(initialState);
48366
50626
  setTestResults([]);
48367
50627
  setVariables([...entityVariables]);
48368
50628
  setSelectedState(null);
48369
50629
  setAddingFrom(null);
48370
- setAttempts(0);
48371
- }, [entityTransitions, initialState, entityVariables]);
50630
+ if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
50631
+ }, [initialState, entityVariables, playAgainEvent, emit]);
48372
50632
  const codeData = React91.useMemo(() => ({
48373
50633
  name: entityName,
48374
50634
  states: entityStates,
@@ -48464,7 +50724,7 @@ function StateArchitectBoard({
48464
50724
  ]
48465
50725
  }
48466
50726
  ),
48467
- playState === "editing" && /* @__PURE__ */ jsxRuntime.jsx(HStack, { gap: "sm", children: /* @__PURE__ */ jsxRuntime.jsx(
50727
+ !isTesting && /* @__PURE__ */ jsxRuntime.jsx(HStack, { gap: "sm", children: /* @__PURE__ */ jsxRuntime.jsx(
48468
50728
  Button,
48469
50729
  {
48470
50730
  variant: "ghost",
@@ -48486,7 +50746,7 @@ function StateArchitectBoard({
48486
50746
  t2.guardHint,
48487
50747
  ")"
48488
50748
  ] }),
48489
- playState === "editing" && /* @__PURE__ */ jsxRuntime.jsx(
50749
+ !isTesting && /* @__PURE__ */ jsxRuntime.jsx(
48490
50750
  Button,
48491
50751
  {
48492
50752
  variant: "ghost",
@@ -48518,21 +50778,21 @@ function StateArchitectBoard({
48518
50778
  resolved.showCodeView !== false && /* @__PURE__ */ jsxRuntime.jsx(StateJsonView, { data: codeData, label: "View Code" })
48519
50779
  ] })
48520
50780
  ] }),
48521
- 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") }) }),
48522
- playState === "fail" && /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
50781
+ 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") }) }),
50782
+ !isTesting && !isSuccess && testResults.some((r2) => !r2.passed) && /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
48523
50783
  /* @__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]) }) }),
48524
- 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: [
50784
+ !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: [
48525
50785
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-accent font-bold shrink-0", children: "\u{1F4A1} " + t("game.hint") + ":" }),
48526
50786
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-foreground", children: hint })
48527
50787
  ] }) })
48528
50788
  ] }),
48529
50789
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", children: [
48530
- playState === "fail" ? /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "primary", onClick: handleTryAgain, children: "\u{1F504} " + t("puzzle.tryAgainButton") }) : /* @__PURE__ */ jsxRuntime.jsx(
50790
+ !isTesting && !isSuccess && testResults.some((r2) => !r2.passed) ? /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "primary", onClick: handleTryAgain, children: "\u{1F504} " + t("puzzle.tryAgainButton") }) : /* @__PURE__ */ jsxRuntime.jsx(
48531
50791
  Button,
48532
50792
  {
48533
50793
  variant: "primary",
48534
50794
  onClick: handleTest,
48535
- disabled: playState !== "editing",
50795
+ disabled: isTesting,
48536
50796
  children: "\u25B6 " + t("game.runTests")
48537
50797
  }
48538
50798
  ),
@@ -49038,6 +51298,289 @@ var init_ToastSlot = __esm({
49038
51298
  ToastSlot.displayName = "ToastSlot";
49039
51299
  }
49040
51300
  });
51301
+ function tilesToIso(tiles) {
51302
+ return tiles.map((t) => ({
51303
+ x: t.x,
51304
+ y: t.y,
51305
+ terrain: t.terrain,
51306
+ terrainSprite: t.terrainSprite ?? TERRAIN_SPRITES[t.terrain ?? "ground"],
51307
+ passable: t.passable
51308
+ }));
51309
+ }
51310
+ function towersToUnits(towers) {
51311
+ return towers.map((t) => ({
51312
+ id: t.id,
51313
+ position: { x: t.x, y: t.y },
51314
+ name: "Tower",
51315
+ team: "player",
51316
+ sprite: TOWER_SPRITE,
51317
+ unitType: "guardian",
51318
+ health: 1,
51319
+ maxHealth: 1
51320
+ }));
51321
+ }
51322
+ function creepsToUnits(creeps) {
51323
+ return creeps.map((c) => ({
51324
+ id: c.id,
51325
+ position: { x: c.x, y: c.y },
51326
+ name: "Creep",
51327
+ team: "enemy",
51328
+ sprite: CREEP_SPRITE,
51329
+ unitType: "scrapper",
51330
+ health: c.hp,
51331
+ maxHealth: c.maxHp
51332
+ }));
51333
+ }
51334
+ function pathToFeatures(path) {
51335
+ return path.map((p2, i) => ({
51336
+ id: `path-${i}`,
51337
+ x: p2.x,
51338
+ y: p2.y,
51339
+ type: "path-marker",
51340
+ sprite: `${CDN6}world-map/road_straight.png`
51341
+ }));
51342
+ }
51343
+ function TowerDefenseBoard({
51344
+ entity,
51345
+ tiles: propTiles,
51346
+ path: propPath,
51347
+ towers: propTowers,
51348
+ creeps: propCreeps,
51349
+ gold: propGold,
51350
+ lives: propLives,
51351
+ wave: propWave,
51352
+ maxWaves: propMaxWaves,
51353
+ result: propResult,
51354
+ waveActive: propWaveActive,
51355
+ towerCost = 25,
51356
+ scale = 0.45,
51357
+ placeTowerEvent,
51358
+ startWaveEvent,
51359
+ playAgainEvent,
51360
+ gameEndEvent,
51361
+ className
51362
+ }) {
51363
+ const board = boardEntity(entity) ?? {};
51364
+ const eventBus = useEventBus();
51365
+ const { t } = hooks.useTranslate();
51366
+ const tiles = propTiles ?? rows(board.tiles);
51367
+ const path = propPath ?? rows(board.path);
51368
+ const towers = propTowers ?? rows(board.towers);
51369
+ const creeps = propCreeps ?? rows(board.creeps);
51370
+ const gold = propGold ?? num(board.gold, 100);
51371
+ const lives = propLives ?? num(board.lives, 20);
51372
+ const wave = propWave ?? num(board.wave, 1);
51373
+ const maxWaves = propMaxWaves ?? num(board.maxWaves, 5);
51374
+ const result = propResult ?? (str(board.result) || "none");
51375
+ const waveActive = propWaveActive ?? Boolean(board.waveActive);
51376
+ const [hoveredTile, setHoveredTile] = React91.useState(null);
51377
+ const emittedGameEnd = React91.useRef(false);
51378
+ if (result !== "none" && !emittedGameEnd.current) {
51379
+ emittedGameEnd.current = true;
51380
+ if (gameEndEvent) {
51381
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
51382
+ }
51383
+ }
51384
+ if (result === "none") {
51385
+ emittedGameEnd.current = false;
51386
+ }
51387
+ const towerPositions = React91.useMemo(() => new Set(towers.map((t2) => `${t2.x},${t2.y}`)), [towers]);
51388
+ const pathPositions = React91.useMemo(() => new Set(path.map((p2) => `${p2.x},${p2.y}`)), [path]);
51389
+ const validMoves = React91.useMemo(() => {
51390
+ if (result !== "none" || gold < towerCost) return [];
51391
+ return tiles.filter(
51392
+ (t2) => t2.passable !== false && !towerPositions.has(`${t2.x},${t2.y}`) && !pathPositions.has(`${t2.x},${t2.y}`)
51393
+ ).map((t2) => ({ x: t2.x, y: t2.y }));
51394
+ }, [tiles, towerPositions, pathPositions, result, gold, towerCost]);
51395
+ const isoTiles = React91.useMemo(() => tilesToIso(tiles), [tiles]);
51396
+ const towerUnits = React91.useMemo(() => towersToUnits(towers), [towers]);
51397
+ const creepUnits = React91.useMemo(() => creepsToUnits(creeps), [creeps]);
51398
+ const isoUnits = React91.useMemo(() => [...towerUnits, ...creepUnits], [towerUnits, creepUnits]);
51399
+ const pathFeatures = React91.useMemo(() => pathToFeatures(path), [path]);
51400
+ const handleTileClick = React91.useCallback((x, y) => {
51401
+ if (result !== "none") return;
51402
+ if (pathPositions.has(`${x},${y}`)) return;
51403
+ if (towerPositions.has(`${x},${y}`)) return;
51404
+ if (gold < towerCost) return;
51405
+ if (placeTowerEvent) {
51406
+ eventBus.emit(`UI:${placeTowerEvent}`, { x, y });
51407
+ }
51408
+ }, [result, pathPositions, towerPositions, gold, towerCost, placeTowerEvent, eventBus]);
51409
+ const handleStartWave = React91.useCallback(() => {
51410
+ if (startWaveEvent) {
51411
+ eventBus.emit(`UI:${startWaveEvent}`, { wave });
51412
+ }
51413
+ }, [startWaveEvent, wave, eventBus]);
51414
+ const handlePlayAgain = React91.useCallback(() => {
51415
+ if (playAgainEvent) {
51416
+ eventBus.emit(`UI:${playAgainEvent}`, {});
51417
+ }
51418
+ }, [playAgainEvent, eventBus]);
51419
+ const canPlaceTower = gold >= towerCost && result === "none";
51420
+ const canStartWave = !waveActive && result === "none";
51421
+ return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { className: cn("tower-defense-board relative min-h-[600px] bg-background", className), gap: "none", children: [
51422
+ /* @__PURE__ */ jsxRuntime.jsxs(
51423
+ HStack,
51424
+ {
51425
+ className: "px-4 py-2 border-b border-border bg-surface",
51426
+ gap: "lg",
51427
+ align: "center",
51428
+ children: [
51429
+ /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", align: "center", children: [
51430
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", color: "muted", children: t("td.gold") ?? "Gold" }),
51431
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", weight: "bold", className: "text-warning", children: gold })
51432
+ ] }),
51433
+ /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", align: "center", children: [
51434
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", color: "muted", children: t("td.lives") ?? "Lives" }),
51435
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", weight: "bold", className: lives <= 5 ? "text-error" : "text-success", children: lives })
51436
+ ] }),
51437
+ /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", align: "center", children: [
51438
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", color: "muted", children: t("td.wave") ?? "Wave" }),
51439
+ /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "body2", weight: "bold", children: [
51440
+ wave,
51441
+ " / ",
51442
+ maxWaves
51443
+ ] })
51444
+ ] }),
51445
+ /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "ml-auto", children: [
51446
+ canStartWave && /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "primary", size: "sm", onClick: handleStartWave, children: t("td.startWave") ?? "Start Wave" }),
51447
+ waveActive && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", className: "text-warning animate-pulse", children: t("td.waveInProgress") ?? "Wave in progress\u2026" })
51448
+ ] })
51449
+ ]
51450
+ }
51451
+ ),
51452
+ /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "relative flex-1", children: [
51453
+ /* @__PURE__ */ jsxRuntime.jsx(
51454
+ IsometricCanvas_default,
51455
+ {
51456
+ tiles: isoTiles,
51457
+ units: isoUnits,
51458
+ features: pathFeatures,
51459
+ validMoves,
51460
+ hoveredTile,
51461
+ onTileClick: handleTileClick,
51462
+ onTileHover: (x, y) => setHoveredTile({ x, y }),
51463
+ onTileLeave: () => setHoveredTile(null),
51464
+ scale
51465
+ }
51466
+ ),
51467
+ 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)` }) })
51468
+ ] }),
51469
+ 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: [
51470
+ /* @__PURE__ */ jsxRuntime.jsx(
51471
+ Typography,
51472
+ {
51473
+ variant: "h2",
51474
+ className: cn(
51475
+ "text-4xl font-black tracking-widest uppercase",
51476
+ result === "won" ? "text-warning" : "text-error"
51477
+ ),
51478
+ children: result === "won" ? t("td.victory") ?? "Victory!" : t("td.defeat") ?? "Defeat!"
51479
+ }
51480
+ ),
51481
+ /* @__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." }),
51482
+ /* @__PURE__ */ jsxRuntime.jsx(
51483
+ Button,
51484
+ {
51485
+ variant: "primary",
51486
+ className: "px-8 py-3 font-semibold",
51487
+ onClick: handlePlayAgain,
51488
+ children: t("td.playAgain") ?? "Play Again"
51489
+ }
51490
+ )
51491
+ ] }) })
51492
+ ] });
51493
+ }
51494
+ var CDN6, TERRAIN_SPRITES, TOWER_SPRITE, CREEP_SPRITE;
51495
+ var init_TowerDefenseBoard = __esm({
51496
+ "components/game/organisms/TowerDefenseBoard.tsx"() {
51497
+ "use client";
51498
+ init_cn();
51499
+ init_useEventBus();
51500
+ init_Box();
51501
+ init_Button();
51502
+ init_Typography();
51503
+ init_Stack();
51504
+ init_IsometricCanvas();
51505
+ init_boardEntity();
51506
+ CDN6 = "https://almadar-kflow-assets.web.app/shared/";
51507
+ TERRAIN_SPRITES = {
51508
+ ground: `${CDN6}isometric-blocks/PNG/Platformer tiles/platformerTile_01.png`,
51509
+ path: `${CDN6}isometric-blocks/PNG/Platformer tiles/platformerTile_09.png`,
51510
+ wall: `${CDN6}isometric-blocks/PNG/Platformer tiles/platformerTile_05.png`
51511
+ };
51512
+ TOWER_SPRITE = `${CDN6}units/guardian.png`;
51513
+ CREEP_SPRITE = `${CDN6}units/scrapper.png`;
51514
+ TowerDefenseBoard.displayName = "TowerDefenseBoard";
51515
+ }
51516
+ });
51517
+ function TowerDefenseTemplate({
51518
+ entity,
51519
+ title = "Tower Defense",
51520
+ tiles,
51521
+ path,
51522
+ towers,
51523
+ gold,
51524
+ lives,
51525
+ maxWaves,
51526
+ towerCost,
51527
+ scale,
51528
+ placeTowerEvent,
51529
+ startWaveEvent,
51530
+ playAgainEvent,
51531
+ gameEndEvent,
51532
+ className
51533
+ }) {
51534
+ const resolved = entity && typeof entity === "object" && !Array.isArray(entity) ? entity : void 0;
51535
+ return /* @__PURE__ */ jsxRuntime.jsxs(
51536
+ Box,
51537
+ {
51538
+ display: "flex",
51539
+ fullHeight: true,
51540
+ className: cn("tower-defense-template flex-col", className),
51541
+ children: [
51542
+ /* @__PURE__ */ jsxRuntime.jsx(
51543
+ HStack,
51544
+ {
51545
+ gap: "sm",
51546
+ align: "center",
51547
+ className: "px-4 py-3 border-b-2 border-border bg-surface shrink-0",
51548
+ children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title })
51549
+ }
51550
+ ),
51551
+ /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "flex-1 relative overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx(
51552
+ TowerDefenseBoard,
51553
+ {
51554
+ entity: resolved,
51555
+ tiles,
51556
+ path,
51557
+ towers,
51558
+ gold,
51559
+ lives,
51560
+ maxWaves,
51561
+ towerCost,
51562
+ scale,
51563
+ placeTowerEvent,
51564
+ startWaveEvent,
51565
+ playAgainEvent,
51566
+ gameEndEvent,
51567
+ className: "h-full"
51568
+ }
51569
+ ) })
51570
+ ]
51571
+ }
51572
+ );
51573
+ }
51574
+ var init_TowerDefenseTemplate = __esm({
51575
+ "components/game/templates/TowerDefenseTemplate.tsx"() {
51576
+ init_cn();
51577
+ init_Box();
51578
+ init_Stack();
51579
+ init_Typography();
51580
+ init_TowerDefenseBoard();
51581
+ TowerDefenseTemplate.displayName = "TowerDefenseTemplate";
51582
+ }
51583
+ });
49041
51584
  function useBattleState(initialUnits, eventConfig = {}, callbacks = {}) {
49042
51585
  const eventBus = useEventBus();
49043
51586
  const {
@@ -49236,17 +51779,12 @@ var init_UncontrolledBattleBoard = __esm({
49236
51779
  }
49237
51780
  });
49238
51781
  function heroPosition(h) {
51782
+ if ("position" in h && h.position != null) return h.position;
49239
51783
  return vec2(h.position);
49240
51784
  }
49241
- function heroOwner(h) {
49242
- return str(h.owner);
49243
- }
49244
51785
  function heroMovement(h) {
49245
51786
  return num(h.movement);
49246
51787
  }
49247
- function hexPassable(h) {
49248
- return h.passable !== false;
49249
- }
49250
51788
  function defaultIsInRange(from, to, range) {
49251
51789
  return Math.abs(from.x - to.x) + Math.abs(from.y - to.y) <= range;
49252
51790
  }
@@ -49282,38 +51820,41 @@ function WorldMapBoard({
49282
51820
  }) {
49283
51821
  const eventBus = useEventBus();
49284
51822
  const resolved = boardEntity(entity);
49285
- const hexes = rows(resolved?.hexes);
49286
- const heroes = rows(resolved?.heroes);
51823
+ const entityUnits = rows(resolved?.units);
51824
+ const entityTiles = rows(resolved?.tiles);
49287
51825
  const features = propFeatures ?? (Array.isArray(resolved?.features) ? resolved.features : []);
49288
51826
  const selectedHeroId = resolved?.selectedHeroId ?? null;
49289
51827
  const assetManifest = propAssetManifest ?? resolved?.assetManifest;
49290
51828
  const backgroundImage = resolved?.backgroundImage;
49291
51829
  const [hoveredTile, setHoveredTile] = React91.useState(null);
49292
- const selectedHero = React91.useMemo(
49293
- () => heroes.find((h) => str(h.id) === selectedHeroId) ?? null,
49294
- [heroes, selectedHeroId]
49295
- );
49296
51830
  const derivedTiles = React91.useMemo(
49297
- () => hexes.map((hex) => ({
49298
- x: num(hex.x),
49299
- y: num(hex.y),
49300
- terrain: str(hex.terrain),
49301
- terrainSprite: hex.terrainSprite == null ? void 0 : str(hex.terrainSprite)
51831
+ () => entityTiles.map((t) => ({
51832
+ x: num(t.x),
51833
+ y: num(t.y),
51834
+ terrain: str(t.terrain),
51835
+ terrainSprite: t.terrainSprite == null ? void 0 : str(t.terrainSprite),
51836
+ passable: t.passable !== false
49302
51837
  })),
49303
- [hexes]
51838
+ [entityTiles]
49304
51839
  );
49305
51840
  const tiles = propTiles ?? derivedTiles;
49306
51841
  const baseUnits = React91.useMemo(
49307
- () => propUnits ?? heroes.map((hero) => ({
49308
- id: str(hero.id),
49309
- position: heroPosition(hero),
49310
- name: str(hero.name),
49311
- team: heroOwner(hero) === "enemy" ? "enemy" : "player",
49312
- health: 100,
49313
- maxHealth: 100,
49314
- sprite: hero.sprite == null ? void 0 : str(hero.sprite)
51842
+ () => propUnits ?? entityUnits.map((u) => ({
51843
+ id: str(u.id),
51844
+ position: heroPosition(u),
51845
+ name: str(u.name),
51846
+ // lolo uses `team` field (not `owner`)
51847
+ team: str(u.team) === "enemy" ? "enemy" : "player",
51848
+ health: num(u.health) || 100,
51849
+ maxHealth: num(u.maxHealth) || 100,
51850
+ sprite: u.sprite == null ? void 0 : str(u.sprite)
49315
51851
  })),
49316
- [heroes, propUnits]
51852
+ [entityUnits, propUnits]
51853
+ );
51854
+ const gameUnits = baseUnits;
51855
+ const selectedHero = React91.useMemo(
51856
+ () => gameUnits.find((u) => u.id === selectedHeroId) ?? null,
51857
+ [gameUnits, selectedHeroId]
49317
51858
  );
49318
51859
  const MOVE_SPEED_MS_PER_TILE = 300;
49319
51860
  const movementAnimRef = React91.useRef(null);
@@ -49359,51 +51900,50 @@ function WorldMapBoard({
49359
51900
  const validMoves = React91.useMemo(() => {
49360
51901
  if (!selectedHero || heroMovement(selectedHero) <= 0) return [];
49361
51902
  const sp = heroPosition(selectedHero);
49362
- const sOwner = heroOwner(selectedHero);
51903
+ const sTeam = str(selectedHero.team);
49363
51904
  const range = heroMovement(selectedHero);
49364
51905
  const moves = [];
49365
- hexes.forEach((hex) => {
49366
- const hx = num(hex.x);
49367
- const hy = num(hex.y);
49368
- if (!hexPassable(hex)) return;
49369
- if (hx === sp.x && hy === sp.y) return;
49370
- if (!isInRange(sp, { x: hx, y: hy }, range)) return;
49371
- if (heroes.some((h) => {
49372
- const hp = heroPosition(h);
49373
- return hp.x === hx && hp.y === hy && heroOwner(h) === sOwner;
51906
+ tiles.forEach((t) => {
51907
+ const tx = t.x;
51908
+ const ty = t.y;
51909
+ if (t.passable === false) return;
51910
+ if (tx === sp.x && ty === sp.y) return;
51911
+ if (!isInRange(sp, { x: tx, y: ty }, range)) return;
51912
+ if (gameUnits.some((u) => {
51913
+ const up = u.position ?? { x: u.x ?? -1, y: u.y ?? -1 };
51914
+ return up.x === tx && up.y === ty && str(u.team) === sTeam;
49374
51915
  })) return;
49375
- moves.push({ x: hx, y: hy });
51916
+ moves.push({ x: tx, y: ty });
49376
51917
  });
49377
51918
  return moves;
49378
- }, [selectedHero, hexes, heroes, isInRange]);
51919
+ }, [selectedHero, tiles, gameUnits, isInRange]);
49379
51920
  const attackTargets = React91.useMemo(() => {
49380
51921
  if (!selectedHero || heroMovement(selectedHero) <= 0) return [];
49381
51922
  const sp = heroPosition(selectedHero);
49382
- const sOwner = heroOwner(selectedHero);
51923
+ const sTeam = str(selectedHero.team);
49383
51924
  const range = heroMovement(selectedHero);
49384
- return heroes.filter((h) => heroOwner(h) !== sOwner).filter((h) => isInRange(sp, heroPosition(h), range)).map((h) => heroPosition(h));
49385
- }, [selectedHero, heroes, isInRange]);
49386
- const maxY = Math.max(...hexes.map((h) => num(h.y)), 0);
51925
+ 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 });
51926
+ }, [selectedHero, gameUnits, isInRange]);
51927
+ const maxY = Math.max(...tiles.map((t) => t.y), 0);
49387
51928
  const baseOffsetX = (maxY + 1) * (TILE_WIDTH * scale / 2);
49388
51929
  const tileToScreen = React91.useCallback(
49389
51930
  (tx, ty) => isoToScreen(tx, ty, scale, baseOffsetX),
49390
51931
  [scale, baseOffsetX]
49391
51932
  );
49392
51933
  const hoveredHex = React91.useMemo(
49393
- () => hoveredTile ? hexes.find((h) => num(h.x) === hoveredTile.x && num(h.y) === hoveredTile.y) ?? null : null,
49394
- [hoveredTile, hexes]
51934
+ () => hoveredTile ? tiles.find((t) => t.x === hoveredTile.x && t.y === hoveredTile.y) ?? null : null,
51935
+ [hoveredTile, tiles]
49395
51936
  );
49396
51937
  const hoveredHero = React91.useMemo(
49397
- () => hoveredTile ? heroes.find((h) => {
49398
- const hp = heroPosition(h);
49399
- return hp.x === hoveredTile.x && hp.y === hoveredTile.y;
51938
+ () => hoveredTile ? gameUnits.find((u) => {
51939
+ const up = u.position ?? { x: u.x ?? -1, y: u.y ?? -1 };
51940
+ return up.x === hoveredTile.x && up.y === hoveredTile.y;
49400
51941
  }) ?? null : null,
49401
- [hoveredTile, heroes]
51942
+ [hoveredTile, gameUnits]
49402
51943
  );
49403
51944
  const handleTileClick = React91.useCallback((x, y) => {
49404
51945
  if (movementAnimRef.current) return;
49405
- const hex = hexes.find((h) => num(h.x) === x && num(h.y) === y);
49406
- if (!hex) return;
51946
+ const tile = tiles.find((t) => t.x === x && t.y === y);
49407
51947
  if (tileClickEvent) {
49408
51948
  eventBus.emit(`UI:${tileClickEvent}`, { x, y });
49409
51949
  }
@@ -49414,38 +51954,39 @@ function WorldMapBoard({
49414
51954
  if (heroMoveEvent) {
49415
51955
  eventBus.emit(`UI:${heroMoveEvent}`, { heroId, toX: x, toY: y });
49416
51956
  }
49417
- const feature = str(hex.feature);
51957
+ const feature = tile ? str(tile.feature) : "";
49418
51958
  if (feature && feature !== "none") {
49419
- onFeatureEnter?.(heroId, hex);
51959
+ const tileRow = tile;
51960
+ onFeatureEnter?.(heroId, tileRow);
49420
51961
  if (featureEnterEvent) {
49421
- eventBus.emit(`UI:${featureEnterEvent}`, { heroId, feature, hex });
51962
+ eventBus.emit(`UI:${featureEnterEvent}`, { heroId, feature, hex: tileRow });
49422
51963
  }
49423
51964
  }
49424
51965
  });
49425
51966
  return;
49426
51967
  }
49427
- const enemy = heroes.find((h) => {
49428
- const hp = heroPosition(h);
49429
- return hp.x === x && hp.y === y && heroOwner(h) === "enemy";
51968
+ const enemy = gameUnits.find((u) => {
51969
+ const up = u.position ?? { x: u.x ?? -1, y: u.y ?? -1 };
51970
+ return up.x === x && up.y === y && str(u.team) === "enemy";
49430
51971
  });
49431
51972
  if (selectedHero && enemy && attackTargets.some((t) => t.x === x && t.y === y)) {
49432
51973
  const attackerId = str(selectedHero.id);
49433
- const defenderId = str(enemy.id);
51974
+ const defenderId = enemy.id;
49434
51975
  onBattleEncounter?.(attackerId, defenderId);
49435
51976
  if (battleEncounterEvent) {
49436
51977
  eventBus.emit(`UI:${battleEncounterEvent}`, { attackerId, defenderId });
49437
51978
  }
49438
51979
  }
49439
- }, [hexes, heroes, selectedHero, validMoves, attackTargets, startMoveAnimation, onHeroMove, onFeatureEnter, onBattleEncounter, eventBus, tileClickEvent, heroMoveEvent, featureEnterEvent, battleEncounterEvent]);
51980
+ }, [tiles, gameUnits, selectedHero, validMoves, attackTargets, startMoveAnimation, onHeroMove, onFeatureEnter, onBattleEncounter, eventBus, tileClickEvent, heroMoveEvent, featureEnterEvent, battleEncounterEvent]);
49440
51981
  const handleUnitClick = React91.useCallback((unitId) => {
49441
- const hero = heroes.find((h) => str(h.id) === unitId);
49442
- if (hero && (heroOwner(hero) === "player" || allowMoveAllHeroes)) {
51982
+ const unit = gameUnits.find((u) => u.id === unitId);
51983
+ if (unit && (str(unit.team) === "player" || allowMoveAllHeroes)) {
49443
51984
  onHeroSelect?.(unitId);
49444
51985
  if (heroSelectEvent) {
49445
51986
  eventBus.emit(`UI:${heroSelectEvent}`, { heroId: unitId });
49446
51987
  }
49447
51988
  }
49448
- }, [heroes, onHeroSelect, allowMoveAllHeroes, eventBus, heroSelectEvent]);
51989
+ }, [gameUnits, onHeroSelect, allowMoveAllHeroes, eventBus, heroSelectEvent]);
49449
51990
  const selectHero = React91.useCallback((id) => {
49450
51991
  onHeroSelect?.(id);
49451
51992
  if (heroSelectEvent) {
@@ -49549,48 +52090,48 @@ function WorldMapTemplate({
49549
52090
  }
49550
52091
  );
49551
52092
  }
49552
- var CDN3, DEFAULT_WORLDMAP_TILES, DEFAULT_WORLDMAP_UNITS, DEFAULT_WORLDMAP_FEATURES, DEFAULT_WORLDMAP_MANIFEST;
52093
+ var CDN7, DEFAULT_WORLDMAP_TILES, DEFAULT_WORLDMAP_UNITS, DEFAULT_WORLDMAP_FEATURES, DEFAULT_WORLDMAP_MANIFEST;
49553
52094
  var init_WorldMapTemplate = __esm({
49554
52095
  "components/game/templates/WorldMapTemplate.tsx"() {
49555
52096
  init_WorldMapBoard();
49556
- CDN3 = "https://almadar-kflow-assets.web.app/shared";
52097
+ CDN7 = "https://almadar-kflow-assets.web.app/shared";
49557
52098
  DEFAULT_WORLDMAP_TILES = [
49558
- { x: 0, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49559
- { x: 1, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49560
- { x: 2, y: 0, terrain: "water", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
49561
- { x: 3, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49562
- { x: 4, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49563
- { x: 0, y: 1, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49564
- { x: 1, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49565
- { x: 2, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49566
- { x: 3, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49567
- { x: 4, y: 1, terrain: "water", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
49568
- { x: 0, y: 2, terrain: "water", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
49569
- { x: 1, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49570
- { x: 2, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
49571
- { x: 3, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49572
- { x: 4, y: 2, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49573
- { x: 0, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49574
- { x: 1, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49575
- { x: 2, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49576
- { x: 3, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
49577
- { x: 4, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49578
- { x: 0, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49579
- { x: 1, y: 4, terrain: "water", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
49580
- { x: 2, y: 4, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49581
- { x: 3, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49582
- { x: 4, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` }
52099
+ { x: 0, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
52100
+ { x: 1, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
52101
+ { x: 2, y: 0, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
52102
+ { x: 3, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
52103
+ { x: 4, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
52104
+ { x: 0, y: 1, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
52105
+ { x: 1, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
52106
+ { x: 2, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
52107
+ { x: 3, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
52108
+ { x: 4, y: 1, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
52109
+ { x: 0, y: 2, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
52110
+ { x: 1, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
52111
+ { x: 2, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
52112
+ { x: 3, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
52113
+ { x: 4, y: 2, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
52114
+ { x: 0, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
52115
+ { x: 1, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
52116
+ { x: 2, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
52117
+ { x: 3, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
52118
+ { x: 4, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
52119
+ { x: 0, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
52120
+ { x: 1, y: 4, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
52121
+ { x: 2, y: 4, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
52122
+ { x: 3, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
52123
+ { x: 4, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` }
49583
52124
  ];
49584
52125
  DEFAULT_WORLDMAP_UNITS = [
49585
- { 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` },
49586
- { 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` }
52126
+ { 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` },
52127
+ { 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` }
49587
52128
  ];
49588
52129
  DEFAULT_WORLDMAP_FEATURES = [
49589
- { id: "f1", x: 2, y: 2, type: "capital", sprite: `${CDN3}/scenes/world/capital.png` },
49590
- { id: "f2", x: 4, y: 2, type: "power_node", sprite: `${CDN3}/world-map/power_node.png` }
52130
+ { id: "f1", x: 2, y: 2, type: "capital", sprite: `${CDN7}/scenes/world/capital.png` },
52131
+ { id: "f2", x: 4, y: 2, type: "power_node", sprite: `${CDN7}/world-map/power_node.png` }
49591
52132
  ];
49592
52133
  DEFAULT_WORLDMAP_MANIFEST = {
49593
- baseUrl: CDN3,
52134
+ baseUrl: CDN7,
49594
52135
  terrains: {
49595
52136
  grass: "/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png",
49596
52137
  water: "/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png",
@@ -49635,7 +52176,7 @@ function lazyThree(name, loader) {
49635
52176
  ThreeWrapper.displayName = `Lazy(${name})`;
49636
52177
  return ThreeWrapper;
49637
52178
  }
49638
- var ThreeBoundary, FeatureRenderer, GameCanvas3D, GameCanvas3DBattleTemplate, GameCanvas3DCastleTemplate, GameCanvas3DWorldMapTemplate, COMPONENT_REGISTRY;
52179
+ var ThreeBoundary, FeatureRenderer, GameCanvas3D2, GameCanvas3DBattleTemplate, GameCanvas3DCastleTemplate, GameCanvas3DWorldMapTemplate, COMPONENT_REGISTRY;
49639
52180
  var init_component_registry_generated = __esm({
49640
52181
  "components/core/organisms/component-registry.generated.ts"() {
49641
52182
  init_AboutPageTemplate();
@@ -49745,6 +52286,7 @@ var init_component_registry_generated = __esm({
49745
52286
  init_FormSectionHeader();
49746
52287
  init_GameAudioProvider();
49747
52288
  init_GameAudioToggle();
52289
+ init_GameBoard3D();
49748
52290
  init_GameCanvas2D();
49749
52291
  init_GameHud();
49750
52292
  init_GameMenu();
@@ -49802,7 +52344,9 @@ var init_component_registry_generated = __esm({
49802
52344
  init_PageHeader();
49803
52345
  init_Pagination();
49804
52346
  init_PatternTile();
52347
+ init_PlatformerBoard();
49805
52348
  init_PlatformerCanvas();
52349
+ init_PlatformerTemplate();
49806
52350
  init_Popover();
49807
52351
  init_PositionedCanvas();
49808
52352
  init_PowerupSlots();
@@ -49826,6 +52370,8 @@ var init_component_registry_generated = __esm({
49826
52370
  init_ResourceBar();
49827
52371
  init_ResourceCounter();
49828
52372
  init_RichBlockEditor();
52373
+ init_RoguelikeBoard();
52374
+ init_RoguelikeTemplate();
49829
52375
  init_RuntimeDebugger2();
49830
52376
  init_ScaledDiagram();
49831
52377
  init_ScoreBoard();
@@ -49898,6 +52444,8 @@ var init_component_registry_generated = __esm({
49898
52444
  init_Toast();
49899
52445
  init_ToastSlot();
49900
52446
  init_Tooltip();
52447
+ init_TowerDefenseBoard();
52448
+ init_TowerDefenseTemplate();
49901
52449
  init_TraitFrame();
49902
52450
  init_TraitSlot();
49903
52451
  init_TrendIndicator();
@@ -49942,7 +52490,7 @@ var init_component_registry_generated = __esm({
49942
52490
  }
49943
52491
  };
49944
52492
  FeatureRenderer = lazyThree("FeatureRenderer", () => import('@almadar/ui/components/molecules/game/three'));
49945
- GameCanvas3D = lazyThree("GameCanvas3D", () => import('@almadar/ui/components/molecules/game/three'));
52493
+ GameCanvas3D2 = lazyThree("GameCanvas3D", () => import('@almadar/ui/components/molecules/game/three'));
49946
52494
  GameCanvas3DBattleTemplate = lazyThree("GameCanvas3DBattleTemplate", () => import('@almadar/ui/components/molecules/game/three'));
49947
52495
  GameCanvas3DCastleTemplate = lazyThree("GameCanvas3DCastleTemplate", () => import('@almadar/ui/components/molecules/game/three'));
49948
52496
  GameCanvas3DWorldMapTemplate = lazyThree("GameCanvas3DWorldMapTemplate", () => import('@almadar/ui/components/molecules/game/three'));
@@ -50061,8 +52609,9 @@ var init_component_registry_generated = __esm({
50061
52609
  "FormSectionHeader": FormSectionHeader,
50062
52610
  "GameAudioProvider": GameAudioProvider,
50063
52611
  "GameAudioToggle": GameAudioToggle,
52612
+ "GameBoard3D": GameBoard3D,
50064
52613
  "GameCanvas2D": GameCanvas2D,
50065
- "GameCanvas3D": GameCanvas3D,
52614
+ "GameCanvas3D": GameCanvas3D2,
50066
52615
  "GameCanvas3DBattleTemplate": GameCanvas3DBattleTemplate,
50067
52616
  "GameCanvas3DCastleTemplate": GameCanvas3DCastleTemplate,
50068
52617
  "GameCanvas3DWorldMapTemplate": GameCanvas3DWorldMapTemplate,
@@ -50124,7 +52673,9 @@ var init_component_registry_generated = __esm({
50124
52673
  "PageHeader": PageHeader,
50125
52674
  "Pagination": Pagination,
50126
52675
  "PatternTile": PatternTile,
52676
+ "PlatformerBoard": PlatformerBoard,
50127
52677
  "PlatformerCanvas": PlatformerCanvas,
52678
+ "PlatformerTemplate": PlatformerTemplate,
50128
52679
  "Popover": Popover,
50129
52680
  "PositionedCanvas": PositionedCanvas,
50130
52681
  "PowerupSlots": PowerupSlots,
@@ -50148,6 +52699,8 @@ var init_component_registry_generated = __esm({
50148
52699
  "ResourceBar": ResourceBar,
50149
52700
  "ResourceCounter": ResourceCounter,
50150
52701
  "RichBlockEditor": RichBlockEditor,
52702
+ "RoguelikeBoard": RoguelikeBoard,
52703
+ "RoguelikeTemplate": RoguelikeTemplate,
50151
52704
  "RuntimeDebugger": RuntimeDebugger,
50152
52705
  "ScaledDiagram": ScaledDiagram,
50153
52706
  "ScoreBoard": ScoreBoard,
@@ -50223,6 +52776,8 @@ var init_component_registry_generated = __esm({
50223
52776
  "Toast": Toast,
50224
52777
  "ToastSlot": ToastSlot,
50225
52778
  "Tooltip": Tooltip,
52779
+ "TowerDefenseBoard": TowerDefenseBoard,
52780
+ "TowerDefenseTemplate": TowerDefenseTemplate,
50226
52781
  "TraitFrame": TraitFrame,
50227
52782
  "TraitSlot": TraitSlot,
50228
52783
  "TrendIndicator": TrendIndicator,
@@ -54709,7 +57264,7 @@ function convertObjectProps(props) {
54709
57264
  function convertFnFormLambdasInProps(props) {
54710
57265
  return convertObjectProps(props);
54711
57266
  }
54712
- var log9 = logger.createLogger("almadar:ui:effects:client-handlers");
57267
+ var log11 = logger.createLogger("almadar:ui:effects:client-handlers");
54713
57268
  function createClientEffectHandlers(options) {
54714
57269
  const { eventBus, slotSetter, navigate, notify, callService } = options;
54715
57270
  return {
@@ -54718,10 +57273,10 @@ function createClientEffectHandlers(options) {
54718
57273
  eventBus.emit(prefixedEvent, payload);
54719
57274
  },
54720
57275
  persist: async () => {
54721
- log9.warn("persist is server-side only, ignored on client");
57276
+ log11.warn("persist is server-side only, ignored on client");
54722
57277
  },
54723
57278
  set: () => {
54724
- log9.warn("set is server-side only, ignored on client");
57279
+ log11.warn("set is server-side only, ignored on client");
54725
57280
  },
54726
57281
  callService: async (service, action, params) => {
54727
57282
  if (callService) return callService(service, action, params);
@@ -54750,10 +57305,10 @@ function createClientEffectHandlers(options) {
54750
57305
  slotSetter.addPattern(slot, pattern, props);
54751
57306
  },
54752
57307
  navigate: navigate ?? ((path) => {
54753
- log9.warn("No navigate handler, ignoring", { path });
57308
+ log11.warn("No navigate handler, ignoring", { path });
54754
57309
  }),
54755
57310
  notify: notify ?? ((msg, type) => {
54756
- log9.debug("notify", { type, message: msg });
57311
+ log11.debug("notify", { type, message: msg });
54757
57312
  })
54758
57313
  };
54759
57314
  }
@@ -55918,7 +58473,7 @@ OrbitalThemeProvider.displayName = "OrbitalThemeProvider";
55918
58473
  init_navigation();
55919
58474
  init_verificationRegistry();
55920
58475
  var PERF_NAMESPACE = "almadar:perf:canvas";
55921
- var log10 = logger.createLogger(PERF_NAMESPACE);
58476
+ var log12 = logger.createLogger(PERF_NAMESPACE);
55922
58477
  var RING_SIZE = 50;
55923
58478
  var ring = [];
55924
58479
  var writeIdx = 0;
@@ -55969,7 +58524,7 @@ function perfEnd(name, startToken, detail) {
55969
58524
  }
55970
58525
  }
55971
58526
  push({ name, durationMs, ts: endTs, detail });
55972
- log10.debug(name, () => ({ durationMs, ...detail ?? {} }));
58527
+ log12.debug(name, () => ({ durationMs, ...detail ?? {} }));
55973
58528
  }
55974
58529
  var profilerOnRender = (id, phase, actualDuration, baseDuration, _startTime, commitTime) => {
55975
58530
  if (!isEnabled()) return;
@@ -55979,7 +58534,7 @@ var profilerOnRender = (id, phase, actualDuration, baseDuration, _startTime, com
55979
58534
  ts: commitTime,
55980
58535
  detail: { baseDuration }
55981
58536
  });
55982
- log10.debug(`profiler:${id}:${phase}`, () => ({ actualDuration, baseDuration }));
58537
+ log12.debug(`profiler:${id}:${phase}`, () => ({ actualDuration, baseDuration }));
55983
58538
  };
55984
58539
 
55985
58540
  // runtime/prepareSchemaForPreview.ts
@@ -56622,7 +59177,7 @@ init_useEventBus();
56622
59177
  // components/avl/molecules/useCanvasDnd.tsx
56623
59178
  init_useEventBus();
56624
59179
  init_useAlmadarDndCollision();
56625
- var log11 = logger.createLogger("almadar:ui:canvas-dnd");
59180
+ var log13 = logger.createLogger("almadar:ui:canvas-dnd");
56626
59181
  function useCanvasDraggable({
56627
59182
  id,
56628
59183
  payload,
@@ -56661,7 +59216,7 @@ function defaultEmit(eventBus, drop) {
56661
59216
  if (payload.kind === "pattern") {
56662
59217
  const patternType = payload.data.type;
56663
59218
  if (typeof patternType !== "string") {
56664
- log11.warn("default-emit:pattern:missing-type");
59219
+ log13.warn("default-emit:pattern:missing-type");
56665
59220
  return;
56666
59221
  }
56667
59222
  const out = { patternType, containerNode: target.containerNode };
@@ -56670,23 +59225,23 @@ function defaultEmit(eventBus, drop) {
56670
59225
  out.index = resolved.index;
56671
59226
  }
56672
59227
  eventBus.emit("UI:PATTERN_DROP", out);
56673
- log11.info("default-emit:pattern", { patternType, level: target.level });
59228
+ log13.info("default-emit:pattern", { patternType, level: target.level });
56674
59229
  return;
56675
59230
  }
56676
59231
  if (payload.kind === "behavior") {
56677
59232
  const behaviorName = payload.data.name;
56678
59233
  if (typeof behaviorName !== "string") {
56679
- log11.warn("default-emit:behavior:missing-name");
59234
+ log13.warn("default-emit:behavior:missing-name");
56680
59235
  return;
56681
59236
  }
56682
59237
  eventBus.emit("UI:BEHAVIOR_DROP", {
56683
59238
  behaviorName,
56684
59239
  containerNode: target.containerNode
56685
59240
  });
56686
- log11.info("default-emit:behavior", { behaviorName, level: target.level });
59241
+ log13.info("default-emit:behavior", { behaviorName, level: target.level });
56687
59242
  return;
56688
59243
  }
56689
- log11.debug("default-emit:unhandled-kind", { kind: payload.kind });
59244
+ log13.debug("default-emit:unhandled-kind", { kind: payload.kind });
56690
59245
  }
56691
59246
  function CanvasDndProvider({
56692
59247
  children,
@@ -56702,9 +59257,9 @@ function CanvasDndProvider({
56702
59257
  if (payload) {
56703
59258
  setActivePayload(payload);
56704
59259
  eventBus.emit("UI:DRAG_START", { kind: payload.kind, data: payload.data });
56705
- log11.info("dragStart", { id: e.active.id, kind: payload.kind });
59260
+ log13.info("dragStart", { id: e.active.id, kind: payload.kind });
56706
59261
  } else {
56707
- log11.warn("dragStart:missing-payload", { id: e.active.id });
59262
+ log13.warn("dragStart:missing-payload", { id: e.active.id });
56708
59263
  }
56709
59264
  }, [eventBus]);
56710
59265
  const handleDragEnd = React91__namespace.default.useCallback((e) => {
@@ -56714,7 +59269,7 @@ function CanvasDndProvider({
56714
59269
  const overData = e.over?.data.current;
56715
59270
  const target = overData?.target;
56716
59271
  const accepts = overData?.accepts;
56717
- log11.info("dragEnd", {
59272
+ log13.info("dragEnd", {
56718
59273
  activeId: e.active.id,
56719
59274
  overId: e.over?.id,
56720
59275
  hasPayload: !!payload,
@@ -56726,7 +59281,7 @@ function CanvasDndProvider({
56726
59281
  }
56727
59282
  if (!payload || !target) return;
56728
59283
  if (accepts && !accepts.includes(payload.kind)) {
56729
- log11.debug("dragEnd:rejected:kind", { kind: payload.kind, accepts: [...accepts] });
59284
+ log13.debug("dragEnd:rejected:kind", { kind: payload.kind, accepts: [...accepts] });
56730
59285
  return;
56731
59286
  }
56732
59287
  const activator = e.activatorEvent;
@@ -56738,7 +59293,7 @@ function CanvasDndProvider({
56738
59293
  }, [eventBus, onDrop]);
56739
59294
  const handleDragCancel = React91__namespace.default.useCallback(() => {
56740
59295
  setActivePayload(null);
56741
- log11.info("dragCancel");
59296
+ log13.info("dragCancel");
56742
59297
  }, []);
56743
59298
  return /* @__PURE__ */ jsxRuntime.jsxs(
56744
59299
  core$1.DndContext,
@@ -58553,7 +61108,7 @@ init_AvlTransitionLane();
58553
61108
  init_AvlSwimLane();
58554
61109
  init_types();
58555
61110
  init_avl_elk_layout();
58556
- var log12 = logger.createLogger("almadar:ui:avl:trait-scene");
61111
+ var log14 = logger.createLogger("almadar:ui:avl:trait-scene");
58557
61112
  var SWIM_GUTTER2 = 120;
58558
61113
  var CENTER_W2 = 360;
58559
61114
  var AvlTraitScene = ({
@@ -58566,7 +61121,7 @@ var AvlTraitScene = ({
58566
61121
  const dataKey = React91.useMemo(() => JSON.stringify(data), [data]);
58567
61122
  React91.useEffect(() => {
58568
61123
  computeTraitLayout(data).then(setLayout).catch((error) => {
58569
- log12.error("computeTraitLayout failed", { error: error instanceof Error ? error : String(error) });
61124
+ log14.error("computeTraitLayout failed", { error: error instanceof Error ? error : String(error) });
58570
61125
  });
58571
61126
  }, [dataKey]);
58572
61127
  if (!layout) {