@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
@@ -1,5 +1,5 @@
1
1
  import * as React83 from 'react';
2
- import React83__default, { createContext, useContext, useMemo, useRef, useEffect, useCallback, Suspense, useState, useLayoutEffect, lazy, useId, useSyncExternalStore } from 'react';
2
+ import React83__default, { createContext, useContext, useMemo, useRef, useEffect, useCallback, Suspense, useState, useLayoutEffect, lazy, useId, useSyncExternalStore, forwardRef, useImperativeHandle, Component } from 'react';
3
3
  import { createLogger, isLogLevelEnabled } from '@almadar/logger';
4
4
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
5
  import { EventBusContext, useTraitScope, TraitScopeProvider } from '@almadar/ui/providers';
@@ -41,6 +41,12 @@ import { DndContext, pointerWithin, rectIntersection, closestCorners, useSensors
41
41
  import { useSortable, arrayMove, sortableKeyboardCoordinates, SortableContext, rectSortingStrategy, verticalListSortingStrategy } from '@dnd-kit/sortable';
42
42
  import { CSS } from '@dnd-kit/utilities';
43
43
  import { useNodeId, ReactFlowProvider, Handle, Position } from '@xyflow/react';
44
+ import * as THREE from 'three';
45
+ import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
46
+ import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
47
+ import { GLTFLoader as GLTFLoader$1 } from 'three/examples/jsm/loaders/GLTFLoader';
48
+ import { Canvas, useThree } from '@react-three/fiber';
49
+ import { Grid as Grid$1, OrbitControls } from '@react-three/drei';
44
50
  import { getPatternDefinition, getComponentForPattern as getComponentForPattern$1 } from '@almadar/patterns';
45
51
 
46
52
  var __defProp = Object.defineProperty;
@@ -673,7 +679,7 @@ var init_Box = __esm({
673
679
  position,
674
680
  className,
675
681
  children,
676
- as: Component = "div",
682
+ as: Component2 = "div",
677
683
  action,
678
684
  actionPayload,
679
685
  hoverEvent,
@@ -705,7 +711,7 @@ var init_Box = __esm({
705
711
  }, [hoverEvent, eventBus, onMouseLeave]);
706
712
  const isClickable = action || onClick;
707
713
  return React83__default.createElement(
708
- Component,
714
+ Component2,
709
715
  {
710
716
  ref,
711
717
  className: cn(
@@ -843,9 +849,9 @@ function resolvePhosphor(name, weight, family) {
843
849
  (lib) => {
844
850
  const PhosphorComp = lib[target];
845
851
  if (!PhosphorComp || typeof PhosphorComp !== "object") return null;
846
- const Component = PhosphorComp;
852
+ const Component2 = PhosphorComp;
847
853
  const Adapter = (props) => /* @__PURE__ */ jsx(
848
- Component,
854
+ Component2,
849
855
  {
850
856
  weight,
851
857
  className: props.className,
@@ -869,9 +875,9 @@ function resolveTabler(name, family) {
869
875
  (lib) => {
870
876
  const TablerComp = lib[target];
871
877
  if (!TablerComp || typeof TablerComp !== "object") return null;
872
- const Component = TablerComp;
878
+ const Component2 = TablerComp;
873
879
  const Adapter = (props) => /* @__PURE__ */ jsx(
874
- Component,
880
+ Component2,
875
881
  {
876
882
  stroke: props.strokeWidth ?? 1.5,
877
883
  className: props.className,
@@ -895,9 +901,9 @@ function resolveFa(name, family) {
895
901
  (lib) => {
896
902
  const FaComp = lib[target];
897
903
  if (!FaComp || typeof FaComp !== "function") return null;
898
- const Component = FaComp;
904
+ const Component2 = FaComp;
899
905
  const Adapter = (props) => /* @__PURE__ */ jsx(
900
- Component,
906
+ Component2,
901
907
  {
902
908
  className: props.className,
903
909
  style: props.style,
@@ -1852,8 +1858,8 @@ var init_Typography = __esm({
1852
1858
  children
1853
1859
  }) => {
1854
1860
  const variant = variantProp ?? (level ? `h${level}` : "body1");
1855
- const Component = as || defaultElements[variant];
1856
- const Comp = Component;
1861
+ const Component2 = as || defaultElements[variant];
1862
+ const Comp = Component2;
1857
1863
  return /* @__PURE__ */ jsx(
1858
1864
  Comp,
1859
1865
  {
@@ -4408,10 +4414,10 @@ var init_Center = __esm({
4408
4414
  className,
4409
4415
  style,
4410
4416
  children,
4411
- as: Component = "div"
4417
+ as: Component2 = "div"
4412
4418
  }) => {
4413
4419
  const mergedStyle = minHeight ? { minHeight, ...style } : style;
4414
- const Comp = Component;
4420
+ const Comp = Component2;
4415
4421
  return /* @__PURE__ */ jsx(
4416
4422
  Comp,
4417
4423
  {
@@ -5042,7 +5048,7 @@ var init_Stack = __esm({
5042
5048
  className,
5043
5049
  style,
5044
5050
  children,
5045
- as: Component = "div",
5051
+ as: Component2 = "div",
5046
5052
  onClick,
5047
5053
  onKeyDown,
5048
5054
  role,
@@ -5060,7 +5066,7 @@ var init_Stack = __esm({
5060
5066
  };
5061
5067
  const isHorizontal = direction === "horizontal";
5062
5068
  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";
5063
- const Comp = Component;
5069
+ const Comp = Component2;
5064
5070
  return /* @__PURE__ */ jsx(
5065
5071
  Comp,
5066
5072
  {
@@ -7116,6 +7122,7 @@ var init_HealthBar = __esm({
7116
7122
  }
7117
7123
  });
7118
7124
  function ScoreDisplay({
7125
+ assetUrl = DEFAULT_ASSET_URL,
7119
7126
  value,
7120
7127
  label,
7121
7128
  icon,
@@ -7162,14 +7169,24 @@ function ScoreDisplay({
7162
7169
  className
7163
7170
  ),
7164
7171
  children: [
7165
- icon && /* @__PURE__ */ jsx("span", { className: "flex-shrink-0", children: typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon }) : /* @__PURE__ */ jsx(Icon, { icon }) }),
7172
+ assetUrl ? /* @__PURE__ */ jsx(
7173
+ "img",
7174
+ {
7175
+ src: assetUrl,
7176
+ alt: "",
7177
+ width: 20,
7178
+ height: 20,
7179
+ style: { imageRendering: "pixelated", objectFit: "contain" },
7180
+ className: "flex-shrink-0"
7181
+ }
7182
+ ) : icon ? /* @__PURE__ */ jsx("span", { className: "flex-shrink-0", children: typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon }) : /* @__PURE__ */ jsx(Icon, { icon }) }) : null,
7166
7183
  label && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: label }),
7167
7184
  /* @__PURE__ */ jsx("span", { className: "tabular-nums", children: formattedValue })
7168
7185
  ]
7169
7186
  }
7170
7187
  );
7171
7188
  }
7172
- var sizeMap2;
7189
+ var sizeMap2, DEFAULT_ASSET_URL;
7173
7190
  var init_ScoreDisplay = __esm({
7174
7191
  "components/game/atoms/ScoreDisplay.tsx"() {
7175
7192
  "use client";
@@ -7181,10 +7198,12 @@ var init_ScoreDisplay = __esm({
7181
7198
  lg: "text-2xl",
7182
7199
  xl: "text-4xl"
7183
7200
  };
7201
+ DEFAULT_ASSET_URL = "https://almadar-kflow-assets.web.app/shared/effects/particles/star_01.png";
7184
7202
  ScoreDisplay.displayName = "ScoreDisplay";
7185
7203
  }
7186
7204
  });
7187
7205
  function ControlButton({
7206
+ assetUrl = DEFAULT_ASSET_URL2,
7188
7207
  label,
7189
7208
  icon,
7190
7209
  size = "md",
@@ -7231,7 +7250,7 @@ function ControlButton({
7231
7250
  },
7232
7251
  [isPressed, releaseEvent, eventBus, onRelease]
7233
7252
  );
7234
- return /* @__PURE__ */ jsxs(
7253
+ return /* @__PURE__ */ jsx(
7235
7254
  "button",
7236
7255
  {
7237
7256
  type: "button",
@@ -7252,20 +7271,27 @@ function ControlButton({
7252
7271
  disabled && "opacity-50 cursor-not-allowed",
7253
7272
  className
7254
7273
  ),
7255
- children: [
7256
- icon && /* @__PURE__ */ jsx("span", { className: "text-2xl", children: typeof icon === "string" ? /^[a-zA-Z0-9-]+$/.test(icon) ? (() => {
7257
- const I = resolveIcon(icon);
7258
- return I ? /* @__PURE__ */ jsx(I, { className: "w-6 h-6" }) : null;
7259
- })() : icon : /* @__PURE__ */ (() => {
7260
- const I = icon;
7261
- return /* @__PURE__ */ jsx(I, { className: "w-6 h-6" });
7262
- })() }),
7263
- label && !icon && /* @__PURE__ */ jsx("span", { children: label })
7264
- ]
7274
+ children: assetUrl ? /* @__PURE__ */ jsx(
7275
+ "img",
7276
+ {
7277
+ src: assetUrl,
7278
+ alt: "",
7279
+ width: 24,
7280
+ height: 24,
7281
+ style: { imageRendering: "pixelated", objectFit: "contain" },
7282
+ className: "flex-shrink-0"
7283
+ }
7284
+ ) : icon ? /* @__PURE__ */ jsx("span", { className: "text-2xl", children: typeof icon === "string" ? /^[a-zA-Z0-9-]+$/.test(icon) ? (() => {
7285
+ const I = resolveIcon(icon);
7286
+ return I ? /* @__PURE__ */ jsx(I, { className: "w-6 h-6" }) : null;
7287
+ })() : icon : /* @__PURE__ */ (() => {
7288
+ const I = icon;
7289
+ return /* @__PURE__ */ jsx(I, { className: "w-6 h-6" });
7290
+ })() }) : label ? /* @__PURE__ */ jsx("span", { children: label }) : null
7265
7291
  }
7266
7292
  );
7267
7293
  }
7268
- var sizeMap3, shapeMap, variantMap;
7294
+ var sizeMap3, shapeMap, variantMap, DEFAULT_ASSET_URL2;
7269
7295
  var init_ControlButton = __esm({
7270
7296
  "components/game/atoms/ControlButton.tsx"() {
7271
7297
  "use client";
@@ -7288,6 +7314,7 @@ var init_ControlButton = __esm({
7288
7314
  secondary: "bg-secondary text-secondary-foreground border-border hover:bg-secondary-hover",
7289
7315
  ghost: "bg-transparent text-foreground border-border hover:bg-muted"
7290
7316
  };
7317
+ DEFAULT_ASSET_URL2 = "https://almadar-kflow-assets.web.app/shared/effects/particles/circle_01.png";
7291
7318
  ControlButton.displayName = "ControlButton";
7292
7319
  }
7293
7320
  });
@@ -7367,6 +7394,7 @@ var init_Sprite = __esm({
7367
7394
  }
7368
7395
  });
7369
7396
  function StateIndicator({
7397
+ assetUrl = DEFAULT_ASSET_URL3,
7370
7398
  state = "idle",
7371
7399
  label,
7372
7400
  size = "md",
@@ -7389,18 +7417,29 @@ function StateIndicator({
7389
7417
  className
7390
7418
  ),
7391
7419
  children: [
7392
- /* @__PURE__ */ jsx(Box, { as: "span", children: typeof config.icon === "string" ? /^[a-zA-Z0-9-]+$/.test(config.icon) ? /* @__PURE__ */ jsx(Icon, { name: config.icon }) : config.icon : /* @__PURE__ */ jsx(Icon, { icon: config.icon }) }),
7420
+ /* @__PURE__ */ jsx(Box, { as: "span", children: assetUrl ? /* @__PURE__ */ jsx(
7421
+ "img",
7422
+ {
7423
+ src: assetUrl,
7424
+ alt: displayLabel,
7425
+ width: 16,
7426
+ height: 16,
7427
+ style: { imageRendering: "pixelated", objectFit: "contain" },
7428
+ className: "flex-shrink-0"
7429
+ }
7430
+ ) : typeof config.icon === "string" ? /^[a-zA-Z0-9-]+$/.test(config.icon) ? /* @__PURE__ */ jsx(Icon, { name: config.icon }) : config.icon : /* @__PURE__ */ jsx(Icon, { icon: config.icon }) }),
7393
7431
  /* @__PURE__ */ jsx(Box, { as: "span", children: displayLabel })
7394
7432
  ]
7395
7433
  }
7396
7434
  );
7397
7435
  }
7398
- var DEFAULT_STATE_STYLES, DEFAULT_STYLE, SIZE_CLASSES;
7436
+ var DEFAULT_ASSET_URL3, DEFAULT_STATE_STYLES, DEFAULT_STYLE, SIZE_CLASSES;
7399
7437
  var init_StateIndicator = __esm({
7400
7438
  "components/game/atoms/StateIndicator.tsx"() {
7401
7439
  init_Box();
7402
7440
  init_Icon();
7403
7441
  init_cn();
7442
+ DEFAULT_ASSET_URL3 = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
7404
7443
  DEFAULT_STATE_STYLES = {
7405
7444
  idle: { icon: "\u23F8", bgClass: "bg-muted" },
7406
7445
  active: { icon: "\u25B6", bgClass: "bg-success" },
@@ -7475,6 +7514,7 @@ var init_TimerDisplay = __esm({
7475
7514
  }
7476
7515
  });
7477
7516
  function ResourceCounter({
7517
+ assetUrl = DEFAULT_ASSET_URL4,
7478
7518
  icon,
7479
7519
  label = "Gold",
7480
7520
  value = 250,
@@ -7494,7 +7534,17 @@ function ResourceCounter({
7494
7534
  className
7495
7535
  ),
7496
7536
  children: [
7497
- icon && /* @__PURE__ */ jsx("span", { className: cn("flex-shrink-0", sizes.icon), children: typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon }) : /* @__PURE__ */ jsx(Icon, { icon }) }),
7537
+ assetUrl ? /* @__PURE__ */ jsx(
7538
+ "img",
7539
+ {
7540
+ src: assetUrl,
7541
+ alt: label,
7542
+ width: sizes.img,
7543
+ height: sizes.img,
7544
+ style: { imageRendering: "pixelated", objectFit: "contain" },
7545
+ className: "flex-shrink-0"
7546
+ }
7547
+ ) : icon ? /* @__PURE__ */ jsx("span", { className: cn("flex-shrink-0", sizes.icon), children: typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon }) : /* @__PURE__ */ jsx(Icon, { icon }) }) : null,
7498
7548
  /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: label }),
7499
7549
  /* @__PURE__ */ jsxs("span", { className: cn("font-bold tabular-nums", color && (color in colorTokenClasses2 ? colorTokenClasses2[color] : color)), children: [
7500
7550
  value,
@@ -7507,7 +7557,7 @@ function ResourceCounter({
7507
7557
  }
7508
7558
  );
7509
7559
  }
7510
- var colorTokenClasses2, sizeMap5;
7560
+ var colorTokenClasses2, DEFAULT_ASSET_URL4, sizeMap5;
7511
7561
  var init_ResourceCounter = __esm({
7512
7562
  "components/game/atoms/ResourceCounter.tsx"() {
7513
7563
  init_cn();
@@ -7520,16 +7570,17 @@ var init_ResourceCounter = __esm({
7520
7570
  error: "text-error",
7521
7571
  muted: "text-muted-foreground"
7522
7572
  };
7573
+ DEFAULT_ASSET_URL4 = "https://almadar-kflow-assets.web.app/shared/world-map/gold_mine.png";
7523
7574
  sizeMap5 = {
7524
- sm: { wrapper: "text-xs gap-1 px-1.5 py-0.5", icon: "text-sm" },
7525
- md: { wrapper: "text-sm gap-1.5 px-2 py-1", icon: "text-base" },
7526
- lg: { wrapper: "text-base gap-2 px-3 py-1.5", icon: "text-lg" }
7575
+ sm: { wrapper: "text-xs gap-1 px-1.5 py-0.5", icon: "text-sm", img: 16 },
7576
+ md: { wrapper: "text-sm gap-1.5 px-2 py-1", icon: "text-base", img: 20 },
7577
+ lg: { wrapper: "text-base gap-2 px-3 py-1.5", icon: "text-lg", img: 28 }
7527
7578
  };
7528
7579
  ResourceCounter.displayName = "ResourceCounter";
7529
7580
  }
7530
7581
  });
7531
7582
  function ItemSlot({
7532
- assetUrl = DEFAULT_ASSET_URL,
7583
+ assetUrl = DEFAULT_ASSET_URL5,
7533
7584
  icon = "sword",
7534
7585
  label = "Iron Sword",
7535
7586
  quantity,
@@ -7588,7 +7639,7 @@ function ItemSlot({
7588
7639
  }
7589
7640
  );
7590
7641
  }
7591
- var sizeMap6, rarityBorderMap, rarityGlowMap, DEFAULT_ASSET_URL, assetSizeMap;
7642
+ var sizeMap6, rarityBorderMap, rarityGlowMap, DEFAULT_ASSET_URL5, assetSizeMap;
7592
7643
  var init_ItemSlot = __esm({
7593
7644
  "components/game/atoms/ItemSlot.tsx"() {
7594
7645
  "use client";
@@ -7613,7 +7664,7 @@ var init_ItemSlot = __esm({
7613
7664
  epic: "shadow-lg",
7614
7665
  legendary: "shadow-lg"
7615
7666
  };
7616
- DEFAULT_ASSET_URL = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
7667
+ DEFAULT_ASSET_URL5 = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
7617
7668
  assetSizeMap = {
7618
7669
  sm: 28,
7619
7670
  md: 40,
@@ -7623,6 +7674,7 @@ var init_ItemSlot = __esm({
7623
7674
  }
7624
7675
  });
7625
7676
  function TurnIndicator({
7677
+ assetUrl = DEFAULT_ASSET_URL6,
7626
7678
  currentTurn = 1,
7627
7679
  maxTurns,
7628
7680
  activeTeam,
@@ -7655,14 +7707,24 @@ function TurnIndicator({
7655
7707
  ] }),
7656
7708
  activeTeam && /* @__PURE__ */ jsxs(Fragment, { children: [
7657
7709
  /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "|" }),
7658
- /* @__PURE__ */ jsx("span", { className: cn("rounded-full bg-success/20", sizes.dot) }),
7710
+ assetUrl ? /* @__PURE__ */ jsx(
7711
+ "img",
7712
+ {
7713
+ src: assetUrl,
7714
+ alt: "",
7715
+ width: 12,
7716
+ height: 12,
7717
+ style: { imageRendering: "pixelated", objectFit: "contain" },
7718
+ className: "flex-shrink-0"
7719
+ }
7720
+ ) : /* @__PURE__ */ jsx("span", { className: cn("rounded-full bg-success/20", sizes.dot) }),
7659
7721
  /* @__PURE__ */ jsx("span", { className: "text-success", children: activeTeam })
7660
7722
  ] })
7661
7723
  ]
7662
7724
  }
7663
7725
  );
7664
7726
  }
7665
- var sizeMap7;
7727
+ var sizeMap7, DEFAULT_ASSET_URL6;
7666
7728
  var init_TurnIndicator = __esm({
7667
7729
  "components/game/atoms/TurnIndicator.tsx"() {
7668
7730
  init_cn();
@@ -7671,6 +7733,7 @@ var init_TurnIndicator = __esm({
7671
7733
  md: { wrapper: "text-sm gap-2 px-3 py-1", dot: "w-2 h-2" },
7672
7734
  lg: { wrapper: "text-base gap-2.5 px-4 py-1.5", dot: "w-2.5 h-2.5" }
7673
7735
  };
7736
+ DEFAULT_ASSET_URL6 = "https://almadar-kflow-assets.web.app/shared/effects/particles/symbol_01.png";
7674
7737
  TurnIndicator.displayName = "TurnIndicator";
7675
7738
  }
7676
7739
  });
@@ -7686,6 +7749,7 @@ function getComboScale(combo) {
7686
7749
  return "";
7687
7750
  }
7688
7751
  function ComboCounter({
7752
+ assetUrl = DEFAULT_ASSET_URL7,
7689
7753
  combo = 5,
7690
7754
  multiplier,
7691
7755
  streak,
@@ -7705,6 +7769,17 @@ function ComboCounter({
7705
7769
  className
7706
7770
  ),
7707
7771
  children: [
7772
+ assetUrl && /* @__PURE__ */ jsx(
7773
+ "img",
7774
+ {
7775
+ src: assetUrl,
7776
+ alt: "combo",
7777
+ width: 24,
7778
+ height: 24,
7779
+ style: { imageRendering: "pixelated", objectFit: "contain" },
7780
+ className: "flex-shrink-0 mb-0.5"
7781
+ }
7782
+ ),
7708
7783
  /* @__PURE__ */ jsx("span", { className: cn("font-black tabular-nums leading-none", sizes.combo, getComboIntensity(combo)), children: combo }),
7709
7784
  /* @__PURE__ */ jsx("span", { className: cn("font-bold uppercase tracking-wider text-muted-foreground", sizes.label), children: "combo" }),
7710
7785
  multiplier != null && multiplier > 1 && /* @__PURE__ */ jsxs("span", { className: cn("font-bold text-warning tabular-nums", sizes.multiplier), children: [
@@ -7719,10 +7794,11 @@ function ComboCounter({
7719
7794
  }
7720
7795
  );
7721
7796
  }
7722
- var sizeMap8;
7797
+ var DEFAULT_ASSET_URL7, sizeMap8;
7723
7798
  var init_ComboCounter = __esm({
7724
7799
  "components/game/atoms/ComboCounter.tsx"() {
7725
7800
  init_cn();
7801
+ DEFAULT_ASSET_URL7 = "https://almadar-kflow-assets.web.app/shared/effects/flash/flash00.png";
7726
7802
  sizeMap8 = {
7727
7803
  sm: { combo: "text-lg", label: "text-xs", multiplier: "text-xs" },
7728
7804
  md: { combo: "text-2xl", label: "text-xs", multiplier: "text-sm" },
@@ -7816,6 +7892,7 @@ var init_XPBar = __esm({
7816
7892
  }
7817
7893
  });
7818
7894
  function WaypointMarker({
7895
+ assetUrl = DEFAULT_ASSET_URL8,
7819
7896
  label,
7820
7897
  icon,
7821
7898
  active = true,
@@ -7854,7 +7931,17 @@ function WaypointMarker({
7854
7931
  active && !completed && "bg-info text-foreground",
7855
7932
  !active && !completed && "bg-muted"
7856
7933
  ),
7857
- children: completed ? checkIcon : icon && (typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon }) : /* @__PURE__ */ jsx(Icon, { icon }))
7934
+ children: completed ? checkIcon : assetUrl ? /* @__PURE__ */ jsx(
7935
+ "img",
7936
+ {
7937
+ src: assetUrl,
7938
+ alt: label,
7939
+ width: sizes.img,
7940
+ height: sizes.img,
7941
+ style: { imageRendering: "pixelated", objectFit: "contain" },
7942
+ className: "flex-shrink-0"
7943
+ }
7944
+ ) : icon ? typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon }) : /* @__PURE__ */ jsx(Icon, { icon }) : null
7858
7945
  }
7859
7946
  )
7860
7947
  ] }),
@@ -7871,15 +7958,16 @@ function WaypointMarker({
7871
7958
  )
7872
7959
  ] });
7873
7960
  }
7874
- var sizeMap10, checkIcon;
7961
+ var DEFAULT_ASSET_URL8, sizeMap10, checkIcon;
7875
7962
  var init_WaypointMarker = __esm({
7876
7963
  "components/game/atoms/WaypointMarker.tsx"() {
7877
7964
  init_cn();
7878
7965
  init_Icon();
7966
+ DEFAULT_ASSET_URL8 = "https://almadar-kflow-assets.web.app/shared/world-map/battle_marker.png";
7879
7967
  sizeMap10 = {
7880
- sm: { dot: "w-4 h-4", ring: "w-6 h-6", label: "text-xs mt-1" },
7881
- md: { dot: "w-6 h-6", ring: "w-8 h-8", label: "text-sm mt-1.5" },
7882
- lg: { dot: "w-8 h-8", ring: "w-10 h-10", label: "text-base mt-2" }
7968
+ sm: { dot: "w-4 h-4", ring: "w-6 h-6", label: "text-xs mt-1", img: 12 },
7969
+ md: { dot: "w-6 h-6", ring: "w-8 h-8", label: "text-sm mt-1.5", img: 18 },
7970
+ lg: { dot: "w-8 h-8", ring: "w-10 h-10", label: "text-base mt-2", img: 24 }
7883
7971
  };
7884
7972
  checkIcon = /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 3, className: "w-3/5 h-3/5", children: /* @__PURE__ */ jsx("path", { d: "M5 13l4 4L19 7", strokeLinecap: "round", strokeLinejoin: "round" }) });
7885
7973
  WaypointMarker.displayName = "WaypointMarker";
@@ -7892,6 +7980,7 @@ function formatDuration(seconds) {
7892
7980
  return `${Math.round(seconds)}s`;
7893
7981
  }
7894
7982
  function StatusEffect({
7983
+ assetUrl = DEFAULT_ASSET_URL9,
7895
7984
  icon = "shield",
7896
7985
  label = "Shield",
7897
7986
  duration = 30,
@@ -7912,7 +8001,17 @@ function StatusEffect({
7912
8001
  ),
7913
8002
  title: label,
7914
8003
  children: [
7915
- /* @__PURE__ */ jsx("span", { className: cn("flex items-center justify-center", sizes.icon), children: typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon, size: "sm" }) : /* @__PURE__ */ jsx(Icon, { icon, size: "sm" }) }),
8004
+ /* @__PURE__ */ jsx("span", { className: cn("flex items-center justify-center", sizes.icon), children: assetUrl ? /* @__PURE__ */ jsx(
8005
+ "img",
8006
+ {
8007
+ src: assetUrl,
8008
+ alt: label,
8009
+ width: sizes.img,
8010
+ height: sizes.img,
8011
+ style: { imageRendering: "pixelated", objectFit: "contain" },
8012
+ className: "flex-shrink-0"
8013
+ }
8014
+ ) : typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon, size: "sm" }) : /* @__PURE__ */ jsx(Icon, { icon, size: "sm" }) }),
7916
8015
  duration !== void 0 && /* @__PURE__ */ jsx(
7917
8016
  "span",
7918
8017
  {
@@ -7939,15 +8038,16 @@ function StatusEffect({
7939
8038
  label && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground mt-0.5 text-center whitespace-nowrap", children: label })
7940
8039
  ] });
7941
8040
  }
7942
- var sizeMap11, variantStyles7;
8041
+ var DEFAULT_ASSET_URL9, sizeMap11, variantStyles7;
7943
8042
  var init_StatusEffect = __esm({
7944
8043
  "components/game/atoms/StatusEffect.tsx"() {
7945
8044
  init_cn();
7946
8045
  init_Icon();
8046
+ DEFAULT_ASSET_URL9 = "https://almadar-kflow-assets.web.app/shared/effects/particles/flame_01.png";
7947
8047
  sizeMap11 = {
7948
- sm: { container: "w-8 h-8", icon: "text-sm", badge: "text-xs -top-1 -right-1 w-4 h-4", timer: "text-[9px]" },
7949
- md: { container: "w-10 h-10", icon: "text-base", badge: "text-xs -top-1 -right-1 w-5 h-5", timer: "text-xs" },
7950
- 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" }
8048
+ 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 },
8049
+ 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 },
8050
+ 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 }
7951
8051
  };
7952
8052
  variantStyles7 = {
7953
8053
  buff: "border-success bg-success/20",
@@ -7958,6 +8058,7 @@ var init_StatusEffect = __esm({
7958
8058
  }
7959
8059
  });
7960
8060
  function DamageNumber({
8061
+ assetUrl = DEFAULT_ASSET_URL10,
7961
8062
  value = 42,
7962
8063
  type = "damage",
7963
8064
  size = "md",
@@ -7966,22 +8067,35 @@ function DamageNumber({
7966
8067
  const displayText = type === "miss" ? "MISS" : type === "heal" ? `+${value}` : `${value}`;
7967
8068
  return /* @__PURE__ */ jsxs(Fragment, { children: [
7968
8069
  /* @__PURE__ */ jsx("style", { children: floatKeyframes }),
7969
- /* @__PURE__ */ jsx(
8070
+ /* @__PURE__ */ jsxs(
7970
8071
  "span",
7971
8072
  {
7972
8073
  className: cn(
7973
- "inline-block font-mono select-none pointer-events-none",
8074
+ "inline-flex items-center gap-0.5 font-mono select-none pointer-events-none",
7974
8075
  sizeMap12[size],
7975
8076
  typeStyles[type],
7976
8077
  className
7977
8078
  ),
7978
8079
  style: { animation: "damageFloat 1s ease-out forwards" },
7979
- children: displayText
8080
+ children: [
8081
+ assetUrl && /* @__PURE__ */ jsx(
8082
+ "img",
8083
+ {
8084
+ src: assetUrl,
8085
+ alt: "",
8086
+ width: 14,
8087
+ height: 14,
8088
+ style: { imageRendering: "pixelated", objectFit: "contain" },
8089
+ className: "flex-shrink-0"
8090
+ }
8091
+ ),
8092
+ displayText
8093
+ ]
7980
8094
  }
7981
8095
  )
7982
8096
  ] });
7983
8097
  }
7984
- var sizeMap12, typeStyles, floatKeyframes;
8098
+ var sizeMap12, typeStyles, floatKeyframes, DEFAULT_ASSET_URL10;
7985
8099
  var init_DamageNumber = __esm({
7986
8100
  "components/game/atoms/DamageNumber.tsx"() {
7987
8101
  init_cn();
@@ -8003,6 +8117,7 @@ var init_DamageNumber = __esm({
8003
8117
  100% { opacity: 0; transform: translateY(-32px) scale(0.8); }
8004
8118
  }
8005
8119
  `;
8120
+ DEFAULT_ASSET_URL10 = "https://almadar-kflow-assets.web.app/shared/effects/particles/spark_01.png";
8006
8121
  DamageNumber.displayName = "DamageNumber";
8007
8122
  }
8008
8123
  });
@@ -8091,6 +8206,7 @@ var init_ChoiceButton = __esm({
8091
8206
  }
8092
8207
  });
8093
8208
  function ActionButton({
8209
+ assetUrl = DEFAULT_ASSET_URL11,
8094
8210
  label = "Attack",
8095
8211
  icon,
8096
8212
  cooldown = 0,
@@ -8130,13 +8246,23 @@ function ActionButton({
8130
8246
  }
8131
8247
  }
8132
8248
  ),
8133
- icon && /* @__PURE__ */ jsx("span", { className: cn("flex-shrink-0", sizes.icon), children: typeof icon === "string" ? (() => {
8249
+ assetUrl ? /* @__PURE__ */ jsx(
8250
+ "img",
8251
+ {
8252
+ src: assetUrl,
8253
+ alt: "",
8254
+ width: 16,
8255
+ height: 16,
8256
+ style: { imageRendering: "pixelated", objectFit: "contain" },
8257
+ className: cn("flex-shrink-0", sizes.icon)
8258
+ }
8259
+ ) : icon ? /* @__PURE__ */ jsx("span", { className: cn("flex-shrink-0", sizes.icon), children: typeof icon === "string" ? (() => {
8134
8260
  const I = resolveIcon(icon);
8135
8261
  return I ? /* @__PURE__ */ jsx(I, { className: "w-4 h-4" }) : null;
8136
8262
  })() : /* @__PURE__ */ (() => {
8137
8263
  const I = icon;
8138
8264
  return /* @__PURE__ */ jsx(I, { className: "w-4 h-4" });
8139
- })() }),
8265
+ })() }) : null,
8140
8266
  /* @__PURE__ */ jsx("span", { className: "relative z-10", children: label }),
8141
8267
  hotkey && /* @__PURE__ */ jsx(
8142
8268
  "span",
@@ -8152,7 +8278,7 @@ function ActionButton({
8152
8278
  }
8153
8279
  );
8154
8280
  }
8155
- var sizeMap13, variantStyles8;
8281
+ var sizeMap13, variantStyles8, DEFAULT_ASSET_URL11;
8156
8282
  var init_ActionButton = __esm({
8157
8283
  "components/game/atoms/ActionButton.tsx"() {
8158
8284
  init_cn();
@@ -8167,6 +8293,7 @@ var init_ActionButton = __esm({
8167
8293
  secondary: "bg-secondary text-secondary-foreground hover:bg-secondary-hover border-border",
8168
8294
  danger: "bg-error text-error-foreground hover:bg-error/90 border-error"
8169
8295
  };
8296
+ DEFAULT_ASSET_URL11 = "https://almadar-kflow-assets.web.app/shared/effects/particles/slash_01.png";
8170
8297
  ActionButton.displayName = "ActionButton";
8171
8298
  }
8172
8299
  });
@@ -9035,10 +9162,10 @@ var init_Container = __esm({
9035
9162
  center = true,
9036
9163
  className,
9037
9164
  children,
9038
- as: Component = "div"
9165
+ as: Component2 = "div"
9039
9166
  }) => {
9040
9167
  const resolvedSize = maxWidth ?? size ?? "lg";
9041
- const Comp = Component;
9168
+ const Comp = Component2;
9042
9169
  return /* @__PURE__ */ jsx(
9043
9170
  Comp,
9044
9171
  {
@@ -11860,15 +11987,15 @@ function BattleBoard({
11860
11987
  const board = boardEntity(entity) ?? {};
11861
11988
  const tiles = propTiles ?? (Array.isArray(board.tiles) ? board.tiles : []);
11862
11989
  const features = propFeatures ?? (Array.isArray(board.features) ? board.features : []);
11863
- const boardWidth = num(board.boardWidth, 8);
11864
- const boardHeight = num(board.boardHeight, 6);
11990
+ const boardWidth = num(board.gridWidth ?? board.boardWidth, 8);
11991
+ const boardHeight = num(board.gridHeight ?? board.boardHeight, 6);
11865
11992
  const assetManifest = propAssetManifest ?? board.assetManifest;
11866
11993
  const backgroundImage = board.backgroundImage;
11867
11994
  const units = rows(board.units);
11868
11995
  const selectedUnitId = board.selectedUnitId ?? null;
11869
11996
  const currentPhase = str(board.phase) || "observation";
11870
11997
  const currentTurn = num(board.turn, 1);
11871
- const gameResult = board.gameResult ?? null;
11998
+ const gameResult = board.result ?? null;
11872
11999
  const eventBus = useEventBus();
11873
12000
  const { t } = useTranslate();
11874
12001
  const [hoveredTile, setHoveredTile] = useState(null);
@@ -11889,7 +12016,7 @@ function BattleBoard({
11889
12016
  const validMoves = useMemo(() => {
11890
12017
  if (!selectedUnit || currentPhase !== "movement") return [];
11891
12018
  const moves = [];
11892
- const range = num(selectedUnit.movement);
12019
+ const range = num(board.movementRange, 2);
11893
12020
  const origin = unitPosition(selectedUnit);
11894
12021
  for (let dy = -range; dy <= range; dy++) {
11895
12022
  for (let dx = -range; dx <= range; dx++) {
@@ -20174,15 +20301,24 @@ function CastleBoard({
20174
20301
  featureClickEvent,
20175
20302
  unitClickEvent,
20176
20303
  tileClickEvent,
20304
+ playAgainEvent,
20177
20305
  className
20178
20306
  }) {
20179
20307
  const eventBus = useEventBus();
20308
+ const { t } = useTranslate();
20180
20309
  const resolved = boardEntity(entity);
20181
20310
  const tiles = propTiles ?? (Array.isArray(resolved?.tiles) ? resolved.tiles : []);
20182
20311
  const features = propFeatures ?? (Array.isArray(resolved?.features) ? resolved.features : []);
20183
20312
  const units = propUnits ?? (Array.isArray(resolved?.units) ? resolved.units : []);
20184
20313
  const assetManifest = propAssetManifest ?? resolved?.assetManifest;
20185
20314
  const backgroundImage = resolved?.backgroundImage;
20315
+ const gold = num(resolved?.gold);
20316
+ const health = num(resolved?.health);
20317
+ const maxHealth = num(resolved?.maxHealth);
20318
+ const wave = num(resolved?.wave);
20319
+ const tickCount = num(resolved?.tickCount);
20320
+ const buildings = rows(resolved?.buildings);
20321
+ const result = str(resolved?.result) || "none";
20186
20322
  const [hoveredTile, setHoveredTile] = useState(null);
20187
20323
  const [selectedFeature, setSelectedFeature] = useState(null);
20188
20324
  const hoveredFeature = useMemo(() => {
@@ -20195,7 +20331,7 @@ function CastleBoard({
20195
20331
  (u) => u.position?.x === hoveredTile.x && u.position?.y === hoveredTile.y
20196
20332
  ) ?? null;
20197
20333
  }, [hoveredTile, units]);
20198
- const maxY = Math.max(...tiles.map((t) => t.y), 0);
20334
+ const maxY = Math.max(...tiles.map((t2) => t2.y), 0);
20199
20335
  const baseOffsetX = (maxY + 1) * (TILE_WIDTH * scale / 2);
20200
20336
  const tileToScreen = useCallback(
20201
20337
  (tx, ty) => isoToScreen(tx, ty, scale, baseOffsetX),
@@ -20230,6 +20366,11 @@ function CastleBoard({
20230
20366
  }
20231
20367
  }, [units, onUnitClick, unitClickEvent, eventBus]);
20232
20368
  const clearSelection = useCallback(() => setSelectedFeature(null), []);
20369
+ const handlePlayAgain = useCallback(() => {
20370
+ if (playAgainEvent) {
20371
+ eventBus.emit(`UI:${playAgainEvent}`, {});
20372
+ }
20373
+ }, [playAgainEvent, eventBus]);
20233
20374
  const ctx = useMemo(
20234
20375
  () => ({
20235
20376
  hoveredTile,
@@ -20238,11 +20379,18 @@ function CastleBoard({
20238
20379
  selectedFeature,
20239
20380
  clearSelection,
20240
20381
  tileToScreen,
20241
- scale
20382
+ scale,
20383
+ gold,
20384
+ health,
20385
+ maxHealth,
20386
+ wave,
20387
+ tickCount,
20388
+ buildings,
20389
+ result
20242
20390
  }),
20243
- [hoveredTile, hoveredFeature, hoveredUnit, selectedFeature, clearSelection, tileToScreen, scale]
20391
+ [hoveredTile, hoveredFeature, hoveredUnit, selectedFeature, clearSelection, tileToScreen, scale, gold, health, maxHealth, wave, tickCount, buildings, result]
20244
20392
  );
20245
- return /* @__PURE__ */ jsxs("div", { className: cn("castle-board min-h-screen flex flex-col bg-background", className), children: [
20393
+ return /* @__PURE__ */ jsxs("div", { className: cn("castle-board relative min-h-screen flex flex-col bg-background", className), children: [
20246
20394
  header && header(ctx),
20247
20395
  /* @__PURE__ */ jsxs("div", { className: "flex flex-1 overflow-hidden", children: [
20248
20396
  /* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-auto p-4 relative", children: [
@@ -20267,7 +20415,29 @@ function CastleBoard({
20267
20415
  ] }),
20268
20416
  sidePanel && /* @__PURE__ */ jsx("div", { className: "w-96 shrink-0 border-l border-border bg-surface overflow-y-auto", children: sidePanel(ctx) })
20269
20417
  ] }),
20270
- footer && footer(ctx)
20418
+ footer && footer(ctx),
20419
+ result !== "none" && /* @__PURE__ */ jsx(Box, { className: "absolute inset-0 z-50 flex items-center justify-center bg-background/70 backdrop-blur-sm rounded-container", children: /* @__PURE__ */ jsxs(VStack, { className: "text-center p-8", gap: "lg", children: [
20420
+ /* @__PURE__ */ jsx(
20421
+ Typography,
20422
+ {
20423
+ variant: "h2",
20424
+ className: cn(
20425
+ "text-4xl font-black tracking-widest uppercase",
20426
+ result === "victory" ? "text-warning" : "text-error"
20427
+ ),
20428
+ children: result === "victory" ? t("battle.victory") : t("battle.defeat")
20429
+ }
20430
+ ),
20431
+ /* @__PURE__ */ jsx(
20432
+ Button,
20433
+ {
20434
+ variant: "primary",
20435
+ className: "px-8 py-3 font-semibold",
20436
+ onClick: handlePlayAgain,
20437
+ children: t("battle.playAgain")
20438
+ }
20439
+ )
20440
+ ] }) })
20271
20441
  ] });
20272
20442
  }
20273
20443
  var init_CastleBoard = __esm({
@@ -20275,6 +20445,10 @@ var init_CastleBoard = __esm({
20275
20445
  "use client";
20276
20446
  init_cn();
20277
20447
  init_useEventBus();
20448
+ init_Box();
20449
+ init_Button();
20450
+ init_Typography();
20451
+ init_Stack();
20278
20452
  init_IsometricCanvas();
20279
20453
  init_boardEntity();
20280
20454
  init_isometric();
@@ -21151,22 +21325,16 @@ function ClassifierBoard({
21151
21325
  const { emit } = useEventBus();
21152
21326
  const { t } = useTranslate();
21153
21327
  const resolved = boardEntity(entity);
21154
- const [localAssignments, setLocalAssignments] = useState({});
21155
21328
  const [headerError, setHeaderError] = useState(false);
21156
- const [localSubmitted, setLocalSubmitted] = useState(false);
21157
- const [localAttempts, setLocalAttempts] = useState(0);
21158
- const [showHint, setShowHint] = useState(false);
21159
21329
  const items = Array.isArray(resolved?.items) ? resolved.items : [];
21160
21330
  const categories = Array.isArray(resolved?.categories) ? resolved.categories : [];
21161
- const entityResult = str(resolved?.result);
21162
- const entityDrivesResult = entityResult.length > 0;
21163
- const entityHasAssignments = items.some((item) => item.assignedCategory != null);
21164
- const assignments = entityHasAssignments ? items.reduce((acc, item) => {
21165
- if (item.assignedCategory != null) acc[item.id] = item.assignedCategory;
21331
+ const result = str(resolved?.result);
21332
+ const submitted = result === "win";
21333
+ const attempts = num(resolved?.attempts);
21334
+ const assignments = items.reduce((acc, item) => {
21335
+ if (item.assignedCategory != null && item.assignedCategory !== "") acc[item.id] = item.assignedCategory;
21166
21336
  return acc;
21167
- }, {}) : localAssignments;
21168
- const attempts = entityDrivesResult ? num(resolved?.attempts) : localAttempts;
21169
- const submitted = entityDrivesResult || localSubmitted;
21337
+ }, {});
21170
21338
  const unassignedItems = items.filter((item) => !assignments[item.id]);
21171
21339
  const allAssigned = items.length > 0 && Object.keys(assignments).length === items.length;
21172
21340
  const results = submitted ? items.map((item) => ({
@@ -21174,54 +21342,25 @@ function ClassifierBoard({
21174
21342
  assigned: assignments[item.id],
21175
21343
  correct: assignments[item.id] === item.correctCategory
21176
21344
  })) : [];
21177
- const allCorrect = entityDrivesResult ? entityResult === "success" : results.length > 0 && results.every((r) => r.correct);
21345
+ const allCorrect = result === "win";
21178
21346
  const correctCount = results.filter((r) => r.correct).length;
21347
+ const showHint = attempts >= 2 && Boolean(str(resolved?.hint));
21179
21348
  const handleAssign = (itemId, categoryId) => {
21180
21349
  if (submitted) return;
21181
- if (assignEvent) {
21182
- emit(`UI:${assignEvent}`, { itemId, categoryId });
21183
- }
21184
- if (!entityHasAssignments) {
21185
- setLocalAssignments((prev) => ({ ...prev, [itemId]: categoryId }));
21186
- }
21350
+ if (assignEvent) emit(`UI:${assignEvent}`, { itemId, categoryId });
21187
21351
  };
21188
21352
  const handleUnassign = (itemId) => {
21189
21353
  if (submitted) return;
21190
- if (!entityHasAssignments) {
21191
- setLocalAssignments((prev) => {
21192
- const next = { ...prev };
21193
- delete next[itemId];
21194
- return next;
21195
- });
21196
- }
21354
+ if (assignEvent) emit(`UI:${assignEvent}`, { itemId, categoryId: "" });
21197
21355
  };
21198
- const handleSubmit = useCallback(() => {
21199
- if (checkEvent) {
21200
- emit(`UI:${checkEvent}`, {});
21201
- }
21202
- if (!entityDrivesResult) {
21203
- setLocalSubmitted(true);
21204
- setLocalAttempts((a) => a + 1);
21205
- const correct = items.every((item) => assignments[item.id] === item.correctCategory);
21206
- if (correct) {
21207
- emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
21208
- }
21209
- }
21210
- }, [checkEvent, entityDrivesResult, items, assignments, attempts, completeEvent, emit]);
21211
- const handleReset = () => {
21212
- if (!entityDrivesResult) setLocalSubmitted(false);
21213
- if (attempts >= 2 && str(resolved?.hint)) {
21214
- setShowHint(true);
21356
+ const handleSubmit = () => {
21357
+ if (checkEvent) emit(`UI:${checkEvent}`, {});
21358
+ if (allAssigned && items.every((item) => assignments[item.id] === item.correctCategory)) {
21359
+ emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
21215
21360
  }
21216
21361
  };
21217
21362
  const handleFullReset = () => {
21218
- if (playAgainEvent) {
21219
- emit(`UI:${playAgainEvent}`, {});
21220
- }
21221
- setLocalAssignments({});
21222
- setLocalSubmitted(false);
21223
- setLocalAttempts(0);
21224
- setShowHint(false);
21363
+ if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
21225
21364
  };
21226
21365
  if (!resolved) return null;
21227
21366
  const theme = resolved.theme ?? void 0;
@@ -21260,17 +21399,17 @@ function ClassifierBoard({
21260
21399
  /* @__PURE__ */ jsx(Badge, { size: "sm", children: catItems.length })
21261
21400
  ] }),
21262
21401
  /* @__PURE__ */ jsx(HStack, { gap: "xs", className: "flex-wrap min-h-[2rem]", children: catItems.map((item) => {
21263
- const result = results.find((r) => r.item.id === item.id);
21402
+ const result2 = results.find((r) => r.item.id === item.id);
21264
21403
  return /* @__PURE__ */ jsxs(
21265
21404
  Badge,
21266
21405
  {
21267
21406
  size: "sm",
21268
- className: `cursor-pointer ${result ? result.correct ? "border-success bg-success/10" : "border-error bg-error/10" : ""}`,
21407
+ className: `cursor-pointer ${result2 ? result2.correct ? "border-success bg-success/10" : "border-error bg-error/10" : ""}`,
21269
21408
  onClick: () => handleUnassign(item.id),
21270
21409
  children: [
21271
21410
  item.iconUrl && /* @__PURE__ */ jsx("img", { src: item.iconUrl, alt: "", className: "w-3 h-3 object-contain inline-block" }),
21272
21411
  item.label,
21273
- result && /* @__PURE__ */ jsx(Icon, { icon: result.correct ? CheckCircle : XCircle, size: "xs", className: result.correct ? "text-success" : "text-error" })
21412
+ result2 && /* @__PURE__ */ jsx(Icon, { icon: result2.correct ? CheckCircle : XCircle, size: "xs", className: result2.correct ? "text-success" : "text-error" })
21274
21413
  ]
21275
21414
  },
21276
21415
  item.id
@@ -21299,10 +21438,10 @@ function ClassifierBoard({
21299
21438
  ] }) }),
21300
21439
  showHint && hint && /* @__PURE__ */ jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsx(Typography, { variant: "body", children: hint }) }),
21301
21440
  /* @__PURE__ */ jsxs(HStack, { gap: "sm", justify: "center", children: [
21302
- !submitted ? /* @__PURE__ */ jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: !allAssigned, children: [
21441
+ !submitted && /* @__PURE__ */ jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: !allAssigned, children: [
21303
21442
  /* @__PURE__ */ jsx(Icon, { icon: Send, size: "sm" }),
21304
21443
  t("classifier.check")
21305
- ] }) : !allCorrect ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleReset, children: t("classifier.tryAgain") }) : null,
21444
+ ] }),
21306
21445
  /* @__PURE__ */ jsxs(Button, { variant: "secondary", onClick: handleFullReset, children: [
21307
21446
  /* @__PURE__ */ jsx(Icon, { icon: RotateCcw, size: "sm" }),
21308
21447
  t("classifier.reset")
@@ -25020,7 +25159,7 @@ var init_Flex = __esm({
25020
25159
  basis,
25021
25160
  className,
25022
25161
  children,
25023
- as: Component = "div"
25162
+ as: Component2 = "div"
25024
25163
  }) => {
25025
25164
  const flexStyle = {};
25026
25165
  if (grow !== void 0 || shrink !== void 0 || basis !== void 0) {
@@ -25032,7 +25171,7 @@ var init_Flex = __esm({
25032
25171
  flexStyle.flexBasis = typeof basis === "number" ? `${basis}px` : basis;
25033
25172
  }
25034
25173
  }
25035
- const Comp = Component;
25174
+ const Comp = Component2;
25036
25175
  return /* @__PURE__ */ jsx(
25037
25176
  Comp,
25038
25177
  {
@@ -25152,11 +25291,11 @@ var init_Grid = __esm({
25152
25291
  className,
25153
25292
  style,
25154
25293
  children,
25155
- as: Component = "div"
25294
+ as: Component2 = "div"
25156
25295
  }) => {
25157
25296
  const mergedStyle = rows2 ? { gridTemplateRows: `repeat(${rows2}, minmax(0, 1fr))`, ...style } : style;
25158
25297
  return React83__default.createElement(
25159
- Component,
25298
+ Component2,
25160
25299
  {
25161
25300
  className: cn(
25162
25301
  "grid",
@@ -26982,6 +27121,7 @@ var init_ProgressDots = __esm({
26982
27121
  }
26983
27122
  });
26984
27123
  function StatBadge({
27124
+ assetUrl = DEFAULT_ASSET_URL12,
26985
27125
  label,
26986
27126
  value = 0,
26987
27127
  max,
@@ -27005,7 +27145,17 @@ function StatBadge({
27005
27145
  className
27006
27146
  ),
27007
27147
  children: [
27008
- icon && /* @__PURE__ */ jsx("span", { className: "flex-shrink-0 text-lg", children: typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon, className: "w-4 h-4" }) : /* @__PURE__ */ jsx(Icon, { icon, className: "w-4 h-4" }) }),
27148
+ assetUrl ? /* @__PURE__ */ jsx(
27149
+ "img",
27150
+ {
27151
+ src: assetUrl,
27152
+ alt: "",
27153
+ width: 16,
27154
+ height: 16,
27155
+ style: { imageRendering: "pixelated", objectFit: "contain" },
27156
+ className: "flex-shrink-0"
27157
+ }
27158
+ ) : icon ? /* @__PURE__ */ jsx("span", { className: "flex-shrink-0 text-lg", children: typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon, className: "w-4 h-4" }) : /* @__PURE__ */ jsx(Icon, { icon, className: "w-4 h-4" }) }) : null,
27009
27159
  /* @__PURE__ */ jsx("span", { className: "text-muted-foreground font-medium", children: label }),
27010
27160
  format === "hearts" && max && /* @__PURE__ */ jsx(
27011
27161
  HealthBar,
@@ -27038,7 +27188,7 @@ function StatBadge({
27038
27188
  }
27039
27189
  );
27040
27190
  }
27041
- var sizeMap17, variantMap2;
27191
+ var sizeMap17, variantMap2, DEFAULT_ASSET_URL12;
27042
27192
  var init_StatBadge = __esm({
27043
27193
  "components/game/molecules/StatBadge.tsx"() {
27044
27194
  init_cn();
@@ -27057,6 +27207,7 @@ var init_StatBadge = __esm({
27057
27207
  warning: "bg-warning/15 border-warning/40 text-foreground",
27058
27208
  danger: "bg-error/15 border-error/40 text-foreground"
27059
27209
  };
27210
+ DEFAULT_ASSET_URL12 = "https://almadar-kflow-assets.web.app/shared/effects/particles/light_01.png";
27060
27211
  StatBadge.displayName = "StatBadge";
27061
27212
  }
27062
27213
  });
@@ -27713,6 +27864,7 @@ var init_TurnPanel = __esm({
27713
27864
  }
27714
27865
  });
27715
27866
  function EnemyPlate({
27867
+ assetUrl = DEFAULT_ASSET_URL13,
27716
27868
  name = "Shadow Guard",
27717
27869
  health = 80,
27718
27870
  maxHealth = 100,
@@ -27730,15 +27882,28 @@ function EnemyPlate({
27730
27882
  ),
27731
27883
  children: [
27732
27884
  /* @__PURE__ */ jsxs(Box, { className: "flex items-center justify-between gap-2", children: [
27733
- /* @__PURE__ */ jsx(
27734
- Typography,
27735
- {
27736
- variant: "caption",
27737
- weight: "bold",
27738
- className: "text-[var(--color-foreground)] truncate",
27739
- children: name
27740
- }
27741
- ),
27885
+ /* @__PURE__ */ jsxs(Box, { className: "flex items-center gap-1.5 min-w-0", children: [
27886
+ assetUrl && /* @__PURE__ */ jsx(
27887
+ "img",
27888
+ {
27889
+ src: assetUrl,
27890
+ alt: name,
27891
+ width: 24,
27892
+ height: 24,
27893
+ style: { objectFit: "cover", borderRadius: "50%" },
27894
+ className: "flex-shrink-0"
27895
+ }
27896
+ ),
27897
+ /* @__PURE__ */ jsx(
27898
+ Typography,
27899
+ {
27900
+ variant: "caption",
27901
+ weight: "bold",
27902
+ className: "text-[var(--color-foreground)] truncate",
27903
+ children: name
27904
+ }
27905
+ )
27906
+ ] }),
27742
27907
  level != null && /* @__PURE__ */ jsxs(Badge, { variant: "neutral", size: "sm", children: [
27743
27908
  "Lv.",
27744
27909
  level
@@ -27783,7 +27948,7 @@ function EnemyPlate({
27783
27948
  }
27784
27949
  );
27785
27950
  }
27786
- var effectVariantMap2, DEFAULT_ENEMY_EFFECTS;
27951
+ var effectVariantMap2, DEFAULT_ENEMY_EFFECTS, DEFAULT_ASSET_URL13;
27787
27952
  var init_EnemyPlate = __esm({
27788
27953
  "components/game/molecules/EnemyPlate.tsx"() {
27789
27954
  "use client";
@@ -27800,6 +27965,7 @@ var init_EnemyPlate = __esm({
27800
27965
  DEFAULT_ENEMY_EFFECTS = [
27801
27966
  { icon: "flame", label: "Burn", variant: "debuff" }
27802
27967
  ];
27968
+ DEFAULT_ASSET_URL13 = "https://almadar-kflow-assets.web.app/shared/portraits/shadow-legion.png";
27803
27969
  EnemyPlate.displayName = "EnemyPlate";
27804
27970
  }
27805
27971
  });
@@ -29331,13 +29497,13 @@ var init_MapView = __esm({
29331
29497
  shadowSize: [41, 41]
29332
29498
  });
29333
29499
  L.Marker.prototype.options.icon = defaultIcon;
29334
- const { useEffect: useEffect73, useRef: useRef68, useCallback: useCallback112, useState: useState104 } = React83__default;
29500
+ const { useEffect: useEffect79, useRef: useRef74, useCallback: useCallback118, useState: useState109 } = React83__default;
29335
29501
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
29336
- const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
29502
+ const { useEventBus: useEventBus3 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
29337
29503
  function MapUpdater({ centerLat, centerLng, zoom }) {
29338
29504
  const map = useMap();
29339
- const prevRef = useRef68({ centerLat, centerLng, zoom });
29340
- useEffect73(() => {
29505
+ const prevRef = useRef74({ centerLat, centerLng, zoom });
29506
+ useEffect79(() => {
29341
29507
  const prev = prevRef.current;
29342
29508
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
29343
29509
  map.setView([centerLat, centerLng], zoom);
@@ -29348,7 +29514,7 @@ var init_MapView = __esm({
29348
29514
  }
29349
29515
  function MapClickHandler({ onMapClick }) {
29350
29516
  const map = useMap();
29351
- useEffect73(() => {
29517
+ useEffect79(() => {
29352
29518
  if (!onMapClick) return;
29353
29519
  const handler = (e) => {
29354
29520
  onMapClick(e.latlng.lat, e.latlng.lng);
@@ -29375,9 +29541,9 @@ var init_MapView = __esm({
29375
29541
  className,
29376
29542
  showAttribution = true
29377
29543
  }) {
29378
- const eventBus = useEventBus2();
29379
- const [clickedPosition, setClickedPosition] = useState104(null);
29380
- const handleMapClick = useCallback112((lat, lng) => {
29544
+ const eventBus = useEventBus3();
29545
+ const [clickedPosition, setClickedPosition] = useState109(null);
29546
+ const handleMapClick = useCallback118((lat, lng) => {
29381
29547
  if (showClickedPin) {
29382
29548
  setClickedPosition({ lat, lng });
29383
29549
  }
@@ -29386,7 +29552,7 @@ var init_MapView = __esm({
29386
29552
  eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
29387
29553
  }
29388
29554
  }, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
29389
- const handleMarkerClick = useCallback112((marker) => {
29555
+ const handleMarkerClick = useCallback118((marker) => {
29390
29556
  onMarkerClick?.(marker);
29391
29557
  if (markerClickEvent) {
29392
29558
  eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
@@ -36513,10 +36679,10 @@ var init_Section = __esm({
36513
36679
  children,
36514
36680
  headerClassName,
36515
36681
  contentClassName,
36516
- as: Component = "section"
36682
+ as: Component2 = "section"
36517
36683
  }) => {
36518
36684
  const hasHeader = title || description || action;
36519
- const Comp = Component;
36685
+ const Comp = Component2;
36520
36686
  return /* @__PURE__ */ jsxs(
36521
36687
  Comp,
36522
36688
  {
@@ -38617,44 +38783,27 @@ function DebuggerBoard({
38617
38783
  const { t } = useTranslate();
38618
38784
  const resolved = boardEntity(entity);
38619
38785
  const [headerError, setHeaderError] = useState(false);
38620
- const [showHint, setShowHint] = useState(false);
38621
38786
  const lines = Array.isArray(resolved?.lines) ? resolved.lines : [];
38622
- const result = resolved?.result ?? null;
38787
+ const result = str(resolved?.result) || "none";
38623
38788
  const attempts = num(resolved?.attempts);
38624
- const submitted = result != null;
38789
+ const submitted = result === "win";
38790
+ const showHint = !submitted && attempts >= 2 && Boolean(str(resolved?.hint));
38625
38791
  const bugLines = lines.filter((l) => l.isBug);
38626
38792
  const flaggedLines = lines.filter((l) => l.isFlagged);
38627
38793
  const correctFlags = lines.filter((l) => l.isBug && l.isFlagged);
38628
38794
  const falseFlags = lines.filter((l) => !l.isBug && l.isFlagged);
38629
- const allCorrect = result === "win";
38795
+ const allCorrect = submitted;
38630
38796
  const toggleLine = (lineId) => {
38631
38797
  if (submitted) return;
38632
- if (toggleFlagEvent) {
38633
- emit(`UI:${toggleFlagEvent}`, { lineId });
38634
- }
38798
+ if (toggleFlagEvent) emit(`UI:${toggleFlagEvent}`, { lineId });
38635
38799
  };
38636
38800
  const handleSubmit = useCallback(() => {
38637
- if (checkEvent) {
38638
- emit(`UI:${checkEvent}`, {});
38639
- }
38801
+ if (checkEvent) emit(`UI:${checkEvent}`, {});
38640
38802
  const correct = correctFlags.length === bugLines.length && falseFlags.length === 0;
38641
- if (correct) {
38642
- emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
38643
- }
38803
+ if (correct) emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
38644
38804
  }, [checkEvent, correctFlags.length, bugLines.length, falseFlags.length, attempts, completeEvent, emit]);
38645
38805
  const handleReset = () => {
38646
- if (playAgainEvent) {
38647
- emit(`UI:${playAgainEvent}`, {});
38648
- }
38649
- if (attempts >= 2 && str(resolved?.hint)) {
38650
- setShowHint(true);
38651
- }
38652
- };
38653
- const handleFullReset = () => {
38654
- if (playAgainEvent) {
38655
- emit(`UI:${playAgainEvent}`, {});
38656
- }
38657
- setShowHint(false);
38806
+ if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
38658
38807
  };
38659
38808
  if (!resolved) return null;
38660
38809
  const theme = resolved.theme ?? void 0;
@@ -38678,7 +38827,7 @@ function DebuggerBoard({
38678
38827
  /* @__PURE__ */ jsx(Typography, { variant: "h4", weight: "bold", children: str(resolved.title) })
38679
38828
  ] }),
38680
38829
  /* @__PURE__ */ jsx(Typography, { variant: "body", children: str(resolved.description) }),
38681
- /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-muted-foreground", children: t("debugger.findBugs", { count: String(num(resolved.bugCount)) }) })
38830
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-muted-foreground", children: t("debugger.findBugs", { count: String(lines.filter((l) => l.isBug).length) }) })
38682
38831
  ] }) }),
38683
38832
  /* @__PURE__ */ jsx(Card, { className: "p-0 overflow-hidden", children: /* @__PURE__ */ jsx(VStack, { gap: "none", children: lines.map((line, i) => {
38684
38833
  const isFlagged = !!line.isFlagged;
@@ -38728,11 +38877,11 @@ function DebuggerBoard({
38728
38877
  ] }) }),
38729
38878
  showHint && hint && /* @__PURE__ */ jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsx(Typography, { variant: "body", children: hint }) }),
38730
38879
  /* @__PURE__ */ jsxs(HStack, { gap: "sm", justify: "center", children: [
38731
- !submitted ? /* @__PURE__ */ jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: flaggedLines.length === 0, children: [
38880
+ !submitted && /* @__PURE__ */ jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: flaggedLines.length === 0, children: [
38732
38881
  /* @__PURE__ */ jsx(Icon, { icon: Send, size: "sm" }),
38733
38882
  t("debugger.submit")
38734
- ] }) : !allCorrect ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleReset, children: t("debugger.tryAgain") }) : null,
38735
- /* @__PURE__ */ jsxs(Button, { variant: "secondary", onClick: handleFullReset, children: [
38883
+ ] }),
38884
+ /* @__PURE__ */ jsxs(Button, { variant: "secondary", onClick: handleReset, children: [
38736
38885
  /* @__PURE__ */ jsx(Icon, { icon: RotateCcw, size: "sm" }),
38737
38886
  t("debugger.reset")
38738
38887
  ] })
@@ -39733,39 +39882,40 @@ function EventHandlerBoard({
39733
39882
  stepDurationMs = 800,
39734
39883
  playEvent,
39735
39884
  completeEvent,
39885
+ editRuleEvent,
39886
+ playAgainEvent,
39736
39887
  className
39737
39888
  }) {
39738
39889
  const { emit } = useEventBus();
39739
39890
  const { t } = useTranslate();
39740
39891
  const resolved = boardEntity(entity);
39741
- const entityObjects = rows(resolved?.objects);
39742
- const [objects, setObjects] = useState(() => [...entityObjects]);
39892
+ const objects = rows(resolved?.objects);
39893
+ const entityResult = str(resolved?.result) || "none";
39894
+ const isSuccess = entityResult === "win";
39895
+ const attempts = typeof resolved?.attempts === "number" ? resolved.attempts : 0;
39743
39896
  const [selectedObjectId, setSelectedObjectId] = useState(
39744
- entityObjects[0] ? objId(entityObjects[0]) : null
39897
+ objects[0] ? objId(objects[0]) : null
39745
39898
  );
39746
39899
  const [headerError, setHeaderError] = useState(false);
39747
- const [playState, setPlayState] = useState("editing");
39900
+ const [isPlaying, setIsPlaying] = useState(false);
39748
39901
  const [eventLog, setEventLog] = useState([]);
39749
- const [attempts, setAttempts] = useState(0);
39750
39902
  const timerRef = useRef(null);
39751
39903
  const logIdCounter = useRef(0);
39752
39904
  useEffect(() => () => {
39753
39905
  if (timerRef.current) clearTimeout(timerRef.current);
39754
39906
  }, []);
39755
- const selectedObject = objects.find((o) => objId(o) === selectedObjectId) || null;
39907
+ const selectedObject = objects.find((o) => objId(o) === selectedObjectId) ?? null;
39756
39908
  const handleRulesChange = useCallback((objectId, rules) => {
39757
- setObjects((prev) => prev.map(
39758
- (o) => objId(o) === objectId ? { ...o, rules } : o
39759
- ));
39760
- }, []);
39909
+ if (editRuleEvent) emit(`UI:${editRuleEvent}`, { objectId, rules });
39910
+ }, [editRuleEvent, emit]);
39761
39911
  const addLogEntry = useCallback((icon, message, status = "done") => {
39762
39912
  const id = `log-${logIdCounter.current++}`;
39763
39913
  setEventLog((prev) => [...prev, { id, timestamp: Date.now(), icon, message, status }]);
39764
39914
  }, []);
39765
39915
  const handlePlay = useCallback(() => {
39766
- if (playState !== "editing") return;
39916
+ if (isPlaying || isSuccess) return;
39767
39917
  if (playEvent) emit(`UI:${playEvent}`, {});
39768
- setPlayState("playing");
39918
+ setIsPlaying(true);
39769
39919
  setEventLog([]);
39770
39920
  const allRules = [];
39771
39921
  objects.forEach((obj) => {
@@ -39781,15 +39931,8 @@ function EventHandlerBoard({
39781
39931
  let goalReached = false;
39782
39932
  const processNext = () => {
39783
39933
  if (eventQueue.length === 0 || stepIdx > 20) {
39784
- if (goalReached) {
39785
- setPlayState("success");
39786
- if (completeEvent) {
39787
- emit(`UI:${completeEvent}`, { success: true });
39788
- }
39789
- } else {
39790
- setAttempts((prev) => prev + 1);
39791
- setPlayState("fail");
39792
- }
39934
+ setIsPlaying(false);
39935
+ if (goalReached && completeEvent) emit(`UI:${completeEvent}`, { success: true });
39793
39936
  return;
39794
39937
  }
39795
39938
  const currentEvent = eventQueue.shift();
@@ -39820,21 +39963,19 @@ function EventHandlerBoard({
39820
39963
  addLogEntry("\u{1F3AC}", t("eventHandler.simulationStarted", { events: triggers.join(", ") }), "active");
39821
39964
  }
39822
39965
  timerRef.current = setTimeout(processNext, stepDurationMs);
39823
- }, [playState, objects, resolved, stepDurationMs, playEvent, completeEvent, emit, addLogEntry, t]);
39966
+ }, [isPlaying, isSuccess, objects, resolved, stepDurationMs, playEvent, completeEvent, emit, addLogEntry, t]);
39824
39967
  const handleTryAgain = useCallback(() => {
39825
39968
  if (timerRef.current) clearTimeout(timerRef.current);
39826
- setPlayState("editing");
39969
+ setIsPlaying(false);
39827
39970
  setEventLog([]);
39828
39971
  }, []);
39829
39972
  const handleReset = useCallback(() => {
39830
39973
  if (timerRef.current) clearTimeout(timerRef.current);
39831
- const resetObjects = rows(resolved?.objects);
39832
- setObjects([...resetObjects]);
39833
- setPlayState("editing");
39974
+ setIsPlaying(false);
39834
39975
  setEventLog([]);
39835
- setSelectedObjectId(resetObjects[0] ? objId(resetObjects[0]) : null);
39836
- setAttempts(0);
39837
- }, [resolved?.objects]);
39976
+ setSelectedObjectId(objects[0] ? objId(objects[0]) : null);
39977
+ if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
39978
+ }, [objects, playAgainEvent, emit]);
39838
39979
  if (!resolved) return null;
39839
39980
  const objectViewers = objects.map((obj) => {
39840
39981
  const states = objStates(obj);
@@ -39903,12 +40044,12 @@ function EventHandlerBoard({
39903
40044
  {
39904
40045
  object: selectedObject,
39905
40046
  onRulesChange: handleRulesChange,
39906
- disabled: playState !== "editing"
40047
+ disabled: isPlaying
39907
40048
  }
39908
40049
  ),
39909
40050
  eventLog.length > 0 && /* @__PURE__ */ jsx(EventLog, { entries: eventLog }),
39910
- playState === "success" && /* @__PURE__ */ jsx(Box, { className: "p-4 rounded-container bg-success/20 border border-success text-center", children: /* @__PURE__ */ jsx(Typography, { variant: "h5", className: "text-success", children: str(resolved.successMessage) || t("eventHandler.chainComplete") }) }),
39911
- playState === "fail" && /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
40051
+ isSuccess && /* @__PURE__ */ jsx(Box, { className: "p-4 rounded-container bg-success/20 border border-success text-center", children: /* @__PURE__ */ jsx(Typography, { variant: "h5", className: "text-success", children: str(resolved.successMessage) || t("eventHandler.chainComplete") }) }),
40052
+ !isPlaying && !isSuccess && attempts > 0 && /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
39912
40053
  /* @__PURE__ */ jsx(Box, { className: "p-4 rounded-container bg-warning/10 border border-warning/30 text-center", children: /* @__PURE__ */ jsx(Typography, { variant: "body1", className: "text-foreground font-medium", children: t(encourageKey) }) }),
39913
40054
  showHint && /* @__PURE__ */ jsx(Box, { className: "p-3 rounded-container bg-accent/10 border border-accent/30", children: /* @__PURE__ */ jsxs(HStack, { className: "items-start", gap: "xs", children: [
39914
40055
  /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-accent font-bold shrink-0", children: "\u{1F4A1} " + t("game.hint") + ":" }),
@@ -39916,12 +40057,12 @@ function EventHandlerBoard({
39916
40057
  ] }) })
39917
40058
  ] }),
39918
40059
  /* @__PURE__ */ jsxs(HStack, { gap: "sm", children: [
39919
- playState === "fail" ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleTryAgain, children: "\u{1F504} " + t("puzzle.tryAgainButton") }) : /* @__PURE__ */ jsx(
40060
+ !isPlaying && !isSuccess && attempts > 0 ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleTryAgain, children: "\u{1F504} " + t("puzzle.tryAgainButton") }) : /* @__PURE__ */ jsx(
39920
40061
  Button,
39921
40062
  {
39922
40063
  variant: "primary",
39923
40064
  onClick: handlePlay,
39924
- disabled: playState !== "editing",
40065
+ disabled: isPlaying || isSuccess,
39925
40066
  children: "\u25B6 " + t("game.play")
39926
40067
  }
39927
40068
  ),
@@ -41106,6 +41247,1568 @@ var init_GameAudioToggle = __esm({
41106
41247
  GameAudioToggle.displayName = "GameAudioToggle";
41107
41248
  }
41108
41249
  });
41250
+ function detectAssetRoot(modelUrl) {
41251
+ const idx = modelUrl.indexOf("/3d/");
41252
+ if (idx !== -1) {
41253
+ return modelUrl.substring(0, idx + 4);
41254
+ }
41255
+ return modelUrl.substring(0, modelUrl.lastIndexOf("/") + 1);
41256
+ }
41257
+ function createGLTFLoaderForUrl(url) {
41258
+ const loader = new GLTFLoader();
41259
+ loader.setResourcePath(detectAssetRoot(url));
41260
+ return loader;
41261
+ }
41262
+ var AssetLoader;
41263
+ var init_AssetLoader = __esm({
41264
+ "components/game/molecules/three/loaders/AssetLoader.ts"() {
41265
+ AssetLoader = class {
41266
+ constructor() {
41267
+ __publicField(this, "objLoader");
41268
+ __publicField(this, "textureLoader");
41269
+ __publicField(this, "modelCache");
41270
+ __publicField(this, "textureCache");
41271
+ __publicField(this, "loadingPromises");
41272
+ this.objLoader = new OBJLoader();
41273
+ this.textureLoader = new THREE.TextureLoader();
41274
+ this.modelCache = /* @__PURE__ */ new Map();
41275
+ this.textureCache = /* @__PURE__ */ new Map();
41276
+ this.loadingPromises = /* @__PURE__ */ new Map();
41277
+ }
41278
+ /**
41279
+ * Load a GLB/GLTF model
41280
+ * @param url - URL to the .glb or .gltf file
41281
+ * @returns Promise with loaded model scene and animations
41282
+ */
41283
+ async loadModel(url) {
41284
+ if (this.modelCache.has(url)) {
41285
+ return this.modelCache.get(url);
41286
+ }
41287
+ if (this.loadingPromises.has(url)) {
41288
+ return this.loadingPromises.get(url);
41289
+ }
41290
+ const loader = createGLTFLoaderForUrl(url);
41291
+ const loadPromise = loader.loadAsync(url).then((gltf) => {
41292
+ const result = {
41293
+ scene: gltf.scene,
41294
+ animations: gltf.animations || []
41295
+ };
41296
+ this.modelCache.set(url, result);
41297
+ this.loadingPromises.delete(url);
41298
+ return result;
41299
+ }).catch((error) => {
41300
+ this.loadingPromises.delete(url);
41301
+ throw new Error(`Failed to load model ${url}: ${error.message}`);
41302
+ });
41303
+ this.loadingPromises.set(url, loadPromise);
41304
+ return loadPromise;
41305
+ }
41306
+ /**
41307
+ * Load an OBJ model (fallback for non-GLB assets)
41308
+ * @param url - URL to the .obj file
41309
+ * @returns Promise with loaded object group
41310
+ */
41311
+ async loadOBJ(url) {
41312
+ if (this.modelCache.has(url)) {
41313
+ return this.modelCache.get(url).scene;
41314
+ }
41315
+ if (this.loadingPromises.has(url)) {
41316
+ const result = await this.loadingPromises.get(url);
41317
+ return result.scene;
41318
+ }
41319
+ const loadPromise = this.objLoader.loadAsync(url).then((group) => {
41320
+ const result = {
41321
+ scene: group,
41322
+ animations: []
41323
+ };
41324
+ this.modelCache.set(url, result);
41325
+ this.loadingPromises.delete(url);
41326
+ return result;
41327
+ }).catch((error) => {
41328
+ this.loadingPromises.delete(url);
41329
+ throw new Error(`Failed to load OBJ ${url}: ${error.message}`);
41330
+ });
41331
+ this.loadingPromises.set(url, loadPromise);
41332
+ return (await loadPromise).scene;
41333
+ }
41334
+ /**
41335
+ * Load a texture
41336
+ * @param url - URL to the texture image
41337
+ * @returns Promise with loaded texture
41338
+ */
41339
+ async loadTexture(url) {
41340
+ if (this.textureCache.has(url)) {
41341
+ return this.textureCache.get(url);
41342
+ }
41343
+ if (this.loadingPromises.has(`texture:${url}`)) {
41344
+ return this.loadingPromises.get(`texture:${url}`);
41345
+ }
41346
+ const loadPromise = this.textureLoader.loadAsync(url).then((texture) => {
41347
+ texture.colorSpace = THREE.SRGBColorSpace;
41348
+ this.textureCache.set(url, texture);
41349
+ this.loadingPromises.delete(`texture:${url}`);
41350
+ return texture;
41351
+ }).catch((error) => {
41352
+ this.loadingPromises.delete(`texture:${url}`);
41353
+ throw new Error(`Failed to load texture ${url}: ${error.message}`);
41354
+ });
41355
+ this.loadingPromises.set(`texture:${url}`, loadPromise);
41356
+ return loadPromise;
41357
+ }
41358
+ /**
41359
+ * Preload multiple assets
41360
+ * @param urls - Array of asset URLs to preload
41361
+ * @returns Promise that resolves when all assets are loaded
41362
+ */
41363
+ async preload(urls) {
41364
+ const promises = urls.map((url) => {
41365
+ if (url.endsWith(".glb") || url.endsWith(".gltf")) {
41366
+ return this.loadModel(url).catch(() => null);
41367
+ } else if (url.endsWith(".obj")) {
41368
+ return this.loadOBJ(url).catch(() => null);
41369
+ } else if (/\.(png|jpg|jpeg|webp)$/i.test(url)) {
41370
+ return this.loadTexture(url).catch(() => null);
41371
+ }
41372
+ return Promise.resolve(null);
41373
+ });
41374
+ await Promise.all(promises);
41375
+ }
41376
+ /**
41377
+ * Check if a model is cached
41378
+ * @param url - Model URL
41379
+ */
41380
+ hasModel(url) {
41381
+ return this.modelCache.has(url);
41382
+ }
41383
+ /**
41384
+ * Check if a texture is cached
41385
+ * @param url - Texture URL
41386
+ */
41387
+ hasTexture(url) {
41388
+ return this.textureCache.has(url);
41389
+ }
41390
+ /**
41391
+ * Get cached model (throws if not cached)
41392
+ * @param url - Model URL
41393
+ */
41394
+ getModel(url) {
41395
+ const model = this.modelCache.get(url);
41396
+ if (!model) {
41397
+ throw new Error(`Model ${url} not in cache`);
41398
+ }
41399
+ return model;
41400
+ }
41401
+ /**
41402
+ * Get cached texture (throws if not cached)
41403
+ * @param url - Texture URL
41404
+ */
41405
+ getTexture(url) {
41406
+ const texture = this.textureCache.get(url);
41407
+ if (!texture) {
41408
+ throw new Error(`Texture ${url} not in cache`);
41409
+ }
41410
+ return texture;
41411
+ }
41412
+ /**
41413
+ * Clear all caches
41414
+ */
41415
+ clearCache() {
41416
+ this.textureCache.forEach((texture) => {
41417
+ texture.dispose();
41418
+ });
41419
+ this.modelCache.forEach((model) => {
41420
+ model.scene.traverse((child) => {
41421
+ if (child instanceof THREE.Mesh) {
41422
+ child.geometry.dispose();
41423
+ if (Array.isArray(child.material)) {
41424
+ child.material.forEach((m) => m.dispose());
41425
+ } else {
41426
+ child.material.dispose();
41427
+ }
41428
+ }
41429
+ });
41430
+ });
41431
+ this.modelCache.clear();
41432
+ this.textureCache.clear();
41433
+ this.loadingPromises.clear();
41434
+ }
41435
+ /**
41436
+ * Get cache statistics
41437
+ */
41438
+ getStats() {
41439
+ return {
41440
+ models: this.modelCache.size,
41441
+ textures: this.textureCache.size,
41442
+ loading: this.loadingPromises.size
41443
+ };
41444
+ }
41445
+ };
41446
+ new AssetLoader();
41447
+ }
41448
+ });
41449
+ function useAssetLoader(options = {}) {
41450
+ const { preloadUrls = [], loader: customLoader } = options;
41451
+ const loaderRef = useRef(customLoader || new AssetLoader());
41452
+ const [state, setState] = useState({
41453
+ isLoading: false,
41454
+ progress: 0,
41455
+ loaded: 0,
41456
+ total: 0,
41457
+ errors: []
41458
+ });
41459
+ useEffect(() => {
41460
+ if (preloadUrls.length > 0) {
41461
+ preload(preloadUrls);
41462
+ }
41463
+ }, []);
41464
+ const updateProgress = useCallback((loaded, total) => {
41465
+ setState((prev) => ({
41466
+ ...prev,
41467
+ loaded,
41468
+ total,
41469
+ progress: total > 0 ? Math.round(loaded / total * 100) : 0
41470
+ }));
41471
+ }, []);
41472
+ const loadModel = useCallback(
41473
+ async (url) => {
41474
+ setState((prev) => ({ ...prev, isLoading: true }));
41475
+ try {
41476
+ const model = await loaderRef.current.loadModel(url);
41477
+ setState((prev) => ({
41478
+ ...prev,
41479
+ isLoading: false,
41480
+ loaded: prev.loaded + 1
41481
+ }));
41482
+ return model;
41483
+ } catch (error) {
41484
+ const errorMsg = error instanceof Error ? error.message : String(error);
41485
+ setState((prev) => ({
41486
+ ...prev,
41487
+ isLoading: false,
41488
+ errors: [...prev.errors, errorMsg]
41489
+ }));
41490
+ throw error;
41491
+ }
41492
+ },
41493
+ []
41494
+ );
41495
+ const loadOBJ = useCallback(
41496
+ async (url) => {
41497
+ setState((prev) => ({ ...prev, isLoading: true }));
41498
+ try {
41499
+ const model = await loaderRef.current.loadOBJ(url);
41500
+ setState((prev) => ({
41501
+ ...prev,
41502
+ isLoading: false,
41503
+ loaded: prev.loaded + 1
41504
+ }));
41505
+ return model;
41506
+ } catch (error) {
41507
+ const errorMsg = error instanceof Error ? error.message : String(error);
41508
+ setState((prev) => ({
41509
+ ...prev,
41510
+ isLoading: false,
41511
+ errors: [...prev.errors, errorMsg]
41512
+ }));
41513
+ throw error;
41514
+ }
41515
+ },
41516
+ []
41517
+ );
41518
+ const loadTexture = useCallback(
41519
+ async (url) => {
41520
+ setState((prev) => ({ ...prev, isLoading: true }));
41521
+ try {
41522
+ const texture = await loaderRef.current.loadTexture(url);
41523
+ setState((prev) => ({
41524
+ ...prev,
41525
+ isLoading: false,
41526
+ loaded: prev.loaded + 1
41527
+ }));
41528
+ return texture;
41529
+ } catch (error) {
41530
+ const errorMsg = error instanceof Error ? error.message : String(error);
41531
+ setState((prev) => ({
41532
+ ...prev,
41533
+ isLoading: false,
41534
+ errors: [...prev.errors, errorMsg]
41535
+ }));
41536
+ throw error;
41537
+ }
41538
+ },
41539
+ []
41540
+ );
41541
+ const preload = useCallback(
41542
+ async (urls) => {
41543
+ setState((prev) => ({
41544
+ ...prev,
41545
+ isLoading: true,
41546
+ total: urls.length,
41547
+ loaded: 0,
41548
+ errors: []
41549
+ }));
41550
+ let completed = 0;
41551
+ const errors = [];
41552
+ await Promise.all(
41553
+ urls.map(async (url) => {
41554
+ try {
41555
+ if (url.endsWith(".glb") || url.endsWith(".gltf")) {
41556
+ await loaderRef.current.loadModel(url);
41557
+ } else if (url.endsWith(".obj")) {
41558
+ await loaderRef.current.loadOBJ(url);
41559
+ } else if (/\.(png|jpg|jpeg|webp)$/i.test(url)) {
41560
+ await loaderRef.current.loadTexture(url);
41561
+ }
41562
+ completed++;
41563
+ updateProgress(completed, urls.length);
41564
+ } catch (error) {
41565
+ const errorMsg = error instanceof Error ? error.message : String(error);
41566
+ errors.push(`${url}: ${errorMsg}`);
41567
+ completed++;
41568
+ updateProgress(completed, urls.length);
41569
+ }
41570
+ })
41571
+ );
41572
+ setState((prev) => ({
41573
+ ...prev,
41574
+ isLoading: false,
41575
+ errors
41576
+ }));
41577
+ },
41578
+ [updateProgress]
41579
+ );
41580
+ const hasModel = useCallback((url) => {
41581
+ return loaderRef.current.hasModel(url);
41582
+ }, []);
41583
+ const hasTexture = useCallback((url) => {
41584
+ return loaderRef.current.hasTexture(url);
41585
+ }, []);
41586
+ const getModel = useCallback((url) => {
41587
+ try {
41588
+ return loaderRef.current.getModel(url);
41589
+ } catch {
41590
+ return void 0;
41591
+ }
41592
+ }, []);
41593
+ const getTexture = useCallback((url) => {
41594
+ try {
41595
+ return loaderRef.current.getTexture(url);
41596
+ } catch {
41597
+ return void 0;
41598
+ }
41599
+ }, []);
41600
+ const clearCache = useCallback(() => {
41601
+ loaderRef.current.clearCache();
41602
+ setState({
41603
+ isLoading: false,
41604
+ progress: 0,
41605
+ loaded: 0,
41606
+ total: 0,
41607
+ errors: []
41608
+ });
41609
+ }, []);
41610
+ return {
41611
+ ...state,
41612
+ loadModel,
41613
+ loadOBJ,
41614
+ loadTexture,
41615
+ preload,
41616
+ hasModel,
41617
+ hasTexture,
41618
+ getModel,
41619
+ getTexture,
41620
+ clearCache
41621
+ };
41622
+ }
41623
+ var init_useAssetLoader = __esm({
41624
+ "components/game/molecules/three/hooks/useAssetLoader.ts"() {
41625
+ "use client";
41626
+ init_AssetLoader();
41627
+ }
41628
+ });
41629
+ function useGameCanvas3DEvents(options) {
41630
+ const {
41631
+ tileClickEvent,
41632
+ unitClickEvent,
41633
+ featureClickEvent,
41634
+ canvasClickEvent,
41635
+ tileHoverEvent,
41636
+ tileLeaveEvent,
41637
+ unitAnimationEvent,
41638
+ cameraChangeEvent,
41639
+ onTileClick,
41640
+ onUnitClick,
41641
+ onFeatureClick,
41642
+ onCanvasClick,
41643
+ onTileHover,
41644
+ onUnitAnimation
41645
+ } = options;
41646
+ const emit = useEmitEvent();
41647
+ const optionsRef = useRef(options);
41648
+ optionsRef.current = options;
41649
+ const handleTileClick = useCallback(
41650
+ (tile, event) => {
41651
+ if (tileClickEvent) {
41652
+ emit(tileClickEvent, {
41653
+ tileId: tile.id,
41654
+ x: tile.x,
41655
+ z: tile.z ?? tile.y ?? 0,
41656
+ type: tile.type,
41657
+ terrain: tile.terrain,
41658
+ elevation: tile.elevation
41659
+ });
41660
+ }
41661
+ optionsRef.current.onTileClick?.(tile, event);
41662
+ },
41663
+ [tileClickEvent, emit]
41664
+ );
41665
+ const handleUnitClick = useCallback(
41666
+ (unit, event) => {
41667
+ if (unitClickEvent) {
41668
+ emit(unitClickEvent, {
41669
+ unitId: unit.id,
41670
+ x: unit.x,
41671
+ z: unit.z ?? unit.y ?? 0,
41672
+ unitType: unit.unitType,
41673
+ name: unit.name,
41674
+ team: unit.team,
41675
+ faction: unit.faction,
41676
+ health: unit.health,
41677
+ maxHealth: unit.maxHealth
41678
+ });
41679
+ }
41680
+ optionsRef.current.onUnitClick?.(unit, event);
41681
+ },
41682
+ [unitClickEvent, emit]
41683
+ );
41684
+ const handleFeatureClick = useCallback(
41685
+ (feature, event) => {
41686
+ if (featureClickEvent) {
41687
+ emit(featureClickEvent, {
41688
+ featureId: feature.id,
41689
+ x: feature.x,
41690
+ z: feature.z ?? feature.y ?? 0,
41691
+ type: feature.type,
41692
+ elevation: feature.elevation
41693
+ });
41694
+ }
41695
+ optionsRef.current.onFeatureClick?.(feature, event);
41696
+ },
41697
+ [featureClickEvent, emit]
41698
+ );
41699
+ const handleCanvasClick = useCallback(
41700
+ (event) => {
41701
+ if (canvasClickEvent) {
41702
+ emit(canvasClickEvent, {
41703
+ clientX: event.clientX,
41704
+ clientY: event.clientY,
41705
+ button: event.button
41706
+ });
41707
+ }
41708
+ optionsRef.current.onCanvasClick?.(event);
41709
+ },
41710
+ [canvasClickEvent, emit]
41711
+ );
41712
+ const handleTileHover = useCallback(
41713
+ (tile, event) => {
41714
+ if (tile) {
41715
+ if (tileHoverEvent) {
41716
+ emit(tileHoverEvent, {
41717
+ tileId: tile.id,
41718
+ x: tile.x,
41719
+ z: tile.z ?? tile.y ?? 0,
41720
+ type: tile.type
41721
+ });
41722
+ }
41723
+ } else {
41724
+ if (tileLeaveEvent) {
41725
+ emit(tileLeaveEvent, {});
41726
+ }
41727
+ }
41728
+ optionsRef.current.onTileHover?.(tile, event);
41729
+ },
41730
+ [tileHoverEvent, tileLeaveEvent, emit]
41731
+ );
41732
+ const handleUnitAnimation = useCallback(
41733
+ (unitId, state) => {
41734
+ if (unitAnimationEvent) {
41735
+ emit(unitAnimationEvent, {
41736
+ unitId,
41737
+ state,
41738
+ timestamp: Date.now()
41739
+ });
41740
+ }
41741
+ optionsRef.current.onUnitAnimation?.(unitId, state);
41742
+ },
41743
+ [unitAnimationEvent, emit]
41744
+ );
41745
+ const handleCameraChange = useCallback(
41746
+ (position) => {
41747
+ if (cameraChangeEvent) {
41748
+ emit(cameraChangeEvent, {
41749
+ position,
41750
+ timestamp: Date.now()
41751
+ });
41752
+ }
41753
+ },
41754
+ [cameraChangeEvent, emit]
41755
+ );
41756
+ return {
41757
+ handleTileClick,
41758
+ handleUnitClick,
41759
+ handleFeatureClick,
41760
+ handleCanvasClick,
41761
+ handleTileHover,
41762
+ handleUnitAnimation,
41763
+ handleCameraChange
41764
+ };
41765
+ }
41766
+ var init_useGameCanvas3DEvents = __esm({
41767
+ "components/game/molecules/three/hooks/useGameCanvas3DEvents.ts"() {
41768
+ "use client";
41769
+ init_useEventBus();
41770
+ }
41771
+ });
41772
+
41773
+ // components/game/molecules/three/components/Canvas3DLoadingState.css
41774
+ var init_Canvas3DLoadingState = __esm({
41775
+ "components/game/molecules/three/components/Canvas3DLoadingState.css"() {
41776
+ }
41777
+ });
41778
+ function Canvas3DLoadingState({
41779
+ progress = 0,
41780
+ loaded = 0,
41781
+ total = 0,
41782
+ message = "Loading 3D Scene...",
41783
+ details,
41784
+ showSpinner = true,
41785
+ className
41786
+ }) {
41787
+ const clampedProgress = Math.max(0, Math.min(100, progress));
41788
+ const hasProgress = total > 0;
41789
+ return /* @__PURE__ */ jsxs("div", { className: `canvas-3d-loading ${className || ""}`, children: [
41790
+ /* @__PURE__ */ jsxs("div", { className: "canvas-3d-loading__content", children: [
41791
+ showSpinner && /* @__PURE__ */ jsxs("div", { className: "canvas-3d-loading__spinner", children: [
41792
+ /* @__PURE__ */ jsx("div", { className: "spinner__ring" }),
41793
+ /* @__PURE__ */ jsx("div", { className: "spinner__ring spinner__ring--secondary" })
41794
+ ] }),
41795
+ /* @__PURE__ */ jsx("div", { className: "canvas-3d-loading__message", children: message }),
41796
+ details && /* @__PURE__ */ jsx("div", { className: "canvas-3d-loading__details", children: details }),
41797
+ hasProgress && /* @__PURE__ */ jsxs("div", { className: "canvas-3d-loading__progress", children: [
41798
+ /* @__PURE__ */ jsx("div", { className: "progress__bar", children: /* @__PURE__ */ jsx(
41799
+ "div",
41800
+ {
41801
+ className: "progress__fill",
41802
+ style: { width: `${clampedProgress}%` }
41803
+ }
41804
+ ) }),
41805
+ /* @__PURE__ */ jsxs("div", { className: "progress__text", children: [
41806
+ /* @__PURE__ */ jsxs("span", { className: "progress__percentage", children: [
41807
+ clampedProgress,
41808
+ "%"
41809
+ ] }),
41810
+ /* @__PURE__ */ jsxs("span", { className: "progress__count", children: [
41811
+ "(",
41812
+ loaded,
41813
+ "/",
41814
+ total,
41815
+ ")"
41816
+ ] })
41817
+ ] })
41818
+ ] })
41819
+ ] }),
41820
+ /* @__PURE__ */ jsx("div", { className: "canvas-3d-loading__background", children: /* @__PURE__ */ jsx("div", { className: "bg__grid" }) })
41821
+ ] });
41822
+ }
41823
+ var init_Canvas3DLoadingState2 = __esm({
41824
+ "components/game/molecules/three/components/Canvas3DLoadingState.tsx"() {
41825
+ init_Canvas3DLoadingState();
41826
+ }
41827
+ });
41828
+
41829
+ // components/game/molecules/three/components/Canvas3DErrorBoundary.css
41830
+ var init_Canvas3DErrorBoundary = __esm({
41831
+ "components/game/molecules/three/components/Canvas3DErrorBoundary.css"() {
41832
+ }
41833
+ });
41834
+ var log11, Canvas3DErrorBoundary;
41835
+ var init_Canvas3DErrorBoundary2 = __esm({
41836
+ "components/game/molecules/three/components/Canvas3DErrorBoundary.tsx"() {
41837
+ init_Canvas3DErrorBoundary();
41838
+ log11 = createLogger("almadar:ui:game:canvas3d:error-boundary");
41839
+ Canvas3DErrorBoundary = class extends Component {
41840
+ constructor(props) {
41841
+ super(props);
41842
+ __publicField(this, "handleReset", () => {
41843
+ this.setState({
41844
+ hasError: false,
41845
+ error: null,
41846
+ errorInfo: null
41847
+ });
41848
+ this.props.onReset?.();
41849
+ });
41850
+ this.state = {
41851
+ hasError: false,
41852
+ error: null,
41853
+ errorInfo: null
41854
+ };
41855
+ }
41856
+ static getDerivedStateFromError(error) {
41857
+ return {
41858
+ hasError: true,
41859
+ error,
41860
+ errorInfo: null
41861
+ };
41862
+ }
41863
+ componentDidCatch(error, errorInfo) {
41864
+ this.setState({ errorInfo });
41865
+ this.props.onError?.(error, errorInfo);
41866
+ log11.error("Error caught", { error });
41867
+ log11.error("Component stack", { componentStack: errorInfo.componentStack ?? "<none>" });
41868
+ }
41869
+ render() {
41870
+ if (this.state.hasError) {
41871
+ if (this.props.fallback) {
41872
+ return this.props.fallback;
41873
+ }
41874
+ return /* @__PURE__ */ jsx("div", { className: "canvas-3d-error", children: /* @__PURE__ */ jsxs("div", { className: "canvas-3d-error__content", children: [
41875
+ /* @__PURE__ */ jsx("div", { className: "canvas-3d-error__icon", children: "\u26A0\uFE0F" }),
41876
+ /* @__PURE__ */ jsx("h2", { className: "canvas-3d-error__title", children: "3D Scene Error" }),
41877
+ /* @__PURE__ */ jsx("p", { className: "canvas-3d-error__message", children: "Something went wrong while rendering the 3D scene." }),
41878
+ this.state.error && /* @__PURE__ */ jsxs("details", { className: "canvas-3d-error__details", children: [
41879
+ /* @__PURE__ */ jsx("summary", { children: "Error Details" }),
41880
+ /* @__PURE__ */ jsxs("pre", { className: "error__stack", children: [
41881
+ this.state.error.message,
41882
+ "\n",
41883
+ this.state.error.stack
41884
+ ] }),
41885
+ this.state.errorInfo && /* @__PURE__ */ jsx("pre", { className: "error__component-stack", children: this.state.errorInfo.componentStack })
41886
+ ] }),
41887
+ /* @__PURE__ */ jsxs("div", { className: "canvas-3d-error__actions", children: [
41888
+ /* @__PURE__ */ jsx(
41889
+ "button",
41890
+ {
41891
+ className: "error__button error__button--primary",
41892
+ onClick: this.handleReset,
41893
+ children: "Try Again"
41894
+ }
41895
+ ),
41896
+ /* @__PURE__ */ jsx(
41897
+ "button",
41898
+ {
41899
+ className: "error__button error__button--secondary",
41900
+ onClick: () => window.location.reload(),
41901
+ children: "Reload Page"
41902
+ }
41903
+ )
41904
+ ] })
41905
+ ] }) });
41906
+ }
41907
+ return this.props.children;
41908
+ }
41909
+ };
41910
+ }
41911
+ });
41912
+ function detectAssetRoot2(modelUrl) {
41913
+ const idx = modelUrl.indexOf("/3d/");
41914
+ if (idx !== -1) {
41915
+ return modelUrl.substring(0, idx + 4);
41916
+ }
41917
+ return modelUrl.substring(0, modelUrl.lastIndexOf("/") + 1);
41918
+ }
41919
+ function useGLTFModel(url, resourceBasePath) {
41920
+ const [state, setState] = useState({
41921
+ model: null,
41922
+ isLoading: false,
41923
+ error: null
41924
+ });
41925
+ useEffect(() => {
41926
+ if (!url) {
41927
+ setState({ model: null, isLoading: false, error: null });
41928
+ return;
41929
+ }
41930
+ log12.debug("Loading", { url });
41931
+ setState((prev) => ({ ...prev, isLoading: true, error: null }));
41932
+ const assetRoot = resourceBasePath || detectAssetRoot2(url);
41933
+ const loader = new GLTFLoader$1();
41934
+ loader.setResourcePath(assetRoot);
41935
+ loader.load(
41936
+ url,
41937
+ (gltf) => {
41938
+ log12.debug("Loaded", { url });
41939
+ setState({
41940
+ model: gltf.scene,
41941
+ isLoading: false,
41942
+ error: null
41943
+ });
41944
+ },
41945
+ void 0,
41946
+ (err) => {
41947
+ log12.warn("Failed", { url, error: err instanceof Error ? err : String(err) });
41948
+ setState({
41949
+ model: null,
41950
+ isLoading: false,
41951
+ error: err instanceof Error ? err : new Error(String(err))
41952
+ });
41953
+ }
41954
+ );
41955
+ }, [url, resourceBasePath]);
41956
+ return state;
41957
+ }
41958
+ function ModelLoader({
41959
+ url,
41960
+ position = [0, 0, 0],
41961
+ scale = 1,
41962
+ rotation = [0, 0, 0],
41963
+ isSelected = false,
41964
+ isHovered = false,
41965
+ onClick,
41966
+ onHover,
41967
+ fallbackGeometry = "box",
41968
+ castShadow = true,
41969
+ receiveShadow = true,
41970
+ resourceBasePath
41971
+ }) {
41972
+ const { model: loadedModel, isLoading, error } = useGLTFModel(url, resourceBasePath);
41973
+ const model = useMemo(() => {
41974
+ if (!loadedModel) return null;
41975
+ const cloned = loadedModel.clone();
41976
+ cloned.traverse((child) => {
41977
+ if (child instanceof THREE.Mesh) {
41978
+ child.castShadow = castShadow;
41979
+ child.receiveShadow = receiveShadow;
41980
+ }
41981
+ });
41982
+ return cloned;
41983
+ }, [loadedModel, castShadow, receiveShadow]);
41984
+ const scaleArray = useMemo(() => {
41985
+ if (typeof scale === "number") {
41986
+ return [scale, scale, scale];
41987
+ }
41988
+ return scale;
41989
+ }, [scale]);
41990
+ const rotationRad = useMemo(() => {
41991
+ return [
41992
+ rotation[0] * Math.PI / 180,
41993
+ rotation[1] * Math.PI / 180,
41994
+ rotation[2] * Math.PI / 180
41995
+ ];
41996
+ }, [rotation]);
41997
+ if (isLoading) {
41998
+ return /* @__PURE__ */ jsx("group", { position, children: /* @__PURE__ */ jsxs("mesh", { rotation: [Math.PI / 2, 0, 0], children: [
41999
+ /* @__PURE__ */ jsx("ringGeometry", { args: [0.3, 0.35, 16] }),
42000
+ /* @__PURE__ */ jsx("meshBasicMaterial", { color: "#4a90d9", transparent: true, opacity: 0.8 })
42001
+ ] }) });
42002
+ }
42003
+ if (error || !model) {
42004
+ if (fallbackGeometry === "none") {
42005
+ return /* @__PURE__ */ jsx("group", { position });
42006
+ }
42007
+ const fallbackProps = {
42008
+ onClick,
42009
+ onPointerOver: () => onHover?.(true),
42010
+ onPointerOut: () => onHover?.(false)
42011
+ };
42012
+ return /* @__PURE__ */ jsxs("group", { position, children: [
42013
+ (isSelected || isHovered) && /* @__PURE__ */ jsxs("mesh", { position: [0, 0.02, 0], rotation: [-Math.PI / 2, 0, 0], children: [
42014
+ /* @__PURE__ */ jsx("ringGeometry", { args: [0.6, 0.7, 32] }),
42015
+ /* @__PURE__ */ jsx(
42016
+ "meshBasicMaterial",
42017
+ {
42018
+ color: isSelected ? 16755200 : 16777215,
42019
+ transparent: true,
42020
+ opacity: 0.5
42021
+ }
42022
+ )
42023
+ ] }),
42024
+ fallbackGeometry === "box" && /* @__PURE__ */ jsxs("mesh", { ...fallbackProps, position: [0, 0.5, 0], children: [
42025
+ /* @__PURE__ */ jsx("boxGeometry", { args: [0.8, 0.8, 0.8] }),
42026
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color: error ? 16729156 : 8947848 })
42027
+ ] }),
42028
+ fallbackGeometry === "sphere" && /* @__PURE__ */ jsxs("mesh", { ...fallbackProps, position: [0, 0.5, 0], children: [
42029
+ /* @__PURE__ */ jsx("sphereGeometry", { args: [0.4, 16, 16] }),
42030
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color: error ? 16729156 : 8947848 })
42031
+ ] }),
42032
+ fallbackGeometry === "cylinder" && /* @__PURE__ */ jsxs("mesh", { ...fallbackProps, position: [0, 0.5, 0], children: [
42033
+ /* @__PURE__ */ jsx("cylinderGeometry", { args: [0.3, 0.3, 0.8, 16] }),
42034
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color: error ? 16729156 : 8947848 })
42035
+ ] })
42036
+ ] });
42037
+ }
42038
+ return /* @__PURE__ */ jsxs(
42039
+ "group",
42040
+ {
42041
+ position,
42042
+ rotation: rotationRad,
42043
+ onClick,
42044
+ onPointerOver: () => onHover?.(true),
42045
+ onPointerOut: () => onHover?.(false),
42046
+ children: [
42047
+ (isSelected || isHovered) && /* @__PURE__ */ jsxs("mesh", { position: [0, 0.02, 0], rotation: [-Math.PI / 2, 0, 0], children: [
42048
+ /* @__PURE__ */ jsx("ringGeometry", { args: [0.6, 0.7, 32] }),
42049
+ /* @__PURE__ */ jsx(
42050
+ "meshBasicMaterial",
42051
+ {
42052
+ color: isSelected ? 16755200 : 16777215,
42053
+ transparent: true,
42054
+ opacity: 0.5
42055
+ }
42056
+ )
42057
+ ] }),
42058
+ /* @__PURE__ */ jsx("primitive", { object: model, scale: scaleArray })
42059
+ ]
42060
+ }
42061
+ );
42062
+ }
42063
+ var log12;
42064
+ var init_ModelLoader = __esm({
42065
+ "components/game/molecules/three/components/ModelLoader.tsx"() {
42066
+ "use client";
42067
+ log12 = createLogger("almadar:ui:game:model-loader");
42068
+ }
42069
+ });
42070
+
42071
+ // components/game/molecules/GameCanvas3D.css
42072
+ var init_GameCanvas3D = __esm({
42073
+ "components/game/molecules/GameCanvas3D.css"() {
42074
+ }
42075
+ });
42076
+ function CameraController({
42077
+ onCameraChange
42078
+ }) {
42079
+ const { camera } = useThree();
42080
+ useEffect(() => {
42081
+ if (onCameraChange) {
42082
+ onCameraChange({
42083
+ x: camera.position.x,
42084
+ y: camera.position.y,
42085
+ z: camera.position.z
42086
+ });
42087
+ }
42088
+ }, [camera.position, onCameraChange]);
42089
+ return null;
42090
+ }
42091
+ var DEFAULT_GRID_CONFIG, GameCanvas3D;
42092
+ var init_GameCanvas3D2 = __esm({
42093
+ "components/game/molecules/GameCanvas3D.tsx"() {
42094
+ "use client";
42095
+ init_AssetLoader();
42096
+ init_useAssetLoader();
42097
+ init_useGameCanvas3DEvents();
42098
+ init_Canvas3DLoadingState2();
42099
+ init_Canvas3DErrorBoundary2();
42100
+ init_ModelLoader();
42101
+ init_cn();
42102
+ init_GameCanvas3D();
42103
+ DEFAULT_GRID_CONFIG = {
42104
+ cellSize: 1,
42105
+ offsetX: 0,
42106
+ offsetZ: 0
42107
+ };
42108
+ GameCanvas3D = forwardRef(
42109
+ ({
42110
+ tiles = [],
42111
+ units = [],
42112
+ features = [],
42113
+ events: events2 = [],
42114
+ orientation = "standard",
42115
+ cameraMode = "isometric",
42116
+ showGrid = true,
42117
+ showCoordinates = false,
42118
+ showTileInfo = false,
42119
+ overlay = "default",
42120
+ shadows = true,
42121
+ backgroundColor = "#1a1a2e",
42122
+ onTileClick,
42123
+ onUnitClick,
42124
+ onFeatureClick,
42125
+ onCanvasClick,
42126
+ onTileHover,
42127
+ onUnitAnimation,
42128
+ assetLoader: customAssetLoader,
42129
+ tileRenderer: CustomTileRenderer,
42130
+ unitRenderer: CustomUnitRenderer,
42131
+ featureRenderer: CustomFeatureRenderer,
42132
+ className,
42133
+ isLoading: externalLoading,
42134
+ error: externalError,
42135
+ entity,
42136
+ preloadAssets = [],
42137
+ tileClickEvent,
42138
+ unitClickEvent,
42139
+ featureClickEvent,
42140
+ canvasClickEvent,
42141
+ tileHoverEvent,
42142
+ tileLeaveEvent,
42143
+ unitAnimationEvent,
42144
+ cameraChangeEvent,
42145
+ loadingMessage = "Loading 3D Scene...",
42146
+ useInstancing = true,
42147
+ validMoves = [],
42148
+ attackTargets = [],
42149
+ selectedTileIds = [],
42150
+ selectedUnitId = null,
42151
+ children
42152
+ }, ref) => {
42153
+ const containerRef = useRef(null);
42154
+ const controlsRef = useRef(null);
42155
+ const [hoveredTile, setHoveredTile] = useState(null);
42156
+ const [internalError, setInternalError] = useState(null);
42157
+ const { isLoading: assetsLoading, progress, loaded, total } = useAssetLoader({
42158
+ preloadUrls: preloadAssets,
42159
+ loader: customAssetLoader
42160
+ });
42161
+ const eventHandlers = useGameCanvas3DEvents({
42162
+ tileClickEvent,
42163
+ unitClickEvent,
42164
+ featureClickEvent,
42165
+ canvasClickEvent,
42166
+ tileHoverEvent,
42167
+ tileLeaveEvent,
42168
+ unitAnimationEvent,
42169
+ cameraChangeEvent,
42170
+ onTileClick,
42171
+ onUnitClick,
42172
+ onFeatureClick,
42173
+ onCanvasClick,
42174
+ onTileHover,
42175
+ onUnitAnimation
42176
+ });
42177
+ const gridBounds = useMemo(() => {
42178
+ if (tiles.length === 0) {
42179
+ return { minX: 0, maxX: 10, minZ: 0, maxZ: 10 };
42180
+ }
42181
+ const xs = tiles.map((t) => t.x);
42182
+ const zs = tiles.map((t) => t.z || t.y || 0);
42183
+ return {
42184
+ minX: Math.min(...xs),
42185
+ maxX: Math.max(...xs),
42186
+ minZ: Math.min(...zs),
42187
+ maxZ: Math.max(...zs)
42188
+ };
42189
+ }, [tiles]);
42190
+ const cameraTarget = useMemo(() => {
42191
+ return [
42192
+ (gridBounds.minX + gridBounds.maxX) / 2,
42193
+ 0,
42194
+ (gridBounds.minZ + gridBounds.maxZ) / 2
42195
+ ];
42196
+ }, [gridBounds]);
42197
+ const gridConfig = useMemo(
42198
+ () => ({
42199
+ ...DEFAULT_GRID_CONFIG,
42200
+ offsetX: -(gridBounds.maxX - gridBounds.minX) / 2,
42201
+ offsetZ: -(gridBounds.maxZ - gridBounds.minZ) / 2
42202
+ }),
42203
+ [gridBounds]
42204
+ );
42205
+ const gridToWorld = useCallback(
42206
+ (x, z, y = 0) => {
42207
+ const worldX = (x - gridBounds.minX) * gridConfig.cellSize;
42208
+ const worldZ = (z - gridBounds.minZ) * gridConfig.cellSize;
42209
+ return [worldX, y * gridConfig.cellSize, worldZ];
42210
+ },
42211
+ [gridBounds, gridConfig]
42212
+ );
42213
+ useImperativeHandle(ref, () => ({
42214
+ getCameraPosition: () => {
42215
+ if (controlsRef.current) {
42216
+ const pos = controlsRef.current.object.position;
42217
+ return new THREE.Vector3(pos.x, pos.y, pos.z);
42218
+ }
42219
+ return null;
42220
+ },
42221
+ setCameraPosition: (x, y, z) => {
42222
+ if (controlsRef.current) {
42223
+ controlsRef.current.object.position.set(x, y, z);
42224
+ controlsRef.current.update();
42225
+ }
42226
+ },
42227
+ lookAt: (x, y, z) => {
42228
+ if (controlsRef.current) {
42229
+ controlsRef.current.target.set(x, y, z);
42230
+ controlsRef.current.update();
42231
+ }
42232
+ },
42233
+ resetCamera: () => {
42234
+ if (controlsRef.current) {
42235
+ controlsRef.current.reset();
42236
+ }
42237
+ },
42238
+ screenshot: () => {
42239
+ const canvas = containerRef.current?.querySelector("canvas");
42240
+ if (canvas) {
42241
+ return canvas.toDataURL("image/png");
42242
+ }
42243
+ return null;
42244
+ },
42245
+ export: () => ({
42246
+ tiles,
42247
+ units,
42248
+ features
42249
+ })
42250
+ }));
42251
+ const handleTileClick = useCallback(
42252
+ (tile, event) => {
42253
+ eventHandlers.handleTileClick(tile, event);
42254
+ },
42255
+ [eventHandlers]
42256
+ );
42257
+ const handleUnitClick = useCallback(
42258
+ (unit, event) => {
42259
+ eventHandlers.handleUnitClick(unit, event);
42260
+ },
42261
+ [eventHandlers]
42262
+ );
42263
+ const handleFeatureClick = useCallback(
42264
+ (feature, event) => {
42265
+ if (event) {
42266
+ eventHandlers.handleFeatureClick(feature, event);
42267
+ }
42268
+ },
42269
+ [eventHandlers]
42270
+ );
42271
+ const handleTileHover = useCallback(
42272
+ (tile, event) => {
42273
+ setHoveredTile(tile);
42274
+ if (event) {
42275
+ eventHandlers.handleTileHover(tile, event);
42276
+ }
42277
+ },
42278
+ [eventHandlers]
42279
+ );
42280
+ const cameraConfig = useMemo(() => {
42281
+ const size = Math.max(
42282
+ gridBounds.maxX - gridBounds.minX,
42283
+ gridBounds.maxZ - gridBounds.minZ
42284
+ );
42285
+ const distance = size * 1.5;
42286
+ switch (cameraMode) {
42287
+ case "isometric":
42288
+ return {
42289
+ position: [distance, distance * 0.8, distance],
42290
+ fov: 45
42291
+ };
42292
+ case "top-down":
42293
+ return {
42294
+ position: [0, distance * 2, 0],
42295
+ fov: 45
42296
+ };
42297
+ case "perspective":
42298
+ default:
42299
+ return {
42300
+ position: [distance, distance, distance],
42301
+ fov: 45
42302
+ };
42303
+ }
42304
+ }, [cameraMode, gridBounds]);
42305
+ const DefaultTileRenderer = useCallback(
42306
+ ({ tile, position }) => {
42307
+ const isSelected = tile.id ? selectedTileIds.includes(tile.id) : false;
42308
+ const isHovered = hoveredTile?.id === tile.id;
42309
+ const isValidMove = validMoves.some(
42310
+ (m) => m.x === tile.x && m.z === (tile.z ?? tile.y ?? 0)
42311
+ );
42312
+ const isAttackTarget = attackTargets.some(
42313
+ (m) => m.x === tile.x && m.z === (tile.z ?? tile.y ?? 0)
42314
+ );
42315
+ let color = 8421504;
42316
+ if (tile.type === "water") color = 4491468;
42317
+ else if (tile.type === "grass") color = 4500036;
42318
+ else if (tile.type === "sand") color = 14535816;
42319
+ else if (tile.type === "rock") color = 8947848;
42320
+ else if (tile.type === "snow") color = 15658734;
42321
+ let emissive = 0;
42322
+ if (isSelected) emissive = 4473924;
42323
+ else if (isAttackTarget) emissive = 4456448;
42324
+ else if (isValidMove) emissive = 17408;
42325
+ else if (isHovered) emissive = 2236962;
42326
+ if (tile.modelUrl) {
42327
+ return /* @__PURE__ */ jsx(
42328
+ "group",
42329
+ {
42330
+ position,
42331
+ onClick: (e) => handleTileClick(tile, e),
42332
+ onPointerEnter: (e) => handleTileHover(tile, e),
42333
+ onPointerLeave: (e) => handleTileHover(null, e),
42334
+ userData: { type: "tile", tileId: tile.id, gridX: tile.x, gridZ: tile.z ?? tile.y },
42335
+ children: /* @__PURE__ */ jsx(
42336
+ ModelLoader,
42337
+ {
42338
+ url: tile.modelUrl,
42339
+ scale: 0.95,
42340
+ fallbackGeometry: "box",
42341
+ castShadow: true,
42342
+ receiveShadow: true
42343
+ }
42344
+ )
42345
+ }
42346
+ );
42347
+ }
42348
+ return /* @__PURE__ */ jsxs(
42349
+ "mesh",
42350
+ {
42351
+ position,
42352
+ onClick: (e) => handleTileClick(tile, e),
42353
+ onPointerEnter: (e) => handleTileHover(tile, e),
42354
+ onPointerLeave: (e) => handleTileHover(null, e),
42355
+ userData: { type: "tile", tileId: tile.id, gridX: tile.x, gridZ: tile.z ?? tile.y },
42356
+ children: [
42357
+ /* @__PURE__ */ jsx("boxGeometry", { args: [0.95, 0.2, 0.95] }),
42358
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color, emissive })
42359
+ ]
42360
+ }
42361
+ );
42362
+ },
42363
+ [selectedTileIds, hoveredTile, validMoves, attackTargets, handleTileClick, handleTileHover]
42364
+ );
42365
+ const DefaultUnitRenderer = useCallback(
42366
+ ({ unit, position }) => {
42367
+ const isSelected = selectedUnitId === unit.id;
42368
+ const color = unit.faction === "player" ? 4491519 : unit.faction === "enemy" ? 16729156 : 16777028;
42369
+ return /* @__PURE__ */ jsxs(
42370
+ "group",
42371
+ {
42372
+ position,
42373
+ onClick: (e) => handleUnitClick(unit, e),
42374
+ userData: { type: "unit", unitId: unit.id },
42375
+ children: [
42376
+ isSelected && /* @__PURE__ */ jsxs("mesh", { position: [0, 0.05, 0], rotation: [-Math.PI / 2, 0, 0], children: [
42377
+ /* @__PURE__ */ jsx("ringGeometry", { args: [0.4, 0.5, 32] }),
42378
+ /* @__PURE__ */ jsx("meshBasicMaterial", { color: "#ffff00", transparent: true, opacity: 0.8 })
42379
+ ] }),
42380
+ unit.modelUrl ? (
42381
+ /* GLB unit model (box fallback while loading / on error) */
42382
+ /* @__PURE__ */ jsx(
42383
+ ModelLoader,
42384
+ {
42385
+ url: unit.modelUrl,
42386
+ scale: 0.5,
42387
+ fallbackGeometry: "box",
42388
+ castShadow: true
42389
+ }
42390
+ )
42391
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
42392
+ /* @__PURE__ */ jsxs("mesh", { position: [0, 0.3, 0], children: [
42393
+ /* @__PURE__ */ jsx("cylinderGeometry", { args: [0.3, 0.3, 0.1, 8] }),
42394
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color })
42395
+ ] }),
42396
+ /* @__PURE__ */ jsxs("mesh", { position: [0, 0.6, 0], children: [
42397
+ /* @__PURE__ */ jsx("capsuleGeometry", { args: [0.2, 0.4, 4, 8] }),
42398
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color })
42399
+ ] }),
42400
+ /* @__PURE__ */ jsxs("mesh", { position: [0, 0.9, 0], children: [
42401
+ /* @__PURE__ */ jsx("sphereGeometry", { args: [0.12, 8, 8] }),
42402
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color })
42403
+ ] })
42404
+ ] }),
42405
+ unit.health !== void 0 && unit.maxHealth !== void 0 && /* @__PURE__ */ jsxs("group", { position: [0, 1.2, 0], children: [
42406
+ /* @__PURE__ */ jsxs("mesh", { position: [-0.25, 0, 0], children: [
42407
+ /* @__PURE__ */ jsx("planeGeometry", { args: [0.5, 0.05] }),
42408
+ /* @__PURE__ */ jsx("meshBasicMaterial", { color: 3355443 })
42409
+ ] }),
42410
+ /* @__PURE__ */ jsxs(
42411
+ "mesh",
42412
+ {
42413
+ position: [
42414
+ -0.25 + 0.5 * (unit.health / unit.maxHealth) / 2,
42415
+ 0,
42416
+ 0.01
42417
+ ],
42418
+ children: [
42419
+ /* @__PURE__ */ jsx("planeGeometry", { args: [0.5 * (unit.health / unit.maxHealth), 0.05] }),
42420
+ /* @__PURE__ */ jsx(
42421
+ "meshBasicMaterial",
42422
+ {
42423
+ color: unit.health / unit.maxHealth > 0.5 ? 4500036 : unit.health / unit.maxHealth > 0.25 ? 11184708 : 16729156
42424
+ }
42425
+ )
42426
+ ]
42427
+ }
42428
+ )
42429
+ ] })
42430
+ ]
42431
+ }
42432
+ );
42433
+ },
42434
+ [selectedUnitId, handleUnitClick]
42435
+ );
42436
+ const DefaultFeatureRenderer = useCallback(
42437
+ ({
42438
+ feature,
42439
+ position
42440
+ }) => {
42441
+ if (feature.assetUrl) {
42442
+ return /* @__PURE__ */ jsx(
42443
+ ModelLoader,
42444
+ {
42445
+ url: feature.assetUrl,
42446
+ position,
42447
+ scale: 0.5,
42448
+ rotation: [0, feature.rotation ?? 0, 0],
42449
+ onClick: () => handleFeatureClick(feature, null),
42450
+ fallbackGeometry: "box"
42451
+ },
42452
+ feature.id
42453
+ );
42454
+ }
42455
+ if (feature.type === "tree") {
42456
+ return /* @__PURE__ */ jsxs(
42457
+ "group",
42458
+ {
42459
+ position,
42460
+ onClick: (e) => handleFeatureClick(feature, e),
42461
+ userData: { type: "feature", featureId: feature.id },
42462
+ children: [
42463
+ /* @__PURE__ */ jsxs("mesh", { position: [0, 0.4, 0], children: [
42464
+ /* @__PURE__ */ jsx("cylinderGeometry", { args: [0.1, 0.15, 0.8, 6] }),
42465
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color: 9127187 })
42466
+ ] }),
42467
+ /* @__PURE__ */ jsxs("mesh", { position: [0, 0.9, 0], children: [
42468
+ /* @__PURE__ */ jsx("coneGeometry", { args: [0.5, 0.8, 8] }),
42469
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color: 2263842 })
42470
+ ] })
42471
+ ]
42472
+ }
42473
+ );
42474
+ }
42475
+ if (feature.type === "rock") {
42476
+ return /* @__PURE__ */ jsxs(
42477
+ "mesh",
42478
+ {
42479
+ position: [position[0], position[1] + 0.3, position[2]],
42480
+ onClick: (e) => handleFeatureClick(feature, e),
42481
+ userData: { type: "feature", featureId: feature.id },
42482
+ children: [
42483
+ /* @__PURE__ */ jsx("dodecahedronGeometry", { args: [0.3, 0] }),
42484
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color: 8421504 })
42485
+ ]
42486
+ }
42487
+ );
42488
+ }
42489
+ return null;
42490
+ },
42491
+ [handleFeatureClick]
42492
+ );
42493
+ if (externalLoading || assetsLoading && preloadAssets.length > 0) {
42494
+ return /* @__PURE__ */ jsx(
42495
+ Canvas3DLoadingState,
42496
+ {
42497
+ progress,
42498
+ loaded,
42499
+ total,
42500
+ message: loadingMessage,
42501
+ className
42502
+ }
42503
+ );
42504
+ }
42505
+ const displayError = externalError || internalError;
42506
+ if (displayError) {
42507
+ return /* @__PURE__ */ jsx(Canvas3DErrorBoundary, { children: /* @__PURE__ */ jsx("div", { className: "game-canvas-3d game-canvas-3d--error", children: /* @__PURE__ */ jsxs("div", { className: "game-canvas-3d__error", children: [
42508
+ "Error: ",
42509
+ displayError
42510
+ ] }) }) });
42511
+ }
42512
+ return /* @__PURE__ */ jsx(
42513
+ Canvas3DErrorBoundary,
42514
+ {
42515
+ onError: (err) => setInternalError(err.message),
42516
+ onReset: () => setInternalError(null),
42517
+ children: /* @__PURE__ */ jsxs(
42518
+ "div",
42519
+ {
42520
+ ref: containerRef,
42521
+ className: cn("game-canvas-3d relative w-full h-full min-h-[85vh] overflow-hidden", className),
42522
+ "data-orientation": orientation,
42523
+ "data-camera-mode": cameraMode,
42524
+ "data-overlay": overlay,
42525
+ children: [
42526
+ /* @__PURE__ */ jsxs(
42527
+ Canvas,
42528
+ {
42529
+ shadows,
42530
+ camera: {
42531
+ position: cameraConfig.position,
42532
+ fov: cameraConfig.fov,
42533
+ near: 0.1,
42534
+ far: 1e3
42535
+ },
42536
+ style: { background: backgroundColor },
42537
+ onClick: (e) => {
42538
+ if (e.target === e.currentTarget) {
42539
+ eventHandlers.handleCanvasClick(e);
42540
+ }
42541
+ },
42542
+ children: [
42543
+ /* @__PURE__ */ jsx(CameraController, { onCameraChange: eventHandlers.handleCameraChange }),
42544
+ /* @__PURE__ */ jsx("ambientLight", { intensity: 0.6 }),
42545
+ /* @__PURE__ */ jsx(
42546
+ "directionalLight",
42547
+ {
42548
+ position: [10, 20, 10],
42549
+ intensity: 0.8,
42550
+ castShadow: shadows,
42551
+ "shadow-mapSize": [2048, 2048]
42552
+ }
42553
+ ),
42554
+ /* @__PURE__ */ jsx("hemisphereLight", { intensity: 0.3, color: "#87ceeb", groundColor: "#362d1d" }),
42555
+ showGrid && /* @__PURE__ */ jsx(
42556
+ Grid$1,
42557
+ {
42558
+ args: [
42559
+ Math.max(gridBounds.maxX - gridBounds.minX + 2, 10),
42560
+ Math.max(gridBounds.maxZ - gridBounds.minZ + 2, 10)
42561
+ ],
42562
+ position: [
42563
+ (gridBounds.maxX - gridBounds.minX) / 2 - 0.5,
42564
+ 0,
42565
+ (gridBounds.maxZ - gridBounds.minZ) / 2 - 0.5
42566
+ ],
42567
+ cellSize: 1,
42568
+ cellThickness: 1,
42569
+ cellColor: "#444444",
42570
+ sectionSize: 5,
42571
+ sectionThickness: 1.5,
42572
+ sectionColor: "#666666",
42573
+ fadeDistance: 50,
42574
+ fadeStrength: 1
42575
+ }
42576
+ ),
42577
+ tiles.map((tile, index) => {
42578
+ const position = gridToWorld(
42579
+ tile.x,
42580
+ tile.z ?? tile.y ?? 0,
42581
+ tile.elevation ?? 0
42582
+ );
42583
+ const Renderer = CustomTileRenderer || DefaultTileRenderer;
42584
+ return /* @__PURE__ */ jsx(Renderer, { tile, position }, tile.id ?? `tile-${index}`);
42585
+ }),
42586
+ features.map((feature, index) => {
42587
+ const position = gridToWorld(
42588
+ feature.x,
42589
+ feature.z ?? feature.y ?? 0,
42590
+ (feature.elevation ?? 0) + 0.5
42591
+ );
42592
+ const Renderer = CustomFeatureRenderer || DefaultFeatureRenderer;
42593
+ return /* @__PURE__ */ jsx(Renderer, { feature, position }, feature.id ?? `feature-${index}`);
42594
+ }),
42595
+ units.map((unit) => {
42596
+ const position = gridToWorld(
42597
+ unit.x ?? 0,
42598
+ unit.z ?? unit.y ?? 0,
42599
+ (unit.elevation ?? 0) + 0.5
42600
+ );
42601
+ const Renderer = CustomUnitRenderer || DefaultUnitRenderer;
42602
+ return /* @__PURE__ */ jsx(Renderer, { unit, position }, unit.id);
42603
+ }),
42604
+ children,
42605
+ /* @__PURE__ */ jsx(
42606
+ OrbitControls,
42607
+ {
42608
+ ref: controlsRef,
42609
+ target: cameraTarget,
42610
+ enableDamping: true,
42611
+ dampingFactor: 0.05,
42612
+ minDistance: 2,
42613
+ maxDistance: 100,
42614
+ maxPolarAngle: Math.PI / 2 - 0.1
42615
+ }
42616
+ )
42617
+ ]
42618
+ }
42619
+ ),
42620
+ showCoordinates && hoveredTile && /* @__PURE__ */ jsxs("div", { className: "game-canvas-3d__coordinates", children: [
42621
+ "X: ",
42622
+ hoveredTile.x,
42623
+ ", Z: ",
42624
+ hoveredTile.z ?? hoveredTile.y ?? 0
42625
+ ] }),
42626
+ showTileInfo && hoveredTile && /* @__PURE__ */ jsxs("div", { className: "game-canvas-3d__tile-info", children: [
42627
+ /* @__PURE__ */ jsx("div", { className: "tile-info__type", children: hoveredTile.type }),
42628
+ hoveredTile.terrain && /* @__PURE__ */ jsx("div", { className: "tile-info__terrain", children: hoveredTile.terrain })
42629
+ ] })
42630
+ ]
42631
+ }
42632
+ )
42633
+ }
42634
+ );
42635
+ }
42636
+ );
42637
+ GameCanvas3D.displayName = "GameCanvas3D";
42638
+ }
42639
+ });
42640
+ function GameBoard3D({
42641
+ entity,
42642
+ tiles = [],
42643
+ features = [],
42644
+ cameraMode = "perspective",
42645
+ backgroundColor = "#2a1a1a",
42646
+ tileClickEvent,
42647
+ unitClickEvent,
42648
+ attackEvent,
42649
+ endTurnEvent,
42650
+ cancelEvent,
42651
+ playAgainEvent,
42652
+ gameEndEvent,
42653
+ className
42654
+ }) {
42655
+ const row = boardEntity(entity);
42656
+ const eventBus = useEventBus();
42657
+ const entityUnits = row ? rows(row.units) : [];
42658
+ const units = entityUnits;
42659
+ const selectedUnitId = row ? str(row.selectedUnitId) || null : null;
42660
+ const phase = row ? str(row.phase) : "observation";
42661
+ const result = row ? str(row.result) : "none";
42662
+ const validMoves = row && Array.isArray(row.validMoves) ? row.validMoves : [];
42663
+ const attackTargets = row && Array.isArray(row.attackTargets) ? row.attackTargets : [];
42664
+ const turn = row ? num(row.turn) : 0;
42665
+ const currentTeam = row ? str(row.currentTeam) : "player";
42666
+ const isGameOver = result !== "none";
42667
+ const gameEndEmittedRef = useRef(false);
42668
+ useEffect(() => {
42669
+ if ((result === "victory" || result === "defeat") && gameEndEvent) {
42670
+ if (!gameEndEmittedRef.current) {
42671
+ gameEndEmittedRef.current = true;
42672
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
42673
+ }
42674
+ } else {
42675
+ gameEndEmittedRef.current = false;
42676
+ }
42677
+ }, [result, gameEndEvent, eventBus]);
42678
+ const checkGameEnd = useCallback(() => {
42679
+ const alivePlayer = entityUnits.filter((u) => unitTeam(u) === "player" && unitHealth(u) > 0);
42680
+ const aliveEnemy = entityUnits.filter((u) => unitTeam(u) === "enemy" && unitHealth(u) > 0);
42681
+ if (alivePlayer.length === 0 && gameEndEvent) {
42682
+ eventBus.emit(`UI:${gameEndEvent}`, { result: "defeat" });
42683
+ } else if (aliveEnemy.length === 0 && gameEndEvent) {
42684
+ eventBus.emit(`UI:${gameEndEvent}`, { result: "victory" });
42685
+ }
42686
+ }, [entityUnits, gameEndEvent, eventBus]);
42687
+ const handleUnitClickCallback = useCallback((isoUnit) => {
42688
+ if (phase !== "action" || !selectedUnitId || !attackEvent) return;
42689
+ const attackerRow = entityUnits.find((u) => str(u.id) === selectedUnitId);
42690
+ const targetRow = entityUnits.find((u) => str(u.id) === isoUnit.id);
42691
+ if (!attackerRow || !targetRow) return;
42692
+ if (unitTeam(targetRow) !== "enemy" || unitHealth(targetRow) <= 0) return;
42693
+ const ap = unitPosition(attackerRow);
42694
+ const tp = unitPosition(targetRow);
42695
+ const dx = Math.abs(ap.x - tp.x);
42696
+ const dy = Math.abs(ap.y - tp.y);
42697
+ if (dx <= 1 && dy <= 1 && dx + dy > 0) {
42698
+ const damage = Math.max(1, num(attackerRow.attack) - num(targetRow.defense));
42699
+ eventBus.emit(`UI:${attackEvent}`, {
42700
+ attackerId: str(attackerRow.id),
42701
+ targetId: str(targetRow.id),
42702
+ damage
42703
+ });
42704
+ setTimeout(checkGameEnd, 100);
42705
+ }
42706
+ }, [phase, selectedUnitId, entityUnits, attackEvent, eventBus, checkGameEnd]);
42707
+ const handleEndTurn = useCallback(() => {
42708
+ if (endTurnEvent) eventBus.emit(`UI:${endTurnEvent}`, {});
42709
+ }, [endTurnEvent, eventBus]);
42710
+ const handleCancel = useCallback(() => {
42711
+ if (cancelEvent) eventBus.emit(`UI:${cancelEvent}`, {});
42712
+ }, [cancelEvent, eventBus]);
42713
+ const handlePlayAgain = useCallback(() => {
42714
+ if (playAgainEvent) eventBus.emit(`UI:${playAgainEvent}`, {});
42715
+ }, [playAgainEvent, eventBus]);
42716
+ return /* @__PURE__ */ jsxs(VStack, { className: cn("game-board-3d block w-full min-h-[85vh] relative", className), children: [
42717
+ /* @__PURE__ */ jsxs("div", { className: "game-board-3d__status absolute top-3 left-3 z-10 flex gap-2 items-center", children: [
42718
+ /* @__PURE__ */ jsx(Typography, { variant: "small", className: "status__phase capitalize", children: phase.replace("_", " ") }),
42719
+ currentTeam && !isGameOver && /* @__PURE__ */ jsxs(Typography, { variant: "small", color: "muted", className: "status__team", children: [
42720
+ "\u2014 ",
42721
+ currentTeam === "player" ? "Your Turn" : "Enemy's Turn"
42722
+ ] }),
42723
+ /* @__PURE__ */ jsxs(Typography, { variant: "small", color: "muted", className: "status__turn", children: [
42724
+ "Turn ",
42725
+ turn
42726
+ ] })
42727
+ ] }),
42728
+ /* @__PURE__ */ jsx(
42729
+ GameCanvas3D,
42730
+ {
42731
+ tiles,
42732
+ units,
42733
+ features,
42734
+ cameraMode,
42735
+ showGrid: true,
42736
+ showCoordinates: false,
42737
+ showTileInfo: false,
42738
+ shadows: true,
42739
+ backgroundColor,
42740
+ tileClickEvent,
42741
+ unitClickEvent,
42742
+ onUnitClick: handleUnitClickCallback,
42743
+ selectedUnitId,
42744
+ validMoves,
42745
+ attackTargets,
42746
+ className: "game-board-3d__canvas w-full min-h-[85vh]"
42747
+ }
42748
+ ),
42749
+ !isGameOver && /* @__PURE__ */ jsxs(HStack, { className: "fixed bottom-6 right-6 z-50", gap: "sm", children: [
42750
+ (phase === "selection" || phase === "movement" || phase === "action") && /* @__PURE__ */ jsx(
42751
+ Button,
42752
+ {
42753
+ variant: "secondary",
42754
+ className: "shadow-xl",
42755
+ onClick: handleCancel,
42756
+ children: "Cancel"
42757
+ }
42758
+ ),
42759
+ phase !== "enemy_turn" && /* @__PURE__ */ jsx(
42760
+ Button,
42761
+ {
42762
+ variant: "primary",
42763
+ className: "shadow-xl",
42764
+ onClick: handleEndTurn,
42765
+ children: "End Turn"
42766
+ }
42767
+ )
42768
+ ] }),
42769
+ isGameOver && /* @__PURE__ */ jsx("div", { className: "game-board-3d__overlay absolute inset-0 z-20 flex items-center justify-center bg-black/60", children: /* @__PURE__ */ jsxs(VStack, { align: "center", gap: "md", className: "overlay__card p-8 rounded-xl bg-gray-900/90 shadow-2xl", children: [
42770
+ /* @__PURE__ */ jsx(
42771
+ Typography,
42772
+ {
42773
+ variant: "h2",
42774
+ className: cn(
42775
+ "overlay__result",
42776
+ result === "victory" ? "text-yellow-400" : "text-red-500"
42777
+ ),
42778
+ children: result === "victory" ? "Victory!" : "Defeat"
42779
+ }
42780
+ ),
42781
+ /* @__PURE__ */ jsxs(Typography, { variant: "body", color: "muted", className: "overlay__turn-count", children: [
42782
+ "Completed in ",
42783
+ turn,
42784
+ " turn",
42785
+ turn !== 1 ? "s" : ""
42786
+ ] }),
42787
+ /* @__PURE__ */ jsx(
42788
+ Button,
42789
+ {
42790
+ variant: "primary",
42791
+ className: "px-8 py-3 font-semibold",
42792
+ onClick: handlePlayAgain,
42793
+ children: "Play Again"
42794
+ }
42795
+ )
42796
+ ] }) })
42797
+ ] });
42798
+ }
42799
+ var init_GameBoard3D = __esm({
42800
+ "components/game/organisms/GameBoard3D.tsx"() {
42801
+ "use client";
42802
+ init_cn();
42803
+ init_Button();
42804
+ init_Typography();
42805
+ init_Stack();
42806
+ init_useEventBus();
42807
+ init_GameCanvas3D2();
42808
+ init_boardEntity();
42809
+ GameBoard3D.displayName = "GameBoard3D";
42810
+ }
42811
+ });
41109
42812
  var GameShell;
41110
42813
  var init_GameShell = __esm({
41111
42814
  "components/game/templates/GameShell.tsx"() {
@@ -42474,21 +44177,6 @@ var init_ModalSlot = __esm({
42474
44177
  ModalSlot.displayName = "ModalSlot";
42475
44178
  }
42476
44179
  });
42477
- function getOpponentAction(strategy, actions, history) {
42478
- const actionIds = actions.map((a) => a.id);
42479
- switch (strategy) {
42480
- case "always-cooperate":
42481
- return actionIds[0];
42482
- case "always-defect":
42483
- return actionIds[actionIds.length - 1];
42484
- case "tit-for-tat":
42485
- if (history.length === 0) return actionIds[0];
42486
- return history[history.length - 1].playerAction;
42487
- case "random":
42488
- default:
42489
- return actionIds[Math.floor(Math.random() * actionIds.length)];
42490
- }
42491
- }
42492
44180
  function NegotiatorBoard({
42493
44181
  entity,
42494
44182
  completeEvent = "PUZZLE_COMPLETE",
@@ -42510,28 +44198,35 @@ function NegotiatorBoard({
42510
44198
  const playerTotal = num(resolved?.score);
42511
44199
  const isComplete = result !== "none" || totalRounds > 0 && currentRound >= totalRounds;
42512
44200
  const won = result === "win";
42513
- const opponentTotal = history.reduce((s, r) => s + r.opponentPayoff, 0);
44201
+ const lastPlayerAction = str(resolved?.lastPlayerAction);
44202
+ const lastOpponentAction = str(resolved?.lastOpponentAction);
44203
+ const lastPayoff = num(resolved?.lastPayoff);
42514
44204
  const actions = Array.isArray(resolved?.actions) ? resolved.actions : [];
42515
44205
  const payoffMatrix = Array.isArray(resolved?.payoffMatrix) ? resolved.payoffMatrix : [];
44206
+ const prevRoundRef = useRef(currentRound);
44207
+ useEffect(() => {
44208
+ if (currentRound > prevRoundRef.current && lastPlayerAction) {
44209
+ const opponentPayoffEntry = payoffMatrix.find(
44210
+ (p2) => p2.playerAction === lastPlayerAction && p2.opponentAction === lastOpponentAction
44211
+ );
44212
+ setHistory((prev) => [
44213
+ ...prev,
44214
+ {
44215
+ round: currentRound,
44216
+ playerAction: lastPlayerAction,
44217
+ opponentAction: lastOpponentAction,
44218
+ playerPayoff: lastPayoff,
44219
+ opponentPayoff: opponentPayoffEntry?.opponentPayoff ?? 0
44220
+ }
44221
+ ]);
44222
+ }
44223
+ prevRoundRef.current = currentRound;
44224
+ }, [currentRound, lastPlayerAction, lastOpponentAction, lastPayoff, payoffMatrix]);
44225
+ const opponentTotal = useMemo(() => history.reduce((s, r) => s + r.opponentPayoff, 0), [history]);
42516
44226
  const handleAction = useCallback((actionId) => {
42517
44227
  if (isComplete) return;
42518
- const opponentAction = getOpponentAction(str(resolved?.opponentStrategy) || "random", actions, history);
42519
- const payoff = payoffMatrix.find(
42520
- (p2) => p2.playerAction === actionId && p2.opponentAction === opponentAction
42521
- );
42522
- const playerPayoff = payoff?.playerPayoff ?? 0;
42523
- setHistory((prev) => [
42524
- ...prev,
42525
- {
42526
- round: prev.length + 1,
42527
- playerAction: actionId,
42528
- opponentAction,
42529
- playerPayoff,
42530
- opponentPayoff: payoff?.opponentPayoff ?? 0
42531
- }
42532
- ]);
42533
44228
  if (playRoundEvent) {
42534
- emit(`UI:${playRoundEvent}`, { playerAction: actionId, payoff: playerPayoff });
44229
+ emit(`UI:${playRoundEvent}`, { playerAction: actionId, payoff: 0 });
42535
44230
  }
42536
44231
  if (totalRounds > 0 && currentRound + 1 >= totalRounds) {
42537
44232
  if (finishEvent) {
@@ -42541,10 +44236,11 @@ function NegotiatorBoard({
42541
44236
  setShowHint(true);
42542
44237
  }
42543
44238
  }
42544
- }, [isComplete, resolved, totalRounds, currentRound, actions, payoffMatrix, history, playRoundEvent, finishEvent, emit]);
44239
+ }, [isComplete, resolved, totalRounds, currentRound, playRoundEvent, finishEvent, emit]);
42545
44240
  const handleReset = useCallback(() => {
42546
44241
  setHistory([]);
42547
44242
  setShowHint(false);
44243
+ prevRoundRef.current = 0;
42548
44244
  if (playAgainEvent) {
42549
44245
  emit(`UI:${playAgainEvent}`, {});
42550
44246
  }
@@ -42655,6 +44351,236 @@ var init_NegotiatorBoard = __esm({
42655
44351
  NegotiatorBoard.displayName = "NegotiatorBoard";
42656
44352
  }
42657
44353
  });
44354
+ function resolveEntity(entity) {
44355
+ if (!entity) return {};
44356
+ if (Array.isArray(entity)) return entity[0] ?? {};
44357
+ return entity;
44358
+ }
44359
+ function numField(row, key, fallback) {
44360
+ const v = row[key];
44361
+ return typeof v === "number" ? v : fallback;
44362
+ }
44363
+ function strField(row, key, fallback) {
44364
+ const v = row[key];
44365
+ return typeof v === "string" ? v : fallback;
44366
+ }
44367
+ function PlatformerBoard({
44368
+ entity,
44369
+ player: propPlayer,
44370
+ platforms: propPlatforms,
44371
+ result: propResult,
44372
+ lives: propLives,
44373
+ score: propScore,
44374
+ level: propLevel,
44375
+ worldWidth = 800,
44376
+ worldHeight = 400,
44377
+ canvasWidth = 800,
44378
+ canvasHeight = 400,
44379
+ playerSprite,
44380
+ tileSprites,
44381
+ bgColor,
44382
+ leftEvent = "LEFT",
44383
+ rightEvent = "RIGHT",
44384
+ jumpEvent = "JUMP",
44385
+ stopEvent = "STOP",
44386
+ playAgainEvent = "PLAY_AGAIN",
44387
+ gameEndEvent = "GAME_END",
44388
+ className
44389
+ }) {
44390
+ const row = resolveEntity(entity);
44391
+ const eventBus = useEventBus();
44392
+ const { t } = useTranslate();
44393
+ const result = propResult ?? strField(row, "result", "none");
44394
+ propLives ?? numField(row, "lives", 3);
44395
+ const score = propScore ?? numField(row, "score", 0);
44396
+ const level = propLevel ?? numField(row, "level", 1);
44397
+ const entityPlayer = row["player"];
44398
+ const player = propPlayer ?? entityPlayer ?? {
44399
+ x: 80,
44400
+ y: 320,
44401
+ width: 32,
44402
+ height: 48,
44403
+ vx: 0,
44404
+ vy: 0,
44405
+ grounded: false,
44406
+ facingRight: true
44407
+ };
44408
+ const entityPlatforms = Array.isArray(row["platforms"]) ? row["platforms"] : void 0;
44409
+ const platforms = propPlatforms ?? entityPlatforms ?? [
44410
+ { x: 0, y: 368, width: 800, height: 32, type: "ground" },
44411
+ { x: 150, y: 280, width: 160, height: 16, type: "platform" },
44412
+ { x: 420, y: 220, width: 160, height: 16, type: "platform" },
44413
+ { x: 580, y: 300, width: 80, height: 16, type: "hazard" },
44414
+ { x: 700, y: 340, width: 64, height: 28, type: "goal" }
44415
+ ];
44416
+ const handleRestart = useCallback(() => {
44417
+ if (playAgainEvent) {
44418
+ eventBus.emit(`UI:${playAgainEvent}`, {});
44419
+ }
44420
+ }, [playAgainEvent, eventBus]);
44421
+ const isGameOver = result === "won" || result === "lost";
44422
+ const gameEndEmittedRef = useRef(false);
44423
+ useEffect(() => {
44424
+ if ((result === "won" || result === "lost") && gameEndEvent) {
44425
+ if (!gameEndEmittedRef.current) {
44426
+ gameEndEmittedRef.current = true;
44427
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
44428
+ }
44429
+ } else {
44430
+ gameEndEmittedRef.current = false;
44431
+ }
44432
+ }, [result, gameEndEvent, eventBus]);
44433
+ return /* @__PURE__ */ jsx(
44434
+ VStack,
44435
+ {
44436
+ className: cn("platformer-board relative bg-background", className),
44437
+ gap: "none",
44438
+ children: /* @__PURE__ */ jsxs(Box, { className: "relative", children: [
44439
+ /* @__PURE__ */ jsx(
44440
+ PlatformerCanvas,
44441
+ {
44442
+ player,
44443
+ platforms,
44444
+ worldWidth,
44445
+ worldHeight,
44446
+ canvasWidth,
44447
+ canvasHeight,
44448
+ followCamera: true,
44449
+ bgColor,
44450
+ playerSprite,
44451
+ tileSprites,
44452
+ leftEvent,
44453
+ rightEvent,
44454
+ jumpEvent,
44455
+ stopEvent
44456
+ }
44457
+ ),
44458
+ isGameOver && /* @__PURE__ */ jsx(Box, { className: "absolute inset-0 z-50 flex items-center justify-center bg-background/70 backdrop-blur-sm rounded-container", children: /* @__PURE__ */ jsxs(VStack, { className: "text-center p-8", gap: "lg", children: [
44459
+ /* @__PURE__ */ jsx(
44460
+ Typography,
44461
+ {
44462
+ variant: "h2",
44463
+ className: cn(
44464
+ "text-4xl font-black tracking-widest uppercase",
44465
+ result === "won" ? "text-warning" : "text-error"
44466
+ ),
44467
+ children: result === "won" ? t("platformer.won") : t("platformer.lost")
44468
+ }
44469
+ ),
44470
+ result === "won" && /* @__PURE__ */ jsxs(Typography, { variant: "body1", className: "text-muted-foreground", children: [
44471
+ t("platformer.level"),
44472
+ " ",
44473
+ level,
44474
+ " \u2014 ",
44475
+ t("platformer.score"),
44476
+ ": ",
44477
+ score
44478
+ ] }),
44479
+ result === "lost" && /* @__PURE__ */ jsx(Typography, { variant: "body1", className: "text-muted-foreground", children: t("platformer.noLives") }),
44480
+ /* @__PURE__ */ jsx(
44481
+ Button,
44482
+ {
44483
+ variant: "primary",
44484
+ className: "px-8 py-3 font-semibold",
44485
+ onClick: handleRestart,
44486
+ children: result === "won" ? t("platformer.nextLevel") : t("platformer.playAgain")
44487
+ }
44488
+ )
44489
+ ] }) })
44490
+ ] })
44491
+ }
44492
+ );
44493
+ }
44494
+ var init_PlatformerBoard = __esm({
44495
+ "components/game/organisms/PlatformerBoard.tsx"() {
44496
+ "use client";
44497
+ init_cn();
44498
+ init_useEventBus();
44499
+ init_Box();
44500
+ init_Button();
44501
+ init_Typography();
44502
+ init_Stack();
44503
+ init_PlatformerCanvas();
44504
+ PlatformerBoard.displayName = "PlatformerBoard";
44505
+ }
44506
+ });
44507
+ function PlatformerTemplate({
44508
+ entity,
44509
+ platforms = DEFAULT_PLATFORMS,
44510
+ worldWidth = 800,
44511
+ worldHeight = 400,
44512
+ canvasWidth = 800,
44513
+ canvasHeight = 400,
44514
+ playerSprite = `${CDN3}/characters/platformChar_idle.png`,
44515
+ tileSprites = DEFAULT_TILE_SPRITES,
44516
+ bgColor = "#5c94fc",
44517
+ gameEndEvent = "GAME_END",
44518
+ className
44519
+ }) {
44520
+ const row = entity && !Array.isArray(entity) ? entity : void 0;
44521
+ const lives = typeof row?.["lives"] === "number" ? row["lives"] : 3;
44522
+ const score = typeof row?.["score"] === "number" ? row["score"] : 0;
44523
+ const level = typeof row?.["level"] === "number" ? row["level"] : 1;
44524
+ return /* @__PURE__ */ jsxs(VStack, { className: cn("platformer-template w-full", className), gap: "none", children: [
44525
+ /* @__PURE__ */ jsx(Box, { className: "relative", children: /* @__PURE__ */ jsx(
44526
+ GameHud,
44527
+ {
44528
+ position: "top",
44529
+ stats: [
44530
+ { label: "Lives", value: lives, format: "number", variant: "danger" },
44531
+ { label: "Level", value: level, format: "number", variant: "default" }
44532
+ ]
44533
+ }
44534
+ ) }),
44535
+ /* @__PURE__ */ jsx(
44536
+ PlatformerBoard,
44537
+ {
44538
+ entity,
44539
+ platforms,
44540
+ worldWidth,
44541
+ worldHeight,
44542
+ canvasWidth,
44543
+ canvasHeight,
44544
+ playerSprite,
44545
+ tileSprites,
44546
+ bgColor,
44547
+ leftEvent: "LEFT",
44548
+ rightEvent: "RIGHT",
44549
+ jumpEvent: "JUMP",
44550
+ stopEvent: "STOP",
44551
+ playAgainEvent: "PLAY_AGAIN",
44552
+ gameEndEvent
44553
+ }
44554
+ ),
44555
+ /* @__PURE__ */ jsx(Box, { className: "px-4 py-2", children: /* @__PURE__ */ jsx(ScoreBoard, { score, level }) })
44556
+ ] });
44557
+ }
44558
+ var CDN3, DEFAULT_TILE_SPRITES, DEFAULT_PLATFORMS;
44559
+ var init_PlatformerTemplate = __esm({
44560
+ "components/game/templates/PlatformerTemplate.tsx"() {
44561
+ init_cn();
44562
+ init_Box();
44563
+ init_Stack();
44564
+ init_PlatformerBoard();
44565
+ init_GameHud();
44566
+ init_ScoreBoard();
44567
+ CDN3 = "https://almadar-kflow-assets.web.app/shared/platformer";
44568
+ DEFAULT_TILE_SPRITES = {
44569
+ ground: `${CDN3}/tiles/platformPack_tile001.png`,
44570
+ platform: `${CDN3}/tiles/platformPack_tile004.png`,
44571
+ hazard: `${CDN3}/tiles/platformPack_tile017.png`,
44572
+ goal: `${CDN3}/tiles/platformPack_tile007.png`
44573
+ };
44574
+ DEFAULT_PLATFORMS = [
44575
+ { x: 0, y: 368, width: 800, height: 32, type: "ground" },
44576
+ { x: 150, y: 280, width: 160, height: 16, type: "platform" },
44577
+ { x: 420, y: 220, width: 160, height: 16, type: "platform" },
44578
+ { x: 580, y: 300, width: 80, height: 16, type: "hazard" },
44579
+ { x: 700, y: 340, width: 64, height: 28, type: "goal" }
44580
+ ];
44581
+ PlatformerTemplate.displayName = "PlatformerTemplate";
44582
+ }
44583
+ });
42658
44584
  var PricingOrganism;
42659
44585
  var init_PricingOrganism = __esm({
42660
44586
  "components/marketing/organisms/PricingOrganism.tsx"() {
@@ -42777,6 +44703,356 @@ var init_PricingPageTemplate = __esm({
42777
44703
  PricingPageTemplate.displayName = "PricingPageTemplate";
42778
44704
  }
42779
44705
  });
44706
+ function RoguelikeBoard({
44707
+ entity,
44708
+ tiles: propTiles,
44709
+ enemies: propEnemies,
44710
+ items: propItems,
44711
+ player: propPlayer,
44712
+ playerHp: propPlayerHp,
44713
+ playerMaxHp: propPlayerMaxHp,
44714
+ playerAttack: propPlayerAttack,
44715
+ depth: propDepth,
44716
+ result: propResult,
44717
+ phase: propPhase,
44718
+ stairsX: propStairsX,
44719
+ stairsY: propStairsY,
44720
+ assetManifest: propAssetManifest,
44721
+ scale = 0.45,
44722
+ unitScale = 1,
44723
+ moveEvent,
44724
+ playAgainEvent,
44725
+ gameEndEvent,
44726
+ className
44727
+ }) {
44728
+ const board = boardEntity(entity) ?? {};
44729
+ const tiles = propTiles ?? (Array.isArray(board.tiles) ? board.tiles : DEFAULT_TILES2);
44730
+ const enemies = propEnemies ?? (Array.isArray(board.enemies) ? board.enemies : DEFAULT_ENEMIES);
44731
+ const items = propItems ?? (Array.isArray(board.items) ? board.items : DEFAULT_ITEMS);
44732
+ const player = propPlayer ?? board.player ?? { x: 1, y: 1 };
44733
+ const playerHp = propPlayerHp ?? num(board.playerHp, 10);
44734
+ const playerMaxHp = propPlayerMaxHp ?? num(board.playerMaxHp, 10);
44735
+ const playerAttack = propPlayerAttack ?? num(board.playerAttack, 3);
44736
+ const depth = propDepth ?? num(board.depth, 1);
44737
+ const result = propResult ?? (str(board.result) || "none");
44738
+ const phase = propPhase ?? (str(board.phase) || "player_turn");
44739
+ propStairsX ?? num(board.stairsX, 3);
44740
+ propStairsY ?? num(board.stairsY, 3);
44741
+ const eventBus = useEventBus();
44742
+ const { t } = useTranslate();
44743
+ const [hoveredTile, setHoveredTile] = useState(null);
44744
+ const emitMove = useCallback((dx, dy) => {
44745
+ if (!moveEvent) return;
44746
+ eventBus.emit(`UI:${moveEvent}`, { dx, dy });
44747
+ }, [moveEvent, eventBus]);
44748
+ useEffect(() => {
44749
+ const handler = (e) => {
44750
+ if (result !== "none" || phase !== "player_turn") return;
44751
+ if (e.key === "ArrowUp" || e.key === "w") emitMove(0, -1);
44752
+ if (e.key === "ArrowDown" || e.key === "s") emitMove(0, 1);
44753
+ if (e.key === "ArrowLeft" || e.key === "a") emitMove(-1, 0);
44754
+ if (e.key === "ArrowRight" || e.key === "d") emitMove(1, 0);
44755
+ };
44756
+ window.addEventListener("keydown", handler);
44757
+ return () => window.removeEventListener("keydown", handler);
44758
+ }, [result, phase, emitMove]);
44759
+ useEffect(() => {
44760
+ if ((result === "won" || result === "lost") && gameEndEvent) {
44761
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
44762
+ }
44763
+ }, [result, gameEndEvent, eventBus]);
44764
+ const handleTileClick = useCallback((x, y) => {
44765
+ if (result !== "none" || phase !== "player_turn") return;
44766
+ const dx = x - player.x;
44767
+ const dy = y - player.y;
44768
+ if (Math.abs(dx) + Math.abs(dy) === 1) {
44769
+ emitMove(dx, dy);
44770
+ }
44771
+ }, [result, phase, player, emitMove]);
44772
+ const handleReset = useCallback(() => {
44773
+ if (!playAgainEvent) return;
44774
+ eventBus.emit(`UI:${playAgainEvent}`, {});
44775
+ }, [playAgainEvent, eventBus]);
44776
+ const isoUnits = useMemo(() => {
44777
+ const liveEnemies = enemies.filter((e) => e.hp > 0);
44778
+ return [
44779
+ {
44780
+ id: "player",
44781
+ position: player,
44782
+ name: t("roguelike.player") || "Hero",
44783
+ team: "player",
44784
+ health: playerHp,
44785
+ maxHealth: playerMaxHp,
44786
+ unitType: "player",
44787
+ sprite: `${CDN4}/isometric-dungeon/Characters/Male/Male_0_Idle0.png`
44788
+ },
44789
+ ...liveEnemies.map((e) => ({
44790
+ id: e.id,
44791
+ position: { x: e.x, y: e.y },
44792
+ name: t("roguelike.enemy") || "Enemy",
44793
+ team: "enemy",
44794
+ health: e.hp,
44795
+ maxHealth: 6,
44796
+ unitType: "enemy",
44797
+ sprite: `${CDN4}/sprite-sheets/shadow-legion-sprite-sheet-sw.png`
44798
+ }))
44799
+ ];
44800
+ }, [player, enemies, playerHp, playerMaxHp, t]);
44801
+ const isoFeatures = useMemo(
44802
+ () => items.map((it) => ({
44803
+ id: it.id,
44804
+ x: it.x,
44805
+ y: it.y,
44806
+ type: it.kind,
44807
+ sprite: FEATURE_SPRITE[it.kind] ?? FEATURE_SPRITE.health_potion
44808
+ })),
44809
+ [items]
44810
+ );
44811
+ const validMoves = useMemo(() => {
44812
+ if (result !== "none" || phase !== "player_turn") return [];
44813
+ const dirs = [{ dx: 0, dy: -1 }, { dx: 0, dy: 1 }, { dx: -1, dy: 0 }, { dx: 1, dy: 0 }];
44814
+ return dirs.map(({ dx, dy }) => ({ x: player.x + dx, y: player.y + dy })).filter(({ x, y }) => {
44815
+ const tile = tiles.find((t2) => t2.x === x && t2.y === y);
44816
+ return tile?.passable ?? false;
44817
+ });
44818
+ }, [result, phase, player, tiles]);
44819
+ const isGameOver = result === "won" || result === "lost";
44820
+ return /* @__PURE__ */ jsxs(VStack, { className: cn("roguelike-board relative min-h-[600px] bg-background", className), gap: "none", children: [
44821
+ /* @__PURE__ */ jsxs(HStack, { className: "items-center justify-between px-4 py-2 border-b border-border", gap: "md", children: [
44822
+ /* @__PURE__ */ jsxs(HStack, { gap: "sm", className: "items-center", children: [
44823
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: t("roguelike.depth") || "Depth" }),
44824
+ /* @__PURE__ */ jsx(Typography, { variant: "body1", className: "font-bold tabular-nums", children: depth })
44825
+ ] }),
44826
+ /* @__PURE__ */ jsxs(HStack, { gap: "sm", className: "items-center", children: [
44827
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: t("roguelike.hp") || "HP" }),
44828
+ /* @__PURE__ */ jsxs(
44829
+ Typography,
44830
+ {
44831
+ variant: "body1",
44832
+ className: cn(
44833
+ "font-bold tabular-nums",
44834
+ playerHp <= Math.floor(playerMaxHp * 0.25) ? "text-error" : "text-success"
44835
+ ),
44836
+ children: [
44837
+ playerHp,
44838
+ " / ",
44839
+ playerMaxHp
44840
+ ]
44841
+ }
44842
+ )
44843
+ ] }),
44844
+ /* @__PURE__ */ jsxs(HStack, { gap: "sm", className: "items-center", children: [
44845
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: t("roguelike.atk") || "ATK" }),
44846
+ /* @__PURE__ */ jsx(Typography, { variant: "body1", className: "font-bold tabular-nums text-foreground", children: playerAttack })
44847
+ ] }),
44848
+ /* @__PURE__ */ jsxs(HStack, { gap: "sm", className: "items-center", children: [
44849
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: t("roguelike.phase") || "Phase" }),
44850
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "capitalize text-foreground", children: phase.replace("_", " ") })
44851
+ ] })
44852
+ ] }),
44853
+ /* @__PURE__ */ jsx(Box, { className: "relative flex-1", children: /* @__PURE__ */ jsx(
44854
+ IsometricCanvas_default,
44855
+ {
44856
+ tiles,
44857
+ units: isoUnits,
44858
+ features: isoFeatures,
44859
+ selectedUnitId: null,
44860
+ validMoves,
44861
+ attackTargets: [],
44862
+ hoveredTile,
44863
+ onTileClick: handleTileClick,
44864
+ onUnitClick: () => void 0,
44865
+ onTileHover: (x, y) => setHoveredTile({ x, y }),
44866
+ onTileLeave: () => setHoveredTile(null),
44867
+ scale,
44868
+ assetBaseUrl: propAssetManifest?.baseUrl ?? CDN4,
44869
+ assetManifest: propAssetManifest,
44870
+ unitScale
44871
+ }
44872
+ ) }),
44873
+ !isGameOver && /* @__PURE__ */ jsx(HStack, { className: "justify-center gap-2 p-3", gap: "none", children: [
44874
+ { label: "\u2191", dx: 0, dy: -1 },
44875
+ { label: "\u2193", dx: 0, dy: 1 },
44876
+ { label: "\u2190", dx: -1, dy: 0 },
44877
+ { label: "\u2192", dx: 1, dy: 0 }
44878
+ ].map(({ label, dx, dy }) => /* @__PURE__ */ jsx(
44879
+ Button,
44880
+ {
44881
+ variant: "secondary",
44882
+ className: "w-9 h-9 p-0 text-base font-mono",
44883
+ onClick: () => emitMove(dx, dy),
44884
+ disabled: phase !== "player_turn",
44885
+ children: label
44886
+ },
44887
+ label
44888
+ )) }),
44889
+ isGameOver && /* @__PURE__ */ jsx(Box, { className: "absolute inset-0 z-50 flex items-center justify-center bg-background/75 backdrop-blur-sm rounded-container", children: /* @__PURE__ */ jsxs(VStack, { className: "text-center p-8", gap: "lg", children: [
44890
+ /* @__PURE__ */ jsx(
44891
+ Typography,
44892
+ {
44893
+ variant: "h2",
44894
+ className: cn(
44895
+ "text-4xl font-black tracking-widest uppercase",
44896
+ result === "won" ? "text-warning" : "text-error"
44897
+ ),
44898
+ children: result === "won" ? t("roguelike.victory") || "Victory!" : t("roguelike.defeat") || "Defeated!"
44899
+ }
44900
+ ),
44901
+ result === "won" && /* @__PURE__ */ jsx(Typography, { variant: "body1", className: "text-muted-foreground", children: t("roguelike.cleared") || `Dungeon cleared! Depth reached: ${depth}` }),
44902
+ /* @__PURE__ */ jsx(
44903
+ Button,
44904
+ {
44905
+ variant: "primary",
44906
+ className: "px-8 py-3 font-semibold",
44907
+ onClick: handleReset,
44908
+ children: t("roguelike.playAgain") || "Play Again"
44909
+ }
44910
+ )
44911
+ ] }) })
44912
+ ] });
44913
+ }
44914
+ var CDN4, DEFAULT_TILES2, DEFAULT_ENEMIES, DEFAULT_ITEMS, FEATURE_SPRITE;
44915
+ var init_RoguelikeBoard = __esm({
44916
+ "components/game/organisms/RoguelikeBoard.tsx"() {
44917
+ "use client";
44918
+ init_cn();
44919
+ init_useEventBus();
44920
+ init_Box();
44921
+ init_Button();
44922
+ init_Typography();
44923
+ init_Stack();
44924
+ init_IsometricCanvas();
44925
+ init_boardEntity();
44926
+ CDN4 = "https://almadar-kflow-assets.web.app/shared";
44927
+ DEFAULT_TILES2 = [
44928
+ { x: 0, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44929
+ { x: 1, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44930
+ { x: 2, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44931
+ { x: 3, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44932
+ { x: 4, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44933
+ { x: 0, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44934
+ { x: 1, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
44935
+ { x: 2, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
44936
+ { x: 3, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
44937
+ { x: 4, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44938
+ { x: 0, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44939
+ { x: 1, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirtTiles_E.png` },
44940
+ { x: 2, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirtTiles_E.png` },
44941
+ { x: 3, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
44942
+ { x: 4, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44943
+ { x: 0, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44944
+ { x: 1, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
44945
+ { x: 2, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirtTiles_E.png` },
44946
+ { x: 3, y: 3, terrain: "stairs", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stairs_E.png` },
44947
+ { x: 4, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44948
+ { x: 0, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44949
+ { x: 1, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44950
+ { x: 2, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44951
+ { x: 3, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44952
+ { x: 4, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` }
44953
+ ];
44954
+ DEFAULT_ENEMIES = [
44955
+ { id: "e1", x: 3, y: 1, hp: 5, attack: 2 },
44956
+ { id: "e2", x: 1, y: 3, hp: 4, attack: 1 }
44957
+ ];
44958
+ DEFAULT_ITEMS = [
44959
+ { id: "i1", x: 2, y: 2, kind: "health_potion" }
44960
+ ];
44961
+ FEATURE_SPRITE = {
44962
+ health_potion: `${CDN4}/isometric-dungeon/Isometric/chestClosed_E.png`,
44963
+ sword: `${CDN4}/isometric-dungeon/Isometric/barrel_E.png`,
44964
+ shield: `${CDN4}/isometric-dungeon/Isometric/barrel_E.png`
44965
+ };
44966
+ RoguelikeBoard.displayName = "RoguelikeBoard";
44967
+ }
44968
+ });
44969
+ function RoguelikeTemplate({
44970
+ entity,
44971
+ scale = 0.45,
44972
+ unitScale = 1,
44973
+ tiles = DEFAULT_ROGUELIKE_TILES,
44974
+ enemies,
44975
+ items,
44976
+ stairsX,
44977
+ stairsY,
44978
+ assetManifest = DEFAULT_ROGUELIKE_MANIFEST,
44979
+ gameEndEvent,
44980
+ className
44981
+ }) {
44982
+ const resolved = entity && typeof entity === "object" && !Array.isArray(entity) ? entity : void 0;
44983
+ if (!resolved && !tiles && !assetManifest) return null;
44984
+ return /* @__PURE__ */ jsx(
44985
+ RoguelikeBoard,
44986
+ {
44987
+ entity: resolved,
44988
+ tiles,
44989
+ enemies,
44990
+ items,
44991
+ stairsX,
44992
+ stairsY,
44993
+ assetManifest,
44994
+ scale,
44995
+ unitScale,
44996
+ moveEvent: "MOVE",
44997
+ playAgainEvent: "PLAY_AGAIN",
44998
+ gameEndEvent: gameEndEvent ?? "GAME_END",
44999
+ className
45000
+ }
45001
+ );
45002
+ }
45003
+ var CDN5, DEFAULT_ROGUELIKE_TILES, DEFAULT_ROGUELIKE_MANIFEST;
45004
+ var init_RoguelikeTemplate = __esm({
45005
+ "components/game/templates/RoguelikeTemplate.tsx"() {
45006
+ init_RoguelikeBoard();
45007
+ CDN5 = "https://almadar-kflow-assets.web.app/shared";
45008
+ DEFAULT_ROGUELIKE_TILES = [
45009
+ { x: 0, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45010
+ { x: 1, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45011
+ { x: 2, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45012
+ { x: 3, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45013
+ { x: 4, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45014
+ { x: 0, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45015
+ { x: 1, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
45016
+ { x: 2, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
45017
+ { x: 3, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
45018
+ { x: 4, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45019
+ { x: 0, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45020
+ { x: 1, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirtTiles_E.png` },
45021
+ { x: 2, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirtTiles_E.png` },
45022
+ { x: 3, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
45023
+ { x: 4, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45024
+ { x: 0, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45025
+ { x: 1, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
45026
+ { x: 2, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirtTiles_E.png` },
45027
+ { x: 3, y: 3, terrain: "stairs", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stairs_E.png` },
45028
+ { x: 4, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45029
+ { x: 0, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45030
+ { x: 1, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45031
+ { x: 2, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45032
+ { x: 3, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45033
+ { x: 4, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` }
45034
+ ];
45035
+ DEFAULT_ROGUELIKE_MANIFEST = {
45036
+ baseUrl: CDN5,
45037
+ terrains: {
45038
+ floor: "/isometric-dungeon/Isometric/dirt_E.png",
45039
+ wall: "/isometric-dungeon/Isometric/stoneInset_E.png",
45040
+ stairs: "/isometric-dungeon/Isometric/stairs_E.png",
45041
+ planks: "/isometric-dungeon/Isometric/planks_E.png"
45042
+ },
45043
+ units: {
45044
+ player: "/isometric-dungeon/Characters/Male/Male_0_Idle0.png",
45045
+ enemy: "/sprite-sheets/shadow-legion-sprite-sheet-sw.png"
45046
+ },
45047
+ features: {
45048
+ health_potion: "/isometric-dungeon/Isometric/chestClosed_E.png",
45049
+ sword: "/isometric-dungeon/Isometric/barrel_E.png",
45050
+ shield: "/isometric-dungeon/Isometric/barrel_E.png"
45051
+ }
45052
+ };
45053
+ RoguelikeTemplate.displayName = "RoguelikeTemplate";
45054
+ }
45055
+ });
42780
45056
 
42781
45057
  // lib/traitRegistry.ts
42782
45058
  function getAllTraits() {
@@ -44011,7 +46287,7 @@ function getAllEvents(traits2) {
44011
46287
  function EventDispatcherTab({ traits: traits2, schema }) {
44012
46288
  const eventBus = useEventBus();
44013
46289
  const { t } = useTranslate();
44014
- const [log12, setLog] = React83.useState([]);
46290
+ const [log14, setLog] = React83.useState([]);
44015
46291
  const prevStatesRef = React83.useRef(/* @__PURE__ */ new Map());
44016
46292
  React83.useEffect(() => {
44017
46293
  for (const trait of traits2) {
@@ -44075,9 +46351,9 @@ function EventDispatcherTab({ traits: traits2, schema }) {
44075
46351
  /* @__PURE__ */ jsx(Typography, { variant: "small", weight: "medium", className: "text-gray-500 mb-1", children: t("debug.otherEvents") }),
44076
46352
  /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1", children: unavailableEvents.map((event) => /* @__PURE__ */ jsx(Badge, { variant: "default", size: "sm", className: "opacity-50", children: event }, event)) })
44077
46353
  ] }),
44078
- log12.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
46354
+ log14.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
44079
46355
  /* @__PURE__ */ jsx(Typography, { variant: "small", weight: "medium", className: "text-gray-500 mb-1", children: t("debug.recentTransitions") }),
44080
- /* @__PURE__ */ jsx(Stack, { gap: "xs", children: log12.map((entry, i) => /* @__PURE__ */ jsxs(Typography, { variant: "small", className: "font-mono text-xs", children: [
46356
+ /* @__PURE__ */ jsx(Stack, { gap: "xs", children: log14.map((entry, i) => /* @__PURE__ */ jsxs(Typography, { variant: "small", className: "font-mono text-xs", children: [
44081
46357
  /* @__PURE__ */ jsx("span", { className: "text-purple-400", children: entry.traitName }),
44082
46358
  " ",
44083
46359
  /* @__PURE__ */ jsx("span", { className: "text-gray-500", children: entry.from }),
@@ -44880,17 +47156,24 @@ function SequencerBoard({
44880
47156
  const { emit } = useEventBus();
44881
47157
  const { t } = useTranslate();
44882
47158
  const resolved = boardEntity(entity);
44883
- const maxSlots = num(resolved?.maxSlots);
47159
+ const maxSlots = num(resolved?.maxSlots) || 3;
44884
47160
  const solutions = Array.isArray(resolved?.solutions) ? resolved.solutions : [];
44885
47161
  const availableActions = Array.isArray(resolved?.availableActions) ? resolved.availableActions : [];
44886
47162
  const allowDuplicates = resolved?.allowDuplicates !== false;
47163
+ const entitySlots = Array.isArray(resolved?.slots) ? resolved.slots : [];
47164
+ const entityResult = str(resolved?.result);
47165
+ const entityAttempts = num(resolved?.attempts);
47166
+ const entityCurrentStep = typeof resolved?.currentStep === "number" ? resolved.currentStep : -1;
47167
+ const slots = Array.from({ length: maxSlots }, (_, i) => {
47168
+ const entitySlot = entitySlots.find((s) => s.index === i);
47169
+ if (!entitySlot?.placedActionId) return void 0;
47170
+ return availableActions.find((a) => a.id === entitySlot.placedActionId);
47171
+ });
47172
+ const isSuccess = entityResult === "win";
47173
+ const attempts = entityAttempts;
47174
+ const isPlayingBack = entityCurrentStep >= 0 && !isSuccess;
47175
+ const currentStep = entityCurrentStep;
44887
47176
  const [headerError, setHeaderError] = useState(false);
44888
- const [slots, setSlots] = useState(
44889
- () => Array.from({ length: maxSlots }, () => void 0)
44890
- );
44891
- const [playState, setPlayState] = useState("idle");
44892
- const [currentStep, setCurrentStep] = useState(-1);
44893
- const [attempts, setAttempts] = useState(0);
44894
47177
  const [slotFeedback, setSlotFeedback] = useState(
44895
47178
  () => Array.from({ length: maxSlots }, () => null)
44896
47179
  );
@@ -44899,11 +47182,6 @@ function SequencerBoard({
44899
47182
  if (timerRef.current) clearTimeout(timerRef.current);
44900
47183
  }, []);
44901
47184
  const handleSlotDrop = useCallback((index, item) => {
44902
- setSlots((prev) => {
44903
- const next = [...prev];
44904
- next[index] = item;
44905
- return next;
44906
- });
44907
47185
  setSlotFeedback((prev) => {
44908
47186
  const next = [...prev];
44909
47187
  next[index] = null;
@@ -44913,11 +47191,6 @@ function SequencerBoard({
44913
47191
  if (placeEvent) emit(`UI:${placeEvent}`, { slotIndex: index, actionId: item.id });
44914
47192
  }, [emit, placeEvent]);
44915
47193
  const handleSlotRemove = useCallback((index) => {
44916
- setSlots((prev) => {
44917
- const next = [...prev];
44918
- next[index] = void 0;
44919
- return next;
44920
- });
44921
47194
  setSlotFeedback((prev) => {
44922
47195
  const next = [...prev];
44923
47196
  next[index] = null;
@@ -44928,48 +47201,34 @@ function SequencerBoard({
44928
47201
  }, [emit, removeEvent]);
44929
47202
  const handleReset = useCallback(() => {
44930
47203
  if (timerRef.current) clearTimeout(timerRef.current);
44931
- setSlots(Array.from({ length: maxSlots }, () => void 0));
44932
- setPlayState("idle");
44933
- setCurrentStep(-1);
44934
- setAttempts(0);
44935
47204
  setSlotFeedback(Array.from({ length: maxSlots }, () => null));
44936
47205
  if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
44937
47206
  }, [maxSlots, playAgainEvent, emit]);
44938
47207
  const filledSlots = slots.filter((s) => !!s);
44939
- const canPlay = filledSlots.length > 0 && playState === "idle";
47208
+ const canPlay = filledSlots.length > 0 && !isPlayingBack && !isSuccess;
44940
47209
  const handlePlay = useCallback(() => {
44941
47210
  if (!canPlay) return;
44942
47211
  setSlotFeedback(Array.from({ length: maxSlots }, () => null));
44943
47212
  emit("UI:PLAY_SOUND", { key: "confirm" });
44944
47213
  const sequence = slots.map((s) => s?.id || "");
44945
- if (playEvent) {
44946
- emit(`UI:${playEvent}`, { sequence });
44947
- }
44948
- setPlayState("playing");
44949
- setCurrentStep(0);
47214
+ const playerIds = slots.filter(Boolean).map((s) => s?.id || "");
47215
+ if (playEvent) emit(`UI:${playEvent}`, { sequence });
44950
47216
  let step = 0;
44951
47217
  const advance = () => {
44952
47218
  step++;
47219
+ emit("UI:STEP", { step });
44953
47220
  if (step >= maxSlots) {
47221
+ if (checkEvent) emit(`UI:${checkEvent}`, { sequence: playerIds });
44954
47222
  const playerSeq = slots.map((s) => s?.id);
44955
- const playerIds = slots.filter(Boolean).map((s) => s?.id || "");
44956
47223
  const success = solutions.some(
44957
47224
  (sol) => sol.length === playerIds.length && sol.every((id, i) => id === playerIds[i])
44958
47225
  );
44959
- if (checkEvent) emit(`UI:${checkEvent}`, { sequence: playerIds });
44960
47226
  if (success) {
44961
- setPlayState("success");
44962
- setCurrentStep(-1);
44963
47227
  emit("UI:PLAY_SOUND", { key: "levelComplete" });
44964
- if (completeEvent) {
44965
- emit(`UI:${completeEvent}`, { success: true, sequence: playerIds });
44966
- }
47228
+ if (completeEvent) emit(`UI:${completeEvent}`, { success: true, sequence: playerIds });
44967
47229
  } else {
44968
- setAttempts((prev) => prev + 1);
44969
47230
  const feedback = computeSlotFeedback(playerSeq, solutions);
44970
47231
  setSlotFeedback(feedback);
44971
- setPlayState("idle");
44972
- setCurrentStep(-1);
44973
47232
  emit("UI:PLAY_SOUND", { key: "fail" });
44974
47233
  const correctCount2 = feedback.filter((f3) => f3 === "correct").length;
44975
47234
  for (let ci = 0; ci < correctCount2; ci++) {
@@ -44979,7 +47238,6 @@ function SequencerBoard({
44979
47238
  }
44980
47239
  }
44981
47240
  } else {
44982
- setCurrentStep(step);
44983
47241
  timerRef.current = setTimeout(advance, stepDurationMs);
44984
47242
  }
44985
47243
  };
@@ -45029,7 +47287,7 @@ function SequencerBoard({
45029
47287
  /* @__PURE__ */ jsxs(VStack, { gap: "xs", children: [
45030
47288
  /* @__PURE__ */ jsxs(HStack, { className: "items-center justify-between", children: [
45031
47289
  /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("sequencer.yourSequence") + ":" }),
45032
- hasFeedback && playState === "idle" && /* @__PURE__ */ jsxs(Typography, { variant: "caption", className: "text-muted-foreground", children: [
47290
+ hasFeedback && !isPlayingBack && !isSuccess && /* @__PURE__ */ jsxs(Typography, { variant: "caption", className: "text-muted-foreground", children: [
45033
47291
  `${correctCount}/${maxSlots} `,
45034
47292
  "\u2705"
45035
47293
  ] })
@@ -45041,7 +47299,7 @@ function SequencerBoard({
45041
47299
  maxSlots,
45042
47300
  onSlotDrop: handleSlotDrop,
45043
47301
  onSlotRemove: handleSlotRemove,
45044
- playing: playState === "playing",
47302
+ playing: isPlayingBack,
45045
47303
  currentStep,
45046
47304
  categoryColors,
45047
47305
  slotFeedback,
@@ -45049,7 +47307,7 @@ function SequencerBoard({
45049
47307
  }
45050
47308
  )
45051
47309
  ] }),
45052
- playState !== "playing" && /* @__PURE__ */ jsx(
47310
+ !isPlayingBack && /* @__PURE__ */ jsx(
45053
47311
  ActionPalette,
45054
47312
  {
45055
47313
  actions: availableActions,
@@ -45059,8 +47317,8 @@ function SequencerBoard({
45059
47317
  label: t("sequencer.dragActions")
45060
47318
  }
45061
47319
  ),
45062
- hasFeedback && playState === "idle" && attempts > 0 && /* @__PURE__ */ jsx(Box, { className: "p-3 rounded-container bg-warning/10 border border-warning/30 text-center", children: /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-foreground", children: t(encourageKey) }) }),
45063
- playState === "success" && /* @__PURE__ */ jsx(Box, { className: "p-4 rounded-container bg-success/20 border border-success text-center", children: /* @__PURE__ */ jsx(Typography, { variant: "h5", className: "text-success", children: str(resolved.successMessage) || t("sequencer.levelComplete") }) }),
47320
+ hasFeedback && !isPlayingBack && !isSuccess && attempts > 0 && /* @__PURE__ */ jsx(Box, { className: "p-3 rounded-container bg-warning/10 border border-warning/30 text-center", children: /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-foreground", children: t(encourageKey) }) }),
47321
+ isSuccess && /* @__PURE__ */ jsx(Box, { className: "p-4 rounded-container bg-success/20 border border-success text-center", children: /* @__PURE__ */ jsx(Typography, { variant: "h5", className: "text-success", children: str(resolved.successMessage) || t("sequencer.levelComplete") }) }),
45064
47322
  /* @__PURE__ */ jsxs(HStack, { gap: "sm", children: [
45065
47323
  /* @__PURE__ */ jsx(
45066
47324
  Button,
@@ -46016,9 +48274,9 @@ function VariablePanel({
46016
48274
  /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("stateArchitect.variables", { name: entityName }) }),
46017
48275
  variables.map((v) => {
46018
48276
  const name = v.name == null ? "" : String(v.name);
46019
- const value = numField(v.value);
46020
- const max = numField(v.max, 100);
46021
- const min = numField(v.min, 0);
48277
+ const value = numField2(v.value);
48278
+ const max = numField2(v.max, 100);
48279
+ const min = numField2(v.min, 0);
46022
48280
  const unit = v.unit == null ? "" : String(v.unit);
46023
48281
  const pct = Math.round((value - min) / (max - min) * 100);
46024
48282
  const isHigh = pct > 80;
@@ -46048,12 +48306,12 @@ function VariablePanel({
46048
48306
  })
46049
48307
  ] });
46050
48308
  }
46051
- var numField;
48309
+ var numField2;
46052
48310
  var init_VariablePanel = __esm({
46053
48311
  "components/game/organisms/puzzles/state-architect/VariablePanel.tsx"() {
46054
48312
  init_atoms2();
46055
48313
  init_cn();
46056
- numField = (v, fallback = 0) => {
48314
+ numField2 = (v, fallback = 0) => {
46057
48315
  const n = Number(v);
46058
48316
  return Number.isFinite(n) ? n : fallback;
46059
48317
  };
@@ -46110,6 +48368,9 @@ function StateArchitectBoard({
46110
48368
  stepDurationMs = 600,
46111
48369
  testEvent,
46112
48370
  completeEvent,
48371
+ addTransitionEvent,
48372
+ removeTransitionEvent,
48373
+ playAgainEvent,
46113
48374
  className
46114
48375
  }) {
46115
48376
  const { emit } = useEventBus();
@@ -46122,14 +48383,16 @@ function StateArchitectBoard({
46122
48383
  const testCases = Array.isArray(resolved?.testCases) ? resolved.testCases : [];
46123
48384
  const entityTransitions = Array.isArray(resolved?.transitions) ? resolved.transitions : [];
46124
48385
  const entityVariables = rows(resolved?.variables);
46125
- const [transitions, setTransitions] = useState(entityTransitions);
48386
+ const transitions = entityTransitions;
48387
+ const attempts = typeof resolved?.attempts === "number" ? resolved.attempts : 0;
48388
+ const entityResult = str(resolved?.result) || "none";
48389
+ const isSuccess = entityResult === "win";
48390
+ const [isTesting, setIsTesting] = useState(false);
46126
48391
  const [headerError, setHeaderError] = useState(false);
46127
- const [playState, setPlayState] = useState("editing");
46128
48392
  const [currentState, setCurrentState] = useState(initialState);
46129
48393
  const [selectedState, setSelectedState] = useState(null);
46130
48394
  const [testResults, setTestResults] = useState([]);
46131
48395
  const [variables, setVariables] = useState(() => [...entityVariables]);
46132
- const [attempts, setAttempts] = useState(0);
46133
48396
  const timerRef = useRef(null);
46134
48397
  const [addingFrom, setAddingFrom] = useState(null);
46135
48398
  useEffect(() => () => {
@@ -46139,7 +48402,7 @@ function StateArchitectBoard({
46139
48402
  const GRAPH_H = 400;
46140
48403
  const positions = useMemo(() => layoutStates(entityStates, GRAPH_W, GRAPH_H), [entityStates]);
46141
48404
  const handleStateClick = useCallback((state) => {
46142
- if (playState !== "editing") return;
48405
+ if (isTesting) return;
46143
48406
  if (addingFrom) {
46144
48407
  if (addingFrom !== state) {
46145
48408
  const event = availableEvents[0] || "EVENT";
@@ -46149,20 +48412,20 @@ function StateArchitectBoard({
46149
48412
  to: state,
46150
48413
  event
46151
48414
  };
46152
- setTransitions((prev) => [...prev, newTrans]);
48415
+ if (addTransitionEvent) emit(`UI:${addTransitionEvent}`, { id: newTrans.id, from: newTrans.from, to: newTrans.to, event: newTrans.event });
46153
48416
  }
46154
48417
  setAddingFrom(null);
46155
48418
  } else {
46156
48419
  setSelectedState(state);
46157
48420
  }
46158
- }, [playState, addingFrom, availableEvents]);
48421
+ }, [isTesting, addingFrom, availableEvents, addTransitionEvent, emit]);
46159
48422
  const handleStartAddTransition = useCallback(() => {
46160
48423
  if (!selectedState) return;
46161
48424
  setAddingFrom(selectedState);
46162
48425
  }, [selectedState]);
46163
48426
  const handleRemoveTransition = useCallback((transId) => {
46164
- setTransitions((prev) => prev.filter((t2) => t2.id !== transId));
46165
- }, []);
48427
+ if (removeTransitionEvent) emit(`UI:${removeTransitionEvent}`, { id: transId });
48428
+ }, [removeTransitionEvent, emit]);
46166
48429
  const machine = useMemo(() => ({
46167
48430
  name: entityName,
46168
48431
  description: str(resolved?.description),
@@ -46176,16 +48439,16 @@ function StateArchitectBoard({
46176
48439
  }))
46177
48440
  }), [entityName, resolved, entityStates, currentState, transitions]);
46178
48441
  const handleTest = useCallback(() => {
46179
- if (playState !== "editing") return;
48442
+ if (isTesting) return;
46180
48443
  if (testEvent) emit(`UI:${testEvent}`, {});
46181
- setPlayState("testing");
48444
+ setIsTesting(true);
46182
48445
  setTestResults([]);
46183
48446
  const results = [];
46184
48447
  let testIdx = 0;
46185
48448
  const runNextTest = () => {
46186
48449
  if (testIdx >= testCases.length) {
46187
48450
  const allPassed = results.every((r) => r.passed);
46188
- setPlayState(allPassed ? "success" : "fail");
48451
+ setIsTesting(false);
46189
48452
  setTestResults(results);
46190
48453
  if (allPassed && completeEvent) {
46191
48454
  emit(`UI:${completeEvent}`, {
@@ -46193,9 +48456,6 @@ function StateArchitectBoard({
46193
48456
  passedTests: results.filter((r) => r.passed).length
46194
48457
  });
46195
48458
  }
46196
- if (!allPassed) {
46197
- setAttempts((prev) => prev + 1);
46198
- }
46199
48459
  return;
46200
48460
  }
46201
48461
  const testCase = testCases[testIdx];
@@ -46218,24 +48478,23 @@ function StateArchitectBoard({
46218
48478
  timerRef.current = setTimeout(runNextTest, stepDurationMs);
46219
48479
  };
46220
48480
  timerRef.current = setTimeout(runNextTest, stepDurationMs);
46221
- }, [playState, transitions, testCases, initialState, stepDurationMs, testEvent, completeEvent, emit]);
48481
+ }, [isTesting, transitions, testCases, initialState, stepDurationMs, testEvent, completeEvent, emit]);
46222
48482
  const handleTryAgain = useCallback(() => {
46223
48483
  if (timerRef.current) clearTimeout(timerRef.current);
46224
- setPlayState("editing");
48484
+ setIsTesting(false);
46225
48485
  setCurrentState(initialState);
46226
48486
  setTestResults([]);
46227
48487
  }, [initialState]);
46228
48488
  const handleReset = useCallback(() => {
46229
48489
  if (timerRef.current) clearTimeout(timerRef.current);
46230
- setTransitions(entityTransitions);
46231
- setPlayState("editing");
48490
+ setIsTesting(false);
46232
48491
  setCurrentState(initialState);
46233
48492
  setTestResults([]);
46234
48493
  setVariables([...entityVariables]);
46235
48494
  setSelectedState(null);
46236
48495
  setAddingFrom(null);
46237
- setAttempts(0);
46238
- }, [entityTransitions, initialState, entityVariables]);
48496
+ if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
48497
+ }, [initialState, entityVariables, playAgainEvent, emit]);
46239
48498
  const codeData = useMemo(() => ({
46240
48499
  name: entityName,
46241
48500
  states: entityStates,
@@ -46331,7 +48590,7 @@ function StateArchitectBoard({
46331
48590
  ]
46332
48591
  }
46333
48592
  ),
46334
- playState === "editing" && /* @__PURE__ */ jsx(HStack, { gap: "sm", children: /* @__PURE__ */ jsx(
48593
+ !isTesting && /* @__PURE__ */ jsx(HStack, { gap: "sm", children: /* @__PURE__ */ jsx(
46335
48594
  Button,
46336
48595
  {
46337
48596
  variant: "ghost",
@@ -46353,7 +48612,7 @@ function StateArchitectBoard({
46353
48612
  t2.guardHint,
46354
48613
  ")"
46355
48614
  ] }),
46356
- playState === "editing" && /* @__PURE__ */ jsx(
48615
+ !isTesting && /* @__PURE__ */ jsx(
46357
48616
  Button,
46358
48617
  {
46359
48618
  variant: "ghost",
@@ -46385,21 +48644,21 @@ function StateArchitectBoard({
46385
48644
  resolved.showCodeView !== false && /* @__PURE__ */ jsx(StateJsonView, { data: codeData, label: "View Code" })
46386
48645
  ] })
46387
48646
  ] }),
46388
- playState === "success" && /* @__PURE__ */ jsx(Box, { className: "p-4 rounded-container bg-success/20 border border-success text-center", children: /* @__PURE__ */ jsx(Typography, { variant: "h5", className: "text-success", children: str(resolved.successMessage) || t("stateArchitect.allPassed") }) }),
46389
- playState === "fail" && /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
48647
+ isSuccess && /* @__PURE__ */ jsx(Box, { className: "p-4 rounded-container bg-success/20 border border-success text-center", children: /* @__PURE__ */ jsx(Typography, { variant: "h5", className: "text-success", children: str(resolved.successMessage) || t("stateArchitect.allPassed") }) }),
48648
+ !isTesting && !isSuccess && testResults.some((r) => !r.passed) && /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
46390
48649
  /* @__PURE__ */ jsx(Box, { className: "p-4 rounded-container bg-warning/10 border border-warning/30 text-center", children: /* @__PURE__ */ jsx(Typography, { variant: "body1", className: "text-foreground font-medium", children: t(ENCOURAGEMENT_KEYS3[Math.min(attempts - 1, ENCOURAGEMENT_KEYS3.length - 1)] ?? ENCOURAGEMENT_KEYS3[0]) }) }),
46391
- attempts >= 3 && hint && /* @__PURE__ */ jsx(Box, { className: "p-3 rounded-container bg-accent/10 border border-accent/30", children: /* @__PURE__ */ jsxs(HStack, { className: "items-start", gap: "xs", children: [
48650
+ !isSuccess && attempts >= 2 && Boolean(str(resolved?.hint)) && /* @__PURE__ */ jsx(Box, { className: "p-3 rounded-container bg-accent/10 border border-accent/30", children: /* @__PURE__ */ jsxs(HStack, { className: "items-start", gap: "xs", children: [
46392
48651
  /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-accent font-bold shrink-0", children: "\u{1F4A1} " + t("game.hint") + ":" }),
46393
48652
  /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-foreground", children: hint })
46394
48653
  ] }) })
46395
48654
  ] }),
46396
48655
  /* @__PURE__ */ jsxs(HStack, { gap: "sm", children: [
46397
- playState === "fail" ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleTryAgain, children: "\u{1F504} " + t("puzzle.tryAgainButton") }) : /* @__PURE__ */ jsx(
48656
+ !isTesting && !isSuccess && testResults.some((r) => !r.passed) ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleTryAgain, children: "\u{1F504} " + t("puzzle.tryAgainButton") }) : /* @__PURE__ */ jsx(
46398
48657
  Button,
46399
48658
  {
46400
48659
  variant: "primary",
46401
48660
  onClick: handleTest,
46402
- disabled: playState !== "editing",
48661
+ disabled: isTesting,
46403
48662
  children: "\u25B6 " + t("game.runTests")
46404
48663
  }
46405
48664
  ),
@@ -46905,6 +49164,289 @@ var init_ToastSlot = __esm({
46905
49164
  ToastSlot.displayName = "ToastSlot";
46906
49165
  }
46907
49166
  });
49167
+ function tilesToIso(tiles) {
49168
+ return tiles.map((t) => ({
49169
+ x: t.x,
49170
+ y: t.y,
49171
+ terrain: t.terrain,
49172
+ terrainSprite: t.terrainSprite ?? TERRAIN_SPRITES[t.terrain ?? "ground"],
49173
+ passable: t.passable
49174
+ }));
49175
+ }
49176
+ function towersToUnits(towers) {
49177
+ return towers.map((t) => ({
49178
+ id: t.id,
49179
+ position: { x: t.x, y: t.y },
49180
+ name: "Tower",
49181
+ team: "player",
49182
+ sprite: TOWER_SPRITE,
49183
+ unitType: "guardian",
49184
+ health: 1,
49185
+ maxHealth: 1
49186
+ }));
49187
+ }
49188
+ function creepsToUnits(creeps) {
49189
+ return creeps.map((c) => ({
49190
+ id: c.id,
49191
+ position: { x: c.x, y: c.y },
49192
+ name: "Creep",
49193
+ team: "enemy",
49194
+ sprite: CREEP_SPRITE,
49195
+ unitType: "scrapper",
49196
+ health: c.hp,
49197
+ maxHealth: c.maxHp
49198
+ }));
49199
+ }
49200
+ function pathToFeatures(path) {
49201
+ return path.map((p2, i) => ({
49202
+ id: `path-${i}`,
49203
+ x: p2.x,
49204
+ y: p2.y,
49205
+ type: "path-marker",
49206
+ sprite: `${CDN6}world-map/road_straight.png`
49207
+ }));
49208
+ }
49209
+ function TowerDefenseBoard({
49210
+ entity,
49211
+ tiles: propTiles,
49212
+ path: propPath,
49213
+ towers: propTowers,
49214
+ creeps: propCreeps,
49215
+ gold: propGold,
49216
+ lives: propLives,
49217
+ wave: propWave,
49218
+ maxWaves: propMaxWaves,
49219
+ result: propResult,
49220
+ waveActive: propWaveActive,
49221
+ towerCost = 25,
49222
+ scale = 0.45,
49223
+ placeTowerEvent,
49224
+ startWaveEvent,
49225
+ playAgainEvent,
49226
+ gameEndEvent,
49227
+ className
49228
+ }) {
49229
+ const board = boardEntity(entity) ?? {};
49230
+ const eventBus = useEventBus();
49231
+ const { t } = useTranslate();
49232
+ const tiles = propTiles ?? rows(board.tiles);
49233
+ const path = propPath ?? rows(board.path);
49234
+ const towers = propTowers ?? rows(board.towers);
49235
+ const creeps = propCreeps ?? rows(board.creeps);
49236
+ const gold = propGold ?? num(board.gold, 100);
49237
+ const lives = propLives ?? num(board.lives, 20);
49238
+ const wave = propWave ?? num(board.wave, 1);
49239
+ const maxWaves = propMaxWaves ?? num(board.maxWaves, 5);
49240
+ const result = propResult ?? (str(board.result) || "none");
49241
+ const waveActive = propWaveActive ?? Boolean(board.waveActive);
49242
+ const [hoveredTile, setHoveredTile] = useState(null);
49243
+ const emittedGameEnd = useRef(false);
49244
+ if (result !== "none" && !emittedGameEnd.current) {
49245
+ emittedGameEnd.current = true;
49246
+ if (gameEndEvent) {
49247
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
49248
+ }
49249
+ }
49250
+ if (result === "none") {
49251
+ emittedGameEnd.current = false;
49252
+ }
49253
+ const towerPositions = useMemo(() => new Set(towers.map((t2) => `${t2.x},${t2.y}`)), [towers]);
49254
+ const pathPositions = useMemo(() => new Set(path.map((p2) => `${p2.x},${p2.y}`)), [path]);
49255
+ const validMoves = useMemo(() => {
49256
+ if (result !== "none" || gold < towerCost) return [];
49257
+ return tiles.filter(
49258
+ (t2) => t2.passable !== false && !towerPositions.has(`${t2.x},${t2.y}`) && !pathPositions.has(`${t2.x},${t2.y}`)
49259
+ ).map((t2) => ({ x: t2.x, y: t2.y }));
49260
+ }, [tiles, towerPositions, pathPositions, result, gold, towerCost]);
49261
+ const isoTiles = useMemo(() => tilesToIso(tiles), [tiles]);
49262
+ const towerUnits = useMemo(() => towersToUnits(towers), [towers]);
49263
+ const creepUnits = useMemo(() => creepsToUnits(creeps), [creeps]);
49264
+ const isoUnits = useMemo(() => [...towerUnits, ...creepUnits], [towerUnits, creepUnits]);
49265
+ const pathFeatures = useMemo(() => pathToFeatures(path), [path]);
49266
+ const handleTileClick = useCallback((x, y) => {
49267
+ if (result !== "none") return;
49268
+ if (pathPositions.has(`${x},${y}`)) return;
49269
+ if (towerPositions.has(`${x},${y}`)) return;
49270
+ if (gold < towerCost) return;
49271
+ if (placeTowerEvent) {
49272
+ eventBus.emit(`UI:${placeTowerEvent}`, { x, y });
49273
+ }
49274
+ }, [result, pathPositions, towerPositions, gold, towerCost, placeTowerEvent, eventBus]);
49275
+ const handleStartWave = useCallback(() => {
49276
+ if (startWaveEvent) {
49277
+ eventBus.emit(`UI:${startWaveEvent}`, { wave });
49278
+ }
49279
+ }, [startWaveEvent, wave, eventBus]);
49280
+ const handlePlayAgain = useCallback(() => {
49281
+ if (playAgainEvent) {
49282
+ eventBus.emit(`UI:${playAgainEvent}`, {});
49283
+ }
49284
+ }, [playAgainEvent, eventBus]);
49285
+ const canPlaceTower = gold >= towerCost && result === "none";
49286
+ const canStartWave = !waveActive && result === "none";
49287
+ return /* @__PURE__ */ jsxs(VStack, { className: cn("tower-defense-board relative min-h-[600px] bg-background", className), gap: "none", children: [
49288
+ /* @__PURE__ */ jsxs(
49289
+ HStack,
49290
+ {
49291
+ className: "px-4 py-2 border-b border-border bg-surface",
49292
+ gap: "lg",
49293
+ align: "center",
49294
+ children: [
49295
+ /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
49296
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", children: t("td.gold") ?? "Gold" }),
49297
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", weight: "bold", className: "text-warning", children: gold })
49298
+ ] }),
49299
+ /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
49300
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", children: t("td.lives") ?? "Lives" }),
49301
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", weight: "bold", className: lives <= 5 ? "text-error" : "text-success", children: lives })
49302
+ ] }),
49303
+ /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
49304
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", children: t("td.wave") ?? "Wave" }),
49305
+ /* @__PURE__ */ jsxs(Typography, { variant: "body2", weight: "bold", children: [
49306
+ wave,
49307
+ " / ",
49308
+ maxWaves
49309
+ ] })
49310
+ ] }),
49311
+ /* @__PURE__ */ jsxs(Box, { className: "ml-auto", children: [
49312
+ canStartWave && /* @__PURE__ */ jsx(Button, { variant: "primary", size: "sm", onClick: handleStartWave, children: t("td.startWave") ?? "Start Wave" }),
49313
+ waveActive && /* @__PURE__ */ jsx(Typography, { variant: "small", className: "text-warning animate-pulse", children: t("td.waveInProgress") ?? "Wave in progress\u2026" })
49314
+ ] })
49315
+ ]
49316
+ }
49317
+ ),
49318
+ /* @__PURE__ */ jsxs(Box, { className: "relative flex-1", children: [
49319
+ /* @__PURE__ */ jsx(
49320
+ IsometricCanvas_default,
49321
+ {
49322
+ tiles: isoTiles,
49323
+ units: isoUnits,
49324
+ features: pathFeatures,
49325
+ validMoves,
49326
+ hoveredTile,
49327
+ onTileClick: handleTileClick,
49328
+ onTileHover: (x, y) => setHoveredTile({ x, y }),
49329
+ onTileLeave: () => setHoveredTile(null),
49330
+ scale
49331
+ }
49332
+ ),
49333
+ hoveredTile && canPlaceTower && validMoves.some((m) => m.x === hoveredTile.x && m.y === hoveredTile.y) && /* @__PURE__ */ 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__ */ jsx(Typography, { variant: "small", children: t("td.placeTower") ?? `Place tower (${towerCost} gold)` }) })
49334
+ ] }),
49335
+ result !== "none" && /* @__PURE__ */ jsx(Box, { className: "absolute inset-0 z-50 flex items-center justify-center bg-background/70 backdrop-blur-sm", children: /* @__PURE__ */ jsxs(VStack, { className: "text-center p-8", gap: "lg", children: [
49336
+ /* @__PURE__ */ jsx(
49337
+ Typography,
49338
+ {
49339
+ variant: "h2",
49340
+ className: cn(
49341
+ "text-4xl font-black tracking-widest uppercase",
49342
+ result === "won" ? "text-warning" : "text-error"
49343
+ ),
49344
+ children: result === "won" ? t("td.victory") ?? "Victory!" : t("td.defeat") ?? "Defeat!"
49345
+ }
49346
+ ),
49347
+ /* @__PURE__ */ 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." }),
49348
+ /* @__PURE__ */ jsx(
49349
+ Button,
49350
+ {
49351
+ variant: "primary",
49352
+ className: "px-8 py-3 font-semibold",
49353
+ onClick: handlePlayAgain,
49354
+ children: t("td.playAgain") ?? "Play Again"
49355
+ }
49356
+ )
49357
+ ] }) })
49358
+ ] });
49359
+ }
49360
+ var CDN6, TERRAIN_SPRITES, TOWER_SPRITE, CREEP_SPRITE;
49361
+ var init_TowerDefenseBoard = __esm({
49362
+ "components/game/organisms/TowerDefenseBoard.tsx"() {
49363
+ "use client";
49364
+ init_cn();
49365
+ init_useEventBus();
49366
+ init_Box();
49367
+ init_Button();
49368
+ init_Typography();
49369
+ init_Stack();
49370
+ init_IsometricCanvas();
49371
+ init_boardEntity();
49372
+ CDN6 = "https://almadar-kflow-assets.web.app/shared/";
49373
+ TERRAIN_SPRITES = {
49374
+ ground: `${CDN6}isometric-blocks/PNG/Platformer tiles/platformerTile_01.png`,
49375
+ path: `${CDN6}isometric-blocks/PNG/Platformer tiles/platformerTile_09.png`,
49376
+ wall: `${CDN6}isometric-blocks/PNG/Platformer tiles/platformerTile_05.png`
49377
+ };
49378
+ TOWER_SPRITE = `${CDN6}units/guardian.png`;
49379
+ CREEP_SPRITE = `${CDN6}units/scrapper.png`;
49380
+ TowerDefenseBoard.displayName = "TowerDefenseBoard";
49381
+ }
49382
+ });
49383
+ function TowerDefenseTemplate({
49384
+ entity,
49385
+ title = "Tower Defense",
49386
+ tiles,
49387
+ path,
49388
+ towers,
49389
+ gold,
49390
+ lives,
49391
+ maxWaves,
49392
+ towerCost,
49393
+ scale,
49394
+ placeTowerEvent,
49395
+ startWaveEvent,
49396
+ playAgainEvent,
49397
+ gameEndEvent,
49398
+ className
49399
+ }) {
49400
+ const resolved = entity && typeof entity === "object" && !Array.isArray(entity) ? entity : void 0;
49401
+ return /* @__PURE__ */ jsxs(
49402
+ Box,
49403
+ {
49404
+ display: "flex",
49405
+ fullHeight: true,
49406
+ className: cn("tower-defense-template flex-col", className),
49407
+ children: [
49408
+ /* @__PURE__ */ jsx(
49409
+ HStack,
49410
+ {
49411
+ gap: "sm",
49412
+ align: "center",
49413
+ className: "px-4 py-3 border-b-2 border-border bg-surface shrink-0",
49414
+ children: /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title })
49415
+ }
49416
+ ),
49417
+ /* @__PURE__ */ jsx(Box, { className: "flex-1 relative overflow-hidden", children: /* @__PURE__ */ jsx(
49418
+ TowerDefenseBoard,
49419
+ {
49420
+ entity: resolved,
49421
+ tiles,
49422
+ path,
49423
+ towers,
49424
+ gold,
49425
+ lives,
49426
+ maxWaves,
49427
+ towerCost,
49428
+ scale,
49429
+ placeTowerEvent,
49430
+ startWaveEvent,
49431
+ playAgainEvent,
49432
+ gameEndEvent,
49433
+ className: "h-full"
49434
+ }
49435
+ ) })
49436
+ ]
49437
+ }
49438
+ );
49439
+ }
49440
+ var init_TowerDefenseTemplate = __esm({
49441
+ "components/game/templates/TowerDefenseTemplate.tsx"() {
49442
+ init_cn();
49443
+ init_Box();
49444
+ init_Stack();
49445
+ init_Typography();
49446
+ init_TowerDefenseBoard();
49447
+ TowerDefenseTemplate.displayName = "TowerDefenseTemplate";
49448
+ }
49449
+ });
46908
49450
  function useBattleState(initialUnits, eventConfig = {}, callbacks = {}) {
46909
49451
  const eventBus = useEventBus();
46910
49452
  const {
@@ -47103,17 +49645,12 @@ var init_UncontrolledBattleBoard = __esm({
47103
49645
  }
47104
49646
  });
47105
49647
  function heroPosition(h) {
49648
+ if ("position" in h && h.position != null) return h.position;
47106
49649
  return vec2(h.position);
47107
49650
  }
47108
- function heroOwner(h) {
47109
- return str(h.owner);
47110
- }
47111
49651
  function heroMovement(h) {
47112
49652
  return num(h.movement);
47113
49653
  }
47114
- function hexPassable(h) {
47115
- return h.passable !== false;
47116
- }
47117
49654
  function defaultIsInRange(from, to, range) {
47118
49655
  return Math.abs(from.x - to.x) + Math.abs(from.y - to.y) <= range;
47119
49656
  }
@@ -47149,38 +49686,41 @@ function WorldMapBoard({
47149
49686
  }) {
47150
49687
  const eventBus = useEventBus();
47151
49688
  const resolved = boardEntity(entity);
47152
- const hexes = rows(resolved?.hexes);
47153
- const heroes = rows(resolved?.heroes);
49689
+ const entityUnits = rows(resolved?.units);
49690
+ const entityTiles = rows(resolved?.tiles);
47154
49691
  const features = propFeatures ?? (Array.isArray(resolved?.features) ? resolved.features : []);
47155
49692
  const selectedHeroId = resolved?.selectedHeroId ?? null;
47156
49693
  const assetManifest = propAssetManifest ?? resolved?.assetManifest;
47157
49694
  const backgroundImage = resolved?.backgroundImage;
47158
49695
  const [hoveredTile, setHoveredTile] = useState(null);
47159
- const selectedHero = useMemo(
47160
- () => heroes.find((h) => str(h.id) === selectedHeroId) ?? null,
47161
- [heroes, selectedHeroId]
47162
- );
47163
49696
  const derivedTiles = useMemo(
47164
- () => hexes.map((hex) => ({
47165
- x: num(hex.x),
47166
- y: num(hex.y),
47167
- terrain: str(hex.terrain),
47168
- terrainSprite: hex.terrainSprite == null ? void 0 : str(hex.terrainSprite)
49697
+ () => entityTiles.map((t) => ({
49698
+ x: num(t.x),
49699
+ y: num(t.y),
49700
+ terrain: str(t.terrain),
49701
+ terrainSprite: t.terrainSprite == null ? void 0 : str(t.terrainSprite),
49702
+ passable: t.passable !== false
47169
49703
  })),
47170
- [hexes]
49704
+ [entityTiles]
47171
49705
  );
47172
49706
  const tiles = propTiles ?? derivedTiles;
47173
49707
  const baseUnits = useMemo(
47174
- () => propUnits ?? heroes.map((hero) => ({
47175
- id: str(hero.id),
47176
- position: heroPosition(hero),
47177
- name: str(hero.name),
47178
- team: heroOwner(hero) === "enemy" ? "enemy" : "player",
47179
- health: 100,
47180
- maxHealth: 100,
47181
- sprite: hero.sprite == null ? void 0 : str(hero.sprite)
49708
+ () => propUnits ?? entityUnits.map((u) => ({
49709
+ id: str(u.id),
49710
+ position: heroPosition(u),
49711
+ name: str(u.name),
49712
+ // lolo uses `team` field (not `owner`)
49713
+ team: str(u.team) === "enemy" ? "enemy" : "player",
49714
+ health: num(u.health) || 100,
49715
+ maxHealth: num(u.maxHealth) || 100,
49716
+ sprite: u.sprite == null ? void 0 : str(u.sprite)
47182
49717
  })),
47183
- [heroes, propUnits]
49718
+ [entityUnits, propUnits]
49719
+ );
49720
+ const gameUnits = baseUnits;
49721
+ const selectedHero = useMemo(
49722
+ () => gameUnits.find((u) => u.id === selectedHeroId) ?? null,
49723
+ [gameUnits, selectedHeroId]
47184
49724
  );
47185
49725
  const MOVE_SPEED_MS_PER_TILE = 300;
47186
49726
  const movementAnimRef = useRef(null);
@@ -47226,51 +49766,50 @@ function WorldMapBoard({
47226
49766
  const validMoves = useMemo(() => {
47227
49767
  if (!selectedHero || heroMovement(selectedHero) <= 0) return [];
47228
49768
  const sp = heroPosition(selectedHero);
47229
- const sOwner = heroOwner(selectedHero);
49769
+ const sTeam = str(selectedHero.team);
47230
49770
  const range = heroMovement(selectedHero);
47231
49771
  const moves = [];
47232
- hexes.forEach((hex) => {
47233
- const hx = num(hex.x);
47234
- const hy = num(hex.y);
47235
- if (!hexPassable(hex)) return;
47236
- if (hx === sp.x && hy === sp.y) return;
47237
- if (!isInRange(sp, { x: hx, y: hy }, range)) return;
47238
- if (heroes.some((h) => {
47239
- const hp = heroPosition(h);
47240
- return hp.x === hx && hp.y === hy && heroOwner(h) === sOwner;
49772
+ tiles.forEach((t) => {
49773
+ const tx = t.x;
49774
+ const ty = t.y;
49775
+ if (t.passable === false) return;
49776
+ if (tx === sp.x && ty === sp.y) return;
49777
+ if (!isInRange(sp, { x: tx, y: ty }, range)) return;
49778
+ if (gameUnits.some((u) => {
49779
+ const up = u.position ?? { x: u.x ?? -1, y: u.y ?? -1 };
49780
+ return up.x === tx && up.y === ty && str(u.team) === sTeam;
47241
49781
  })) return;
47242
- moves.push({ x: hx, y: hy });
49782
+ moves.push({ x: tx, y: ty });
47243
49783
  });
47244
49784
  return moves;
47245
- }, [selectedHero, hexes, heroes, isInRange]);
49785
+ }, [selectedHero, tiles, gameUnits, isInRange]);
47246
49786
  const attackTargets = useMemo(() => {
47247
49787
  if (!selectedHero || heroMovement(selectedHero) <= 0) return [];
47248
49788
  const sp = heroPosition(selectedHero);
47249
- const sOwner = heroOwner(selectedHero);
49789
+ const sTeam = str(selectedHero.team);
47250
49790
  const range = heroMovement(selectedHero);
47251
- return heroes.filter((h) => heroOwner(h) !== sOwner).filter((h) => isInRange(sp, heroPosition(h), range)).map((h) => heroPosition(h));
47252
- }, [selectedHero, heroes, isInRange]);
47253
- const maxY = Math.max(...hexes.map((h) => num(h.y)), 0);
49791
+ 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 });
49792
+ }, [selectedHero, gameUnits, isInRange]);
49793
+ const maxY = Math.max(...tiles.map((t) => t.y), 0);
47254
49794
  const baseOffsetX = (maxY + 1) * (TILE_WIDTH * scale / 2);
47255
49795
  const tileToScreen = useCallback(
47256
49796
  (tx, ty) => isoToScreen(tx, ty, scale, baseOffsetX),
47257
49797
  [scale, baseOffsetX]
47258
49798
  );
47259
49799
  const hoveredHex = useMemo(
47260
- () => hoveredTile ? hexes.find((h) => num(h.x) === hoveredTile.x && num(h.y) === hoveredTile.y) ?? null : null,
47261
- [hoveredTile, hexes]
49800
+ () => hoveredTile ? tiles.find((t) => t.x === hoveredTile.x && t.y === hoveredTile.y) ?? null : null,
49801
+ [hoveredTile, tiles]
47262
49802
  );
47263
49803
  const hoveredHero = useMemo(
47264
- () => hoveredTile ? heroes.find((h) => {
47265
- const hp = heroPosition(h);
47266
- return hp.x === hoveredTile.x && hp.y === hoveredTile.y;
49804
+ () => hoveredTile ? gameUnits.find((u) => {
49805
+ const up = u.position ?? { x: u.x ?? -1, y: u.y ?? -1 };
49806
+ return up.x === hoveredTile.x && up.y === hoveredTile.y;
47267
49807
  }) ?? null : null,
47268
- [hoveredTile, heroes]
49808
+ [hoveredTile, gameUnits]
47269
49809
  );
47270
49810
  const handleTileClick = useCallback((x, y) => {
47271
49811
  if (movementAnimRef.current) return;
47272
- const hex = hexes.find((h) => num(h.x) === x && num(h.y) === y);
47273
- if (!hex) return;
49812
+ const tile = tiles.find((t) => t.x === x && t.y === y);
47274
49813
  if (tileClickEvent) {
47275
49814
  eventBus.emit(`UI:${tileClickEvent}`, { x, y });
47276
49815
  }
@@ -47281,38 +49820,39 @@ function WorldMapBoard({
47281
49820
  if (heroMoveEvent) {
47282
49821
  eventBus.emit(`UI:${heroMoveEvent}`, { heroId, toX: x, toY: y });
47283
49822
  }
47284
- const feature = str(hex.feature);
49823
+ const feature = tile ? str(tile.feature) : "";
47285
49824
  if (feature && feature !== "none") {
47286
- onFeatureEnter?.(heroId, hex);
49825
+ const tileRow = tile;
49826
+ onFeatureEnter?.(heroId, tileRow);
47287
49827
  if (featureEnterEvent) {
47288
- eventBus.emit(`UI:${featureEnterEvent}`, { heroId, feature, hex });
49828
+ eventBus.emit(`UI:${featureEnterEvent}`, { heroId, feature, hex: tileRow });
47289
49829
  }
47290
49830
  }
47291
49831
  });
47292
49832
  return;
47293
49833
  }
47294
- const enemy = heroes.find((h) => {
47295
- const hp = heroPosition(h);
47296
- return hp.x === x && hp.y === y && heroOwner(h) === "enemy";
49834
+ const enemy = gameUnits.find((u) => {
49835
+ const up = u.position ?? { x: u.x ?? -1, y: u.y ?? -1 };
49836
+ return up.x === x && up.y === y && str(u.team) === "enemy";
47297
49837
  });
47298
49838
  if (selectedHero && enemy && attackTargets.some((t) => t.x === x && t.y === y)) {
47299
49839
  const attackerId = str(selectedHero.id);
47300
- const defenderId = str(enemy.id);
49840
+ const defenderId = enemy.id;
47301
49841
  onBattleEncounter?.(attackerId, defenderId);
47302
49842
  if (battleEncounterEvent) {
47303
49843
  eventBus.emit(`UI:${battleEncounterEvent}`, { attackerId, defenderId });
47304
49844
  }
47305
49845
  }
47306
- }, [hexes, heroes, selectedHero, validMoves, attackTargets, startMoveAnimation, onHeroMove, onFeatureEnter, onBattleEncounter, eventBus, tileClickEvent, heroMoveEvent, featureEnterEvent, battleEncounterEvent]);
49846
+ }, [tiles, gameUnits, selectedHero, validMoves, attackTargets, startMoveAnimation, onHeroMove, onFeatureEnter, onBattleEncounter, eventBus, tileClickEvent, heroMoveEvent, featureEnterEvent, battleEncounterEvent]);
47307
49847
  const handleUnitClick = useCallback((unitId) => {
47308
- const hero = heroes.find((h) => str(h.id) === unitId);
47309
- if (hero && (heroOwner(hero) === "player" || allowMoveAllHeroes)) {
49848
+ const unit = gameUnits.find((u) => u.id === unitId);
49849
+ if (unit && (str(unit.team) === "player" || allowMoveAllHeroes)) {
47310
49850
  onHeroSelect?.(unitId);
47311
49851
  if (heroSelectEvent) {
47312
49852
  eventBus.emit(`UI:${heroSelectEvent}`, { heroId: unitId });
47313
49853
  }
47314
49854
  }
47315
- }, [heroes, onHeroSelect, allowMoveAllHeroes, eventBus, heroSelectEvent]);
49855
+ }, [gameUnits, onHeroSelect, allowMoveAllHeroes, eventBus, heroSelectEvent]);
47316
49856
  const selectHero = useCallback((id) => {
47317
49857
  onHeroSelect?.(id);
47318
49858
  if (heroSelectEvent) {
@@ -47416,48 +49956,48 @@ function WorldMapTemplate({
47416
49956
  }
47417
49957
  );
47418
49958
  }
47419
- var CDN3, DEFAULT_WORLDMAP_TILES, DEFAULT_WORLDMAP_UNITS, DEFAULT_WORLDMAP_FEATURES, DEFAULT_WORLDMAP_MANIFEST;
49959
+ var CDN7, DEFAULT_WORLDMAP_TILES, DEFAULT_WORLDMAP_UNITS, DEFAULT_WORLDMAP_FEATURES, DEFAULT_WORLDMAP_MANIFEST;
47420
49960
  var init_WorldMapTemplate = __esm({
47421
49961
  "components/game/templates/WorldMapTemplate.tsx"() {
47422
49962
  init_WorldMapBoard();
47423
- CDN3 = "https://almadar-kflow-assets.web.app/shared";
49963
+ CDN7 = "https://almadar-kflow-assets.web.app/shared";
47424
49964
  DEFAULT_WORLDMAP_TILES = [
47425
- { x: 0, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47426
- { x: 1, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47427
- { x: 2, y: 0, terrain: "water", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
47428
- { x: 3, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47429
- { x: 4, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47430
- { x: 0, y: 1, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47431
- { x: 1, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47432
- { x: 2, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47433
- { x: 3, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47434
- { x: 4, y: 1, terrain: "water", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
47435
- { x: 0, y: 2, terrain: "water", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
47436
- { x: 1, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47437
- { x: 2, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
47438
- { x: 3, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47439
- { x: 4, y: 2, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47440
- { x: 0, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47441
- { x: 1, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47442
- { x: 2, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47443
- { x: 3, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
47444
- { x: 4, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47445
- { x: 0, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47446
- { x: 1, y: 4, terrain: "water", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
47447
- { x: 2, y: 4, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47448
- { x: 3, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47449
- { x: 4, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` }
49965
+ { x: 0, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49966
+ { x: 1, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49967
+ { x: 2, y: 0, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
49968
+ { x: 3, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49969
+ { x: 4, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49970
+ { x: 0, y: 1, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49971
+ { x: 1, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49972
+ { x: 2, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49973
+ { x: 3, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49974
+ { x: 4, y: 1, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
49975
+ { x: 0, y: 2, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
49976
+ { x: 1, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49977
+ { x: 2, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
49978
+ { x: 3, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49979
+ { x: 4, y: 2, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49980
+ { x: 0, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49981
+ { x: 1, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49982
+ { x: 2, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49983
+ { x: 3, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
49984
+ { x: 4, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49985
+ { x: 0, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49986
+ { x: 1, y: 4, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
49987
+ { x: 2, y: 4, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49988
+ { x: 3, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49989
+ { x: 4, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` }
47450
49990
  ];
47451
49991
  DEFAULT_WORLDMAP_UNITS = [
47452
- { 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` },
47453
- { 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` }
49992
+ { 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` },
49993
+ { 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` }
47454
49994
  ];
47455
49995
  DEFAULT_WORLDMAP_FEATURES = [
47456
- { id: "f1", x: 2, y: 2, type: "capital", sprite: `${CDN3}/scenes/world/capital.png` },
47457
- { id: "f2", x: 4, y: 2, type: "power_node", sprite: `${CDN3}/world-map/power_node.png` }
49996
+ { id: "f1", x: 2, y: 2, type: "capital", sprite: `${CDN7}/scenes/world/capital.png` },
49997
+ { id: "f2", x: 4, y: 2, type: "power_node", sprite: `${CDN7}/world-map/power_node.png` }
47458
49998
  ];
47459
49999
  DEFAULT_WORLDMAP_MANIFEST = {
47460
- baseUrl: CDN3,
50000
+ baseUrl: CDN7,
47461
50001
  terrains: {
47462
50002
  grass: "/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png",
47463
50003
  water: "/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png",
@@ -47502,7 +50042,7 @@ function lazyThree(name, loader) {
47502
50042
  ThreeWrapper.displayName = `Lazy(${name})`;
47503
50043
  return ThreeWrapper;
47504
50044
  }
47505
- var ThreeBoundary, FeatureRenderer, GameCanvas3D, GameCanvas3DBattleTemplate, GameCanvas3DCastleTemplate, GameCanvas3DWorldMapTemplate, COMPONENT_REGISTRY;
50045
+ var ThreeBoundary, FeatureRenderer, GameCanvas3D2, GameCanvas3DBattleTemplate, GameCanvas3DCastleTemplate, GameCanvas3DWorldMapTemplate, COMPONENT_REGISTRY;
47506
50046
  var init_component_registry_generated = __esm({
47507
50047
  "components/core/organisms/component-registry.generated.ts"() {
47508
50048
  init_AboutPageTemplate();
@@ -47612,6 +50152,7 @@ var init_component_registry_generated = __esm({
47612
50152
  init_FormSectionHeader();
47613
50153
  init_GameAudioProvider();
47614
50154
  init_GameAudioToggle();
50155
+ init_GameBoard3D();
47615
50156
  init_GameCanvas2D();
47616
50157
  init_GameHud();
47617
50158
  init_GameMenu();
@@ -47669,7 +50210,9 @@ var init_component_registry_generated = __esm({
47669
50210
  init_PageHeader();
47670
50211
  init_Pagination();
47671
50212
  init_PatternTile();
50213
+ init_PlatformerBoard();
47672
50214
  init_PlatformerCanvas();
50215
+ init_PlatformerTemplate();
47673
50216
  init_Popover();
47674
50217
  init_PositionedCanvas();
47675
50218
  init_PowerupSlots();
@@ -47693,6 +50236,8 @@ var init_component_registry_generated = __esm({
47693
50236
  init_ResourceBar();
47694
50237
  init_ResourceCounter();
47695
50238
  init_RichBlockEditor();
50239
+ init_RoguelikeBoard();
50240
+ init_RoguelikeTemplate();
47696
50241
  init_RuntimeDebugger2();
47697
50242
  init_ScaledDiagram();
47698
50243
  init_ScoreBoard();
@@ -47765,6 +50310,8 @@ var init_component_registry_generated = __esm({
47765
50310
  init_Toast();
47766
50311
  init_ToastSlot();
47767
50312
  init_Tooltip();
50313
+ init_TowerDefenseBoard();
50314
+ init_TowerDefenseTemplate();
47768
50315
  init_TraitFrame();
47769
50316
  init_TraitSlot();
47770
50317
  init_TrendIndicator();
@@ -47809,7 +50356,7 @@ var init_component_registry_generated = __esm({
47809
50356
  }
47810
50357
  };
47811
50358
  FeatureRenderer = lazyThree("FeatureRenderer", () => import('@almadar/ui/components/molecules/game/three'));
47812
- GameCanvas3D = lazyThree("GameCanvas3D", () => import('@almadar/ui/components/molecules/game/three'));
50359
+ GameCanvas3D2 = lazyThree("GameCanvas3D", () => import('@almadar/ui/components/molecules/game/three'));
47813
50360
  GameCanvas3DBattleTemplate = lazyThree("GameCanvas3DBattleTemplate", () => import('@almadar/ui/components/molecules/game/three'));
47814
50361
  GameCanvas3DCastleTemplate = lazyThree("GameCanvas3DCastleTemplate", () => import('@almadar/ui/components/molecules/game/three'));
47815
50362
  GameCanvas3DWorldMapTemplate = lazyThree("GameCanvas3DWorldMapTemplate", () => import('@almadar/ui/components/molecules/game/three'));
@@ -47928,8 +50475,9 @@ var init_component_registry_generated = __esm({
47928
50475
  "FormSectionHeader": FormSectionHeader,
47929
50476
  "GameAudioProvider": GameAudioProvider,
47930
50477
  "GameAudioToggle": GameAudioToggle,
50478
+ "GameBoard3D": GameBoard3D,
47931
50479
  "GameCanvas2D": GameCanvas2D,
47932
- "GameCanvas3D": GameCanvas3D,
50480
+ "GameCanvas3D": GameCanvas3D2,
47933
50481
  "GameCanvas3DBattleTemplate": GameCanvas3DBattleTemplate,
47934
50482
  "GameCanvas3DCastleTemplate": GameCanvas3DCastleTemplate,
47935
50483
  "GameCanvas3DWorldMapTemplate": GameCanvas3DWorldMapTemplate,
@@ -47991,7 +50539,9 @@ var init_component_registry_generated = __esm({
47991
50539
  "PageHeader": PageHeader,
47992
50540
  "Pagination": Pagination,
47993
50541
  "PatternTile": PatternTile,
50542
+ "PlatformerBoard": PlatformerBoard,
47994
50543
  "PlatformerCanvas": PlatformerCanvas,
50544
+ "PlatformerTemplate": PlatformerTemplate,
47995
50545
  "Popover": Popover,
47996
50546
  "PositionedCanvas": PositionedCanvas,
47997
50547
  "PowerupSlots": PowerupSlots,
@@ -48015,6 +50565,8 @@ var init_component_registry_generated = __esm({
48015
50565
  "ResourceBar": ResourceBar,
48016
50566
  "ResourceCounter": ResourceCounter,
48017
50567
  "RichBlockEditor": RichBlockEditor,
50568
+ "RoguelikeBoard": RoguelikeBoard,
50569
+ "RoguelikeTemplate": RoguelikeTemplate,
48018
50570
  "RuntimeDebugger": RuntimeDebugger,
48019
50571
  "ScaledDiagram": ScaledDiagram,
48020
50572
  "ScoreBoard": ScoreBoard,
@@ -48090,6 +50642,8 @@ var init_component_registry_generated = __esm({
48090
50642
  "Toast": Toast,
48091
50643
  "ToastSlot": ToastSlot,
48092
50644
  "Tooltip": Tooltip,
50645
+ "TowerDefenseBoard": TowerDefenseBoard,
50646
+ "TowerDefenseTemplate": TowerDefenseTemplate,
48093
50647
  "TraitFrame": TraitFrame,
48094
50648
  "TraitSlot": TraitSlot,
48095
50649
  "TrendIndicator": TrendIndicator,
@@ -49261,7 +51815,7 @@ init_UISlotRenderer();
49261
51815
  // providers/VerificationProvider.tsx
49262
51816
  init_useEventBus();
49263
51817
  init_verificationRegistry();
49264
- var log11 = createLogger("almadar:verify");
51818
+ var log13 = createLogger("almadar:verify");
49265
51819
  var DISPATCH_SUFFIX = ":DISPATCH";
49266
51820
  var SUCCESS_SUFFIX = ":SUCCESS";
49267
51821
  var ERROR_SUFFIX = ":ERROR";
@@ -49308,7 +51862,7 @@ function VerificationProvider({
49308
51862
  const verificationProviderLifecycleListener = (evt) => {
49309
51863
  const parsed = parseLifecycleEvent(evt.type);
49310
51864
  if (!parsed) return;
49311
- log11.debug("lifecycle:event", { kind: parsed.kind, traitName: parsed.traitName, event: parsed.event, type: evt.type });
51865
+ log13.debug("lifecycle:event", { kind: parsed.kind, traitName: parsed.traitName, event: parsed.event, type: evt.type });
49312
51866
  const payload = evt.payload ?? {};
49313
51867
  if (parsed.kind === "dispatch") {
49314
51868
  const key = `${parsed.traitName}:${String(payload["event"] ?? "")}`;
@@ -49363,7 +51917,7 @@ function VerificationProvider({
49363
51917
  },
49364
51918
  timestamp: Date.now()
49365
51919
  });
49366
- log11.debug("transition:success", { trait: parsed.traitName, event: parsed.event, from: pending?.from, to: newState, effectCount: effects.length });
51920
+ log13.debug("transition:success", { trait: parsed.traitName, event: parsed.event, from: pending?.from, to: newState, effectCount: effects.length });
49367
51921
  } else if (parsed.kind === "error" && parsed.event) {
49368
51922
  const key = `${parsed.traitName}:${parsed.event}`;
49369
51923
  const pending = pendingRef.current.get(key);
@@ -49393,7 +51947,7 @@ function VerificationProvider({
49393
51947
  },
49394
51948
  timestamp: Date.now()
49395
51949
  });
49396
- log11.warn("transition:error", { trait: parsed.traitName, event: parsed.event, from: fromState, error: errorMsg });
51950
+ log13.warn("transition:error", { trait: parsed.traitName, event: parsed.event, from: fromState, error: errorMsg });
49397
51951
  }
49398
51952
  };
49399
51953
  Object.defineProperty(verificationProviderLifecycleListener, "name", {