@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 React82 from 'react';
2
- import React82__default, { createContext, useMemo, useContext, useRef, useEffect, useCallback, Suspense, useState, useSyncExternalStore, useLayoutEffect, lazy, useId } from 'react';
2
+ import React82__default, { createContext, useMemo, useContext, useRef, useEffect, useCallback, Suspense, useState, useSyncExternalStore, useLayoutEffect, lazy, useId, forwardRef, useImperativeHandle, Component } from 'react';
3
3
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
4
  import { clsx } from 'clsx';
5
5
  import { twMerge } from 'tailwind-merge';
@@ -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
  import { StateMachineManager, createContextFromBindings, interpolateValue, collectDeclaredConfigDefaults, createServerEffectHandlers, EffectExecutor, InMemoryPersistence } from '@almadar/runtime';
46
52
  import { OrbitalServerRuntime } from '@almadar/runtime/OrbitalServerRuntime';
@@ -409,7 +415,7 @@ var init_Box = __esm({
409
415
  position,
410
416
  className,
411
417
  children,
412
- as: Component = "div",
418
+ as: Component2 = "div",
413
419
  action,
414
420
  actionPayload,
415
421
  hoverEvent,
@@ -441,7 +447,7 @@ var init_Box = __esm({
441
447
  }, [hoverEvent, eventBus, onMouseLeave]);
442
448
  const isClickable = action || onClick;
443
449
  return React82__default.createElement(
444
- Component,
450
+ Component2,
445
451
  {
446
452
  ref,
447
453
  className: cn(
@@ -579,9 +585,9 @@ function resolvePhosphor(name, weight, family) {
579
585
  (lib) => {
580
586
  const PhosphorComp = lib[target];
581
587
  if (!PhosphorComp || typeof PhosphorComp !== "object") return null;
582
- const Component = PhosphorComp;
588
+ const Component2 = PhosphorComp;
583
589
  const Adapter = (props) => /* @__PURE__ */ jsx(
584
- Component,
590
+ Component2,
585
591
  {
586
592
  weight,
587
593
  className: props.className,
@@ -605,9 +611,9 @@ function resolveTabler(name, family) {
605
611
  (lib) => {
606
612
  const TablerComp = lib[target];
607
613
  if (!TablerComp || typeof TablerComp !== "object") return null;
608
- const Component = TablerComp;
614
+ const Component2 = TablerComp;
609
615
  const Adapter = (props) => /* @__PURE__ */ jsx(
610
- Component,
616
+ Component2,
611
617
  {
612
618
  stroke: props.strokeWidth ?? 1.5,
613
619
  className: props.className,
@@ -631,9 +637,9 @@ function resolveFa(name, family) {
631
637
  (lib) => {
632
638
  const FaComp = lib[target];
633
639
  if (!FaComp || typeof FaComp !== "function") return null;
634
- const Component = FaComp;
640
+ const Component2 = FaComp;
635
641
  const Adapter = (props) => /* @__PURE__ */ jsx(
636
- Component,
642
+ Component2,
637
643
  {
638
644
  className: props.className,
639
645
  style: props.style,
@@ -1588,8 +1594,8 @@ var init_Typography = __esm({
1588
1594
  children
1589
1595
  }) => {
1590
1596
  const variant = variantProp ?? (level ? `h${level}` : "body1");
1591
- const Component = as || defaultElements[variant];
1592
- const Comp = Component;
1597
+ const Component2 = as || defaultElements[variant];
1598
+ const Comp = Component2;
1593
1599
  return /* @__PURE__ */ jsx(
1594
1600
  Comp,
1595
1601
  {
@@ -4144,10 +4150,10 @@ var init_Center = __esm({
4144
4150
  className,
4145
4151
  style,
4146
4152
  children,
4147
- as: Component = "div"
4153
+ as: Component2 = "div"
4148
4154
  }) => {
4149
4155
  const mergedStyle = minHeight ? { minHeight, ...style } : style;
4150
- const Comp = Component;
4156
+ const Comp = Component2;
4151
4157
  return /* @__PURE__ */ jsx(
4152
4158
  Comp,
4153
4159
  {
@@ -4778,7 +4784,7 @@ var init_Stack = __esm({
4778
4784
  className,
4779
4785
  style,
4780
4786
  children,
4781
- as: Component = "div",
4787
+ as: Component2 = "div",
4782
4788
  onClick,
4783
4789
  onKeyDown,
4784
4790
  role,
@@ -4796,7 +4802,7 @@ var init_Stack = __esm({
4796
4802
  };
4797
4803
  const isHorizontal = direction === "horizontal";
4798
4804
  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";
4799
- const Comp = Component;
4805
+ const Comp = Component2;
4800
4806
  return /* @__PURE__ */ jsx(
4801
4807
  Comp,
4802
4808
  {
@@ -7027,6 +7033,7 @@ var init_HealthBar = __esm({
7027
7033
  }
7028
7034
  });
7029
7035
  function ScoreDisplay({
7036
+ assetUrl = DEFAULT_ASSET_URL,
7030
7037
  value,
7031
7038
  label,
7032
7039
  icon,
@@ -7073,14 +7080,24 @@ function ScoreDisplay({
7073
7080
  className
7074
7081
  ),
7075
7082
  children: [
7076
- icon && /* @__PURE__ */ jsx("span", { className: "flex-shrink-0", children: typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon }) : /* @__PURE__ */ jsx(Icon, { icon }) }),
7083
+ assetUrl ? /* @__PURE__ */ jsx(
7084
+ "img",
7085
+ {
7086
+ src: assetUrl,
7087
+ alt: "",
7088
+ width: 20,
7089
+ height: 20,
7090
+ style: { imageRendering: "pixelated", objectFit: "contain" },
7091
+ className: "flex-shrink-0"
7092
+ }
7093
+ ) : icon ? /* @__PURE__ */ jsx("span", { className: "flex-shrink-0", children: typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon }) : /* @__PURE__ */ jsx(Icon, { icon }) }) : null,
7077
7094
  label && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: label }),
7078
7095
  /* @__PURE__ */ jsx("span", { className: "tabular-nums", children: formattedValue })
7079
7096
  ]
7080
7097
  }
7081
7098
  );
7082
7099
  }
7083
- var sizeMap2;
7100
+ var sizeMap2, DEFAULT_ASSET_URL;
7084
7101
  var init_ScoreDisplay = __esm({
7085
7102
  "components/game/atoms/ScoreDisplay.tsx"() {
7086
7103
  "use client";
@@ -7092,10 +7109,12 @@ var init_ScoreDisplay = __esm({
7092
7109
  lg: "text-2xl",
7093
7110
  xl: "text-4xl"
7094
7111
  };
7112
+ DEFAULT_ASSET_URL = "https://almadar-kflow-assets.web.app/shared/effects/particles/star_01.png";
7095
7113
  ScoreDisplay.displayName = "ScoreDisplay";
7096
7114
  }
7097
7115
  });
7098
7116
  function ControlButton({
7117
+ assetUrl = DEFAULT_ASSET_URL2,
7099
7118
  label,
7100
7119
  icon,
7101
7120
  size = "md",
@@ -7142,7 +7161,7 @@ function ControlButton({
7142
7161
  },
7143
7162
  [isPressed, releaseEvent, eventBus, onRelease]
7144
7163
  );
7145
- return /* @__PURE__ */ jsxs(
7164
+ return /* @__PURE__ */ jsx(
7146
7165
  "button",
7147
7166
  {
7148
7167
  type: "button",
@@ -7163,20 +7182,27 @@ function ControlButton({
7163
7182
  disabled && "opacity-50 cursor-not-allowed",
7164
7183
  className
7165
7184
  ),
7166
- children: [
7167
- icon && /* @__PURE__ */ jsx("span", { className: "text-2xl", children: typeof icon === "string" ? /^[a-zA-Z0-9-]+$/.test(icon) ? (() => {
7168
- const I = resolveIcon(icon);
7169
- return I ? /* @__PURE__ */ jsx(I, { className: "w-6 h-6" }) : null;
7170
- })() : icon : /* @__PURE__ */ (() => {
7171
- const I = icon;
7172
- return /* @__PURE__ */ jsx(I, { className: "w-6 h-6" });
7173
- })() }),
7174
- label && !icon && /* @__PURE__ */ jsx("span", { children: label })
7175
- ]
7185
+ children: assetUrl ? /* @__PURE__ */ jsx(
7186
+ "img",
7187
+ {
7188
+ src: assetUrl,
7189
+ alt: "",
7190
+ width: 24,
7191
+ height: 24,
7192
+ style: { imageRendering: "pixelated", objectFit: "contain" },
7193
+ className: "flex-shrink-0"
7194
+ }
7195
+ ) : icon ? /* @__PURE__ */ jsx("span", { className: "text-2xl", children: typeof icon === "string" ? /^[a-zA-Z0-9-]+$/.test(icon) ? (() => {
7196
+ const I = resolveIcon(icon);
7197
+ return I ? /* @__PURE__ */ jsx(I, { className: "w-6 h-6" }) : null;
7198
+ })() : icon : /* @__PURE__ */ (() => {
7199
+ const I = icon;
7200
+ return /* @__PURE__ */ jsx(I, { className: "w-6 h-6" });
7201
+ })() }) : label ? /* @__PURE__ */ jsx("span", { children: label }) : null
7176
7202
  }
7177
7203
  );
7178
7204
  }
7179
- var sizeMap3, shapeMap, variantMap;
7205
+ var sizeMap3, shapeMap, variantMap, DEFAULT_ASSET_URL2;
7180
7206
  var init_ControlButton = __esm({
7181
7207
  "components/game/atoms/ControlButton.tsx"() {
7182
7208
  "use client";
@@ -7199,6 +7225,7 @@ var init_ControlButton = __esm({
7199
7225
  secondary: "bg-secondary text-secondary-foreground border-border hover:bg-secondary-hover",
7200
7226
  ghost: "bg-transparent text-foreground border-border hover:bg-muted"
7201
7227
  };
7228
+ DEFAULT_ASSET_URL2 = "https://almadar-kflow-assets.web.app/shared/effects/particles/circle_01.png";
7202
7229
  ControlButton.displayName = "ControlButton";
7203
7230
  }
7204
7231
  });
@@ -7278,6 +7305,7 @@ var init_Sprite = __esm({
7278
7305
  }
7279
7306
  });
7280
7307
  function StateIndicator({
7308
+ assetUrl = DEFAULT_ASSET_URL3,
7281
7309
  state = "idle",
7282
7310
  label,
7283
7311
  size = "md",
@@ -7300,18 +7328,29 @@ function StateIndicator({
7300
7328
  className
7301
7329
  ),
7302
7330
  children: [
7303
- /* @__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 }) }),
7331
+ /* @__PURE__ */ jsx(Box, { as: "span", children: assetUrl ? /* @__PURE__ */ jsx(
7332
+ "img",
7333
+ {
7334
+ src: assetUrl,
7335
+ alt: displayLabel,
7336
+ width: 16,
7337
+ height: 16,
7338
+ style: { imageRendering: "pixelated", objectFit: "contain" },
7339
+ className: "flex-shrink-0"
7340
+ }
7341
+ ) : 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 }) }),
7304
7342
  /* @__PURE__ */ jsx(Box, { as: "span", children: displayLabel })
7305
7343
  ]
7306
7344
  }
7307
7345
  );
7308
7346
  }
7309
- var DEFAULT_STATE_STYLES, DEFAULT_STYLE, SIZE_CLASSES;
7347
+ var DEFAULT_ASSET_URL3, DEFAULT_STATE_STYLES, DEFAULT_STYLE, SIZE_CLASSES;
7310
7348
  var init_StateIndicator = __esm({
7311
7349
  "components/game/atoms/StateIndicator.tsx"() {
7312
7350
  init_Box();
7313
7351
  init_Icon();
7314
7352
  init_cn();
7353
+ DEFAULT_ASSET_URL3 = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
7315
7354
  DEFAULT_STATE_STYLES = {
7316
7355
  idle: { icon: "\u23F8", bgClass: "bg-muted" },
7317
7356
  active: { icon: "\u25B6", bgClass: "bg-success" },
@@ -7386,6 +7425,7 @@ var init_TimerDisplay = __esm({
7386
7425
  }
7387
7426
  });
7388
7427
  function ResourceCounter({
7428
+ assetUrl = DEFAULT_ASSET_URL4,
7389
7429
  icon,
7390
7430
  label = "Gold",
7391
7431
  value = 250,
@@ -7405,7 +7445,17 @@ function ResourceCounter({
7405
7445
  className
7406
7446
  ),
7407
7447
  children: [
7408
- icon && /* @__PURE__ */ jsx("span", { className: cn("flex-shrink-0", sizes.icon), children: typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon }) : /* @__PURE__ */ jsx(Icon, { icon }) }),
7448
+ assetUrl ? /* @__PURE__ */ jsx(
7449
+ "img",
7450
+ {
7451
+ src: assetUrl,
7452
+ alt: label,
7453
+ width: sizes.img,
7454
+ height: sizes.img,
7455
+ style: { imageRendering: "pixelated", objectFit: "contain" },
7456
+ className: "flex-shrink-0"
7457
+ }
7458
+ ) : 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,
7409
7459
  /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: label }),
7410
7460
  /* @__PURE__ */ jsxs("span", { className: cn("font-bold tabular-nums", color && (color in colorTokenClasses2 ? colorTokenClasses2[color] : color)), children: [
7411
7461
  value,
@@ -7418,7 +7468,7 @@ function ResourceCounter({
7418
7468
  }
7419
7469
  );
7420
7470
  }
7421
- var colorTokenClasses2, sizeMap5;
7471
+ var colorTokenClasses2, DEFAULT_ASSET_URL4, sizeMap5;
7422
7472
  var init_ResourceCounter = __esm({
7423
7473
  "components/game/atoms/ResourceCounter.tsx"() {
7424
7474
  init_cn();
@@ -7431,16 +7481,17 @@ var init_ResourceCounter = __esm({
7431
7481
  error: "text-error",
7432
7482
  muted: "text-muted-foreground"
7433
7483
  };
7484
+ DEFAULT_ASSET_URL4 = "https://almadar-kflow-assets.web.app/shared/world-map/gold_mine.png";
7434
7485
  sizeMap5 = {
7435
- sm: { wrapper: "text-xs gap-1 px-1.5 py-0.5", icon: "text-sm" },
7436
- md: { wrapper: "text-sm gap-1.5 px-2 py-1", icon: "text-base" },
7437
- lg: { wrapper: "text-base gap-2 px-3 py-1.5", icon: "text-lg" }
7486
+ sm: { wrapper: "text-xs gap-1 px-1.5 py-0.5", icon: "text-sm", img: 16 },
7487
+ md: { wrapper: "text-sm gap-1.5 px-2 py-1", icon: "text-base", img: 20 },
7488
+ lg: { wrapper: "text-base gap-2 px-3 py-1.5", icon: "text-lg", img: 28 }
7438
7489
  };
7439
7490
  ResourceCounter.displayName = "ResourceCounter";
7440
7491
  }
7441
7492
  });
7442
7493
  function ItemSlot({
7443
- assetUrl = DEFAULT_ASSET_URL,
7494
+ assetUrl = DEFAULT_ASSET_URL5,
7444
7495
  icon = "sword",
7445
7496
  label = "Iron Sword",
7446
7497
  quantity,
@@ -7499,7 +7550,7 @@ function ItemSlot({
7499
7550
  }
7500
7551
  );
7501
7552
  }
7502
- var sizeMap6, rarityBorderMap, rarityGlowMap, DEFAULT_ASSET_URL, assetSizeMap;
7553
+ var sizeMap6, rarityBorderMap, rarityGlowMap, DEFAULT_ASSET_URL5, assetSizeMap;
7503
7554
  var init_ItemSlot = __esm({
7504
7555
  "components/game/atoms/ItemSlot.tsx"() {
7505
7556
  "use client";
@@ -7524,7 +7575,7 @@ var init_ItemSlot = __esm({
7524
7575
  epic: "shadow-lg",
7525
7576
  legendary: "shadow-lg"
7526
7577
  };
7527
- DEFAULT_ASSET_URL = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
7578
+ DEFAULT_ASSET_URL5 = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
7528
7579
  assetSizeMap = {
7529
7580
  sm: 28,
7530
7581
  md: 40,
@@ -7534,6 +7585,7 @@ var init_ItemSlot = __esm({
7534
7585
  }
7535
7586
  });
7536
7587
  function TurnIndicator({
7588
+ assetUrl = DEFAULT_ASSET_URL6,
7537
7589
  currentTurn = 1,
7538
7590
  maxTurns,
7539
7591
  activeTeam,
@@ -7566,14 +7618,24 @@ function TurnIndicator({
7566
7618
  ] }),
7567
7619
  activeTeam && /* @__PURE__ */ jsxs(Fragment, { children: [
7568
7620
  /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "|" }),
7569
- /* @__PURE__ */ jsx("span", { className: cn("rounded-full bg-success/20", sizes.dot) }),
7621
+ assetUrl ? /* @__PURE__ */ jsx(
7622
+ "img",
7623
+ {
7624
+ src: assetUrl,
7625
+ alt: "",
7626
+ width: 12,
7627
+ height: 12,
7628
+ style: { imageRendering: "pixelated", objectFit: "contain" },
7629
+ className: "flex-shrink-0"
7630
+ }
7631
+ ) : /* @__PURE__ */ jsx("span", { className: cn("rounded-full bg-success/20", sizes.dot) }),
7570
7632
  /* @__PURE__ */ jsx("span", { className: "text-success", children: activeTeam })
7571
7633
  ] })
7572
7634
  ]
7573
7635
  }
7574
7636
  );
7575
7637
  }
7576
- var sizeMap7;
7638
+ var sizeMap7, DEFAULT_ASSET_URL6;
7577
7639
  var init_TurnIndicator = __esm({
7578
7640
  "components/game/atoms/TurnIndicator.tsx"() {
7579
7641
  init_cn();
@@ -7582,6 +7644,7 @@ var init_TurnIndicator = __esm({
7582
7644
  md: { wrapper: "text-sm gap-2 px-3 py-1", dot: "w-2 h-2" },
7583
7645
  lg: { wrapper: "text-base gap-2.5 px-4 py-1.5", dot: "w-2.5 h-2.5" }
7584
7646
  };
7647
+ DEFAULT_ASSET_URL6 = "https://almadar-kflow-assets.web.app/shared/effects/particles/symbol_01.png";
7585
7648
  TurnIndicator.displayName = "TurnIndicator";
7586
7649
  }
7587
7650
  });
@@ -7597,6 +7660,7 @@ function getComboScale(combo) {
7597
7660
  return "";
7598
7661
  }
7599
7662
  function ComboCounter({
7663
+ assetUrl = DEFAULT_ASSET_URL7,
7600
7664
  combo = 5,
7601
7665
  multiplier,
7602
7666
  streak,
@@ -7616,6 +7680,17 @@ function ComboCounter({
7616
7680
  className
7617
7681
  ),
7618
7682
  children: [
7683
+ assetUrl && /* @__PURE__ */ jsx(
7684
+ "img",
7685
+ {
7686
+ src: assetUrl,
7687
+ alt: "combo",
7688
+ width: 24,
7689
+ height: 24,
7690
+ style: { imageRendering: "pixelated", objectFit: "contain" },
7691
+ className: "flex-shrink-0 mb-0.5"
7692
+ }
7693
+ ),
7619
7694
  /* @__PURE__ */ jsx("span", { className: cn("font-black tabular-nums leading-none", sizes.combo, getComboIntensity(combo)), children: combo }),
7620
7695
  /* @__PURE__ */ jsx("span", { className: cn("font-bold uppercase tracking-wider text-muted-foreground", sizes.label), children: "combo" }),
7621
7696
  multiplier != null && multiplier > 1 && /* @__PURE__ */ jsxs("span", { className: cn("font-bold text-warning tabular-nums", sizes.multiplier), children: [
@@ -7630,10 +7705,11 @@ function ComboCounter({
7630
7705
  }
7631
7706
  );
7632
7707
  }
7633
- var sizeMap8;
7708
+ var DEFAULT_ASSET_URL7, sizeMap8;
7634
7709
  var init_ComboCounter = __esm({
7635
7710
  "components/game/atoms/ComboCounter.tsx"() {
7636
7711
  init_cn();
7712
+ DEFAULT_ASSET_URL7 = "https://almadar-kflow-assets.web.app/shared/effects/flash/flash00.png";
7637
7713
  sizeMap8 = {
7638
7714
  sm: { combo: "text-lg", label: "text-xs", multiplier: "text-xs" },
7639
7715
  md: { combo: "text-2xl", label: "text-xs", multiplier: "text-sm" },
@@ -7727,6 +7803,7 @@ var init_XPBar = __esm({
7727
7803
  }
7728
7804
  });
7729
7805
  function WaypointMarker({
7806
+ assetUrl = DEFAULT_ASSET_URL8,
7730
7807
  label,
7731
7808
  icon,
7732
7809
  active = true,
@@ -7765,7 +7842,17 @@ function WaypointMarker({
7765
7842
  active && !completed && "bg-info text-foreground",
7766
7843
  !active && !completed && "bg-muted"
7767
7844
  ),
7768
- children: completed ? checkIcon : icon && (typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon }) : /* @__PURE__ */ jsx(Icon, { icon }))
7845
+ children: completed ? checkIcon : assetUrl ? /* @__PURE__ */ jsx(
7846
+ "img",
7847
+ {
7848
+ src: assetUrl,
7849
+ alt: label,
7850
+ width: sizes.img,
7851
+ height: sizes.img,
7852
+ style: { imageRendering: "pixelated", objectFit: "contain" },
7853
+ className: "flex-shrink-0"
7854
+ }
7855
+ ) : icon ? typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon }) : /* @__PURE__ */ jsx(Icon, { icon }) : null
7769
7856
  }
7770
7857
  )
7771
7858
  ] }),
@@ -7782,15 +7869,16 @@ function WaypointMarker({
7782
7869
  )
7783
7870
  ] });
7784
7871
  }
7785
- var sizeMap10, checkIcon;
7872
+ var DEFAULT_ASSET_URL8, sizeMap10, checkIcon;
7786
7873
  var init_WaypointMarker = __esm({
7787
7874
  "components/game/atoms/WaypointMarker.tsx"() {
7788
7875
  init_cn();
7789
7876
  init_Icon();
7877
+ DEFAULT_ASSET_URL8 = "https://almadar-kflow-assets.web.app/shared/world-map/battle_marker.png";
7790
7878
  sizeMap10 = {
7791
- sm: { dot: "w-4 h-4", ring: "w-6 h-6", label: "text-xs mt-1" },
7792
- md: { dot: "w-6 h-6", ring: "w-8 h-8", label: "text-sm mt-1.5" },
7793
- lg: { dot: "w-8 h-8", ring: "w-10 h-10", label: "text-base mt-2" }
7879
+ sm: { dot: "w-4 h-4", ring: "w-6 h-6", label: "text-xs mt-1", img: 12 },
7880
+ md: { dot: "w-6 h-6", ring: "w-8 h-8", label: "text-sm mt-1.5", img: 18 },
7881
+ lg: { dot: "w-8 h-8", ring: "w-10 h-10", label: "text-base mt-2", img: 24 }
7794
7882
  };
7795
7883
  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" }) });
7796
7884
  WaypointMarker.displayName = "WaypointMarker";
@@ -7803,6 +7891,7 @@ function formatDuration(seconds) {
7803
7891
  return `${Math.round(seconds)}s`;
7804
7892
  }
7805
7893
  function StatusEffect({
7894
+ assetUrl = DEFAULT_ASSET_URL9,
7806
7895
  icon = "shield",
7807
7896
  label = "Shield",
7808
7897
  duration = 30,
@@ -7823,7 +7912,17 @@ function StatusEffect({
7823
7912
  ),
7824
7913
  title: label,
7825
7914
  children: [
7826
- /* @__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" }) }),
7915
+ /* @__PURE__ */ jsx("span", { className: cn("flex items-center justify-center", sizes.icon), children: assetUrl ? /* @__PURE__ */ jsx(
7916
+ "img",
7917
+ {
7918
+ src: assetUrl,
7919
+ alt: label,
7920
+ width: sizes.img,
7921
+ height: sizes.img,
7922
+ style: { imageRendering: "pixelated", objectFit: "contain" },
7923
+ className: "flex-shrink-0"
7924
+ }
7925
+ ) : typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon, size: "sm" }) : /* @__PURE__ */ jsx(Icon, { icon, size: "sm" }) }),
7827
7926
  duration !== void 0 && /* @__PURE__ */ jsx(
7828
7927
  "span",
7829
7928
  {
@@ -7850,15 +7949,16 @@ function StatusEffect({
7850
7949
  label && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground mt-0.5 text-center whitespace-nowrap", children: label })
7851
7950
  ] });
7852
7951
  }
7853
- var sizeMap11, variantStyles7;
7952
+ var DEFAULT_ASSET_URL9, sizeMap11, variantStyles7;
7854
7953
  var init_StatusEffect = __esm({
7855
7954
  "components/game/atoms/StatusEffect.tsx"() {
7856
7955
  init_cn();
7857
7956
  init_Icon();
7957
+ DEFAULT_ASSET_URL9 = "https://almadar-kflow-assets.web.app/shared/effects/particles/flame_01.png";
7858
7958
  sizeMap11 = {
7859
- sm: { container: "w-8 h-8", icon: "text-sm", badge: "text-xs -top-1 -right-1 w-4 h-4", timer: "text-[9px]" },
7860
- md: { container: "w-10 h-10", icon: "text-base", badge: "text-xs -top-1 -right-1 w-5 h-5", timer: "text-xs" },
7861
- 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" }
7959
+ 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 },
7960
+ 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 },
7961
+ 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 }
7862
7962
  };
7863
7963
  variantStyles7 = {
7864
7964
  buff: "border-success bg-success/20",
@@ -7869,6 +7969,7 @@ var init_StatusEffect = __esm({
7869
7969
  }
7870
7970
  });
7871
7971
  function DamageNumber({
7972
+ assetUrl = DEFAULT_ASSET_URL10,
7872
7973
  value = 42,
7873
7974
  type = "damage",
7874
7975
  size = "md",
@@ -7877,22 +7978,35 @@ function DamageNumber({
7877
7978
  const displayText = type === "miss" ? "MISS" : type === "heal" ? `+${value}` : `${value}`;
7878
7979
  return /* @__PURE__ */ jsxs(Fragment, { children: [
7879
7980
  /* @__PURE__ */ jsx("style", { children: floatKeyframes }),
7880
- /* @__PURE__ */ jsx(
7981
+ /* @__PURE__ */ jsxs(
7881
7982
  "span",
7882
7983
  {
7883
7984
  className: cn(
7884
- "inline-block font-mono select-none pointer-events-none",
7985
+ "inline-flex items-center gap-0.5 font-mono select-none pointer-events-none",
7885
7986
  sizeMap12[size],
7886
7987
  typeStyles[type],
7887
7988
  className
7888
7989
  ),
7889
7990
  style: { animation: "damageFloat 1s ease-out forwards" },
7890
- children: displayText
7991
+ children: [
7992
+ assetUrl && /* @__PURE__ */ jsx(
7993
+ "img",
7994
+ {
7995
+ src: assetUrl,
7996
+ alt: "",
7997
+ width: 14,
7998
+ height: 14,
7999
+ style: { imageRendering: "pixelated", objectFit: "contain" },
8000
+ className: "flex-shrink-0"
8001
+ }
8002
+ ),
8003
+ displayText
8004
+ ]
7891
8005
  }
7892
8006
  )
7893
8007
  ] });
7894
8008
  }
7895
- var sizeMap12, typeStyles, floatKeyframes;
8009
+ var sizeMap12, typeStyles, floatKeyframes, DEFAULT_ASSET_URL10;
7896
8010
  var init_DamageNumber = __esm({
7897
8011
  "components/game/atoms/DamageNumber.tsx"() {
7898
8012
  init_cn();
@@ -7914,6 +8028,7 @@ var init_DamageNumber = __esm({
7914
8028
  100% { opacity: 0; transform: translateY(-32px) scale(0.8); }
7915
8029
  }
7916
8030
  `;
8031
+ DEFAULT_ASSET_URL10 = "https://almadar-kflow-assets.web.app/shared/effects/particles/spark_01.png";
7917
8032
  DamageNumber.displayName = "DamageNumber";
7918
8033
  }
7919
8034
  });
@@ -8002,6 +8117,7 @@ var init_ChoiceButton = __esm({
8002
8117
  }
8003
8118
  });
8004
8119
  function ActionButton({
8120
+ assetUrl = DEFAULT_ASSET_URL11,
8005
8121
  label = "Attack",
8006
8122
  icon,
8007
8123
  cooldown = 0,
@@ -8041,13 +8157,23 @@ function ActionButton({
8041
8157
  }
8042
8158
  }
8043
8159
  ),
8044
- icon && /* @__PURE__ */ jsx("span", { className: cn("flex-shrink-0", sizes.icon), children: typeof icon === "string" ? (() => {
8160
+ assetUrl ? /* @__PURE__ */ jsx(
8161
+ "img",
8162
+ {
8163
+ src: assetUrl,
8164
+ alt: "",
8165
+ width: 16,
8166
+ height: 16,
8167
+ style: { imageRendering: "pixelated", objectFit: "contain" },
8168
+ className: cn("flex-shrink-0", sizes.icon)
8169
+ }
8170
+ ) : icon ? /* @__PURE__ */ jsx("span", { className: cn("flex-shrink-0", sizes.icon), children: typeof icon === "string" ? (() => {
8045
8171
  const I = resolveIcon(icon);
8046
8172
  return I ? /* @__PURE__ */ jsx(I, { className: "w-4 h-4" }) : null;
8047
8173
  })() : /* @__PURE__ */ (() => {
8048
8174
  const I = icon;
8049
8175
  return /* @__PURE__ */ jsx(I, { className: "w-4 h-4" });
8050
- })() }),
8176
+ })() }) : null,
8051
8177
  /* @__PURE__ */ jsx("span", { className: "relative z-10", children: label }),
8052
8178
  hotkey && /* @__PURE__ */ jsx(
8053
8179
  "span",
@@ -8063,7 +8189,7 @@ function ActionButton({
8063
8189
  }
8064
8190
  );
8065
8191
  }
8066
- var sizeMap13, variantStyles8;
8192
+ var sizeMap13, variantStyles8, DEFAULT_ASSET_URL11;
8067
8193
  var init_ActionButton = __esm({
8068
8194
  "components/game/atoms/ActionButton.tsx"() {
8069
8195
  init_cn();
@@ -8078,6 +8204,7 @@ var init_ActionButton = __esm({
8078
8204
  secondary: "bg-secondary text-secondary-foreground hover:bg-secondary-hover border-border",
8079
8205
  danger: "bg-error text-error-foreground hover:bg-error/90 border-error"
8080
8206
  };
8207
+ DEFAULT_ASSET_URL11 = "https://almadar-kflow-assets.web.app/shared/effects/particles/slash_01.png";
8081
8208
  ActionButton.displayName = "ActionButton";
8082
8209
  }
8083
8210
  });
@@ -8590,10 +8717,10 @@ var init_Container = __esm({
8590
8717
  center = true,
8591
8718
  className,
8592
8719
  children,
8593
- as: Component = "div"
8720
+ as: Component2 = "div"
8594
8721
  }) => {
8595
8722
  const resolvedSize = maxWidth ?? size ?? "lg";
8596
- const Comp = Component;
8723
+ const Comp = Component2;
8597
8724
  return /* @__PURE__ */ jsx(
8598
8725
  Comp,
8599
8726
  {
@@ -11427,15 +11554,15 @@ function BattleBoard({
11427
11554
  const board = boardEntity(entity) ?? {};
11428
11555
  const tiles = propTiles ?? (Array.isArray(board.tiles) ? board.tiles : []);
11429
11556
  const features = propFeatures ?? (Array.isArray(board.features) ? board.features : []);
11430
- const boardWidth = num(board.boardWidth, 8);
11431
- const boardHeight = num(board.boardHeight, 6);
11557
+ const boardWidth = num(board.gridWidth ?? board.boardWidth, 8);
11558
+ const boardHeight = num(board.gridHeight ?? board.boardHeight, 6);
11432
11559
  const assetManifest = propAssetManifest ?? board.assetManifest;
11433
11560
  const backgroundImage = board.backgroundImage;
11434
11561
  const units = rows(board.units);
11435
11562
  const selectedUnitId = board.selectedUnitId ?? null;
11436
11563
  const currentPhase = str(board.phase) || "observation";
11437
11564
  const currentTurn = num(board.turn, 1);
11438
- const gameResult = board.gameResult ?? null;
11565
+ const gameResult = board.result ?? null;
11439
11566
  const eventBus = useEventBus();
11440
11567
  const { t } = useTranslate();
11441
11568
  const [hoveredTile, setHoveredTile] = useState(null);
@@ -11456,7 +11583,7 @@ function BattleBoard({
11456
11583
  const validMoves = useMemo(() => {
11457
11584
  if (!selectedUnit || currentPhase !== "movement") return [];
11458
11585
  const moves = [];
11459
- const range = num(selectedUnit.movement);
11586
+ const range = num(board.movementRange, 2);
11460
11587
  const origin = unitPosition(selectedUnit);
11461
11588
  for (let dy = -range; dy <= range; dy++) {
11462
11589
  for (let dx = -range; dx <= range; dx++) {
@@ -19741,15 +19868,24 @@ function CastleBoard({
19741
19868
  featureClickEvent,
19742
19869
  unitClickEvent,
19743
19870
  tileClickEvent,
19871
+ playAgainEvent,
19744
19872
  className
19745
19873
  }) {
19746
19874
  const eventBus = useEventBus();
19875
+ const { t } = useTranslate();
19747
19876
  const resolved = boardEntity(entity);
19748
19877
  const tiles = propTiles ?? (Array.isArray(resolved?.tiles) ? resolved.tiles : []);
19749
19878
  const features = propFeatures ?? (Array.isArray(resolved?.features) ? resolved.features : []);
19750
19879
  const units = propUnits ?? (Array.isArray(resolved?.units) ? resolved.units : []);
19751
19880
  const assetManifest = propAssetManifest ?? resolved?.assetManifest;
19752
19881
  const backgroundImage = resolved?.backgroundImage;
19882
+ const gold = num(resolved?.gold);
19883
+ const health = num(resolved?.health);
19884
+ const maxHealth = num(resolved?.maxHealth);
19885
+ const wave = num(resolved?.wave);
19886
+ const tickCount = num(resolved?.tickCount);
19887
+ const buildings = rows(resolved?.buildings);
19888
+ const result = str(resolved?.result) || "none";
19753
19889
  const [hoveredTile, setHoveredTile] = useState(null);
19754
19890
  const [selectedFeature, setSelectedFeature] = useState(null);
19755
19891
  const hoveredFeature = useMemo(() => {
@@ -19762,7 +19898,7 @@ function CastleBoard({
19762
19898
  (u) => u.position?.x === hoveredTile.x && u.position?.y === hoveredTile.y
19763
19899
  ) ?? null;
19764
19900
  }, [hoveredTile, units]);
19765
- const maxY = Math.max(...tiles.map((t) => t.y), 0);
19901
+ const maxY = Math.max(...tiles.map((t2) => t2.y), 0);
19766
19902
  const baseOffsetX = (maxY + 1) * (TILE_WIDTH * scale / 2);
19767
19903
  const tileToScreen = useCallback(
19768
19904
  (tx, ty) => isoToScreen(tx, ty, scale, baseOffsetX),
@@ -19797,6 +19933,11 @@ function CastleBoard({
19797
19933
  }
19798
19934
  }, [units, onUnitClick, unitClickEvent, eventBus]);
19799
19935
  const clearSelection = useCallback(() => setSelectedFeature(null), []);
19936
+ const handlePlayAgain = useCallback(() => {
19937
+ if (playAgainEvent) {
19938
+ eventBus.emit(`UI:${playAgainEvent}`, {});
19939
+ }
19940
+ }, [playAgainEvent, eventBus]);
19800
19941
  const ctx = useMemo(
19801
19942
  () => ({
19802
19943
  hoveredTile,
@@ -19805,11 +19946,18 @@ function CastleBoard({
19805
19946
  selectedFeature,
19806
19947
  clearSelection,
19807
19948
  tileToScreen,
19808
- scale
19949
+ scale,
19950
+ gold,
19951
+ health,
19952
+ maxHealth,
19953
+ wave,
19954
+ tickCount,
19955
+ buildings,
19956
+ result
19809
19957
  }),
19810
- [hoveredTile, hoveredFeature, hoveredUnit, selectedFeature, clearSelection, tileToScreen, scale]
19958
+ [hoveredTile, hoveredFeature, hoveredUnit, selectedFeature, clearSelection, tileToScreen, scale, gold, health, maxHealth, wave, tickCount, buildings, result]
19811
19959
  );
19812
- return /* @__PURE__ */ jsxs("div", { className: cn("castle-board min-h-screen flex flex-col bg-background", className), children: [
19960
+ return /* @__PURE__ */ jsxs("div", { className: cn("castle-board relative min-h-screen flex flex-col bg-background", className), children: [
19813
19961
  header && header(ctx),
19814
19962
  /* @__PURE__ */ jsxs("div", { className: "flex flex-1 overflow-hidden", children: [
19815
19963
  /* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-auto p-4 relative", children: [
@@ -19834,7 +19982,29 @@ function CastleBoard({
19834
19982
  ] }),
19835
19983
  sidePanel && /* @__PURE__ */ jsx("div", { className: "w-96 shrink-0 border-l border-border bg-surface overflow-y-auto", children: sidePanel(ctx) })
19836
19984
  ] }),
19837
- footer && footer(ctx)
19985
+ footer && footer(ctx),
19986
+ 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: [
19987
+ /* @__PURE__ */ jsx(
19988
+ Typography,
19989
+ {
19990
+ variant: "h2",
19991
+ className: cn(
19992
+ "text-4xl font-black tracking-widest uppercase",
19993
+ result === "victory" ? "text-warning" : "text-error"
19994
+ ),
19995
+ children: result === "victory" ? t("battle.victory") : t("battle.defeat")
19996
+ }
19997
+ ),
19998
+ /* @__PURE__ */ jsx(
19999
+ Button,
20000
+ {
20001
+ variant: "primary",
20002
+ className: "px-8 py-3 font-semibold",
20003
+ onClick: handlePlayAgain,
20004
+ children: t("battle.playAgain")
20005
+ }
20006
+ )
20007
+ ] }) })
19838
20008
  ] });
19839
20009
  }
19840
20010
  var init_CastleBoard = __esm({
@@ -19842,6 +20012,10 @@ var init_CastleBoard = __esm({
19842
20012
  "use client";
19843
20013
  init_cn();
19844
20014
  init_useEventBus();
20015
+ init_Box();
20016
+ init_Button();
20017
+ init_Typography();
20018
+ init_Stack();
19845
20019
  init_IsometricCanvas();
19846
20020
  init_boardEntity();
19847
20021
  init_isometric();
@@ -20718,22 +20892,16 @@ function ClassifierBoard({
20718
20892
  const { emit } = useEventBus();
20719
20893
  const { t } = useTranslate();
20720
20894
  const resolved = boardEntity(entity);
20721
- const [localAssignments, setLocalAssignments] = useState({});
20722
20895
  const [headerError, setHeaderError] = useState(false);
20723
- const [localSubmitted, setLocalSubmitted] = useState(false);
20724
- const [localAttempts, setLocalAttempts] = useState(0);
20725
- const [showHint, setShowHint] = useState(false);
20726
20896
  const items = Array.isArray(resolved?.items) ? resolved.items : [];
20727
20897
  const categories = Array.isArray(resolved?.categories) ? resolved.categories : [];
20728
- const entityResult = str(resolved?.result);
20729
- const entityDrivesResult = entityResult.length > 0;
20730
- const entityHasAssignments = items.some((item) => item.assignedCategory != null);
20731
- const assignments = entityHasAssignments ? items.reduce((acc, item) => {
20732
- if (item.assignedCategory != null) acc[item.id] = item.assignedCategory;
20898
+ const result = str(resolved?.result);
20899
+ const submitted = result === "win";
20900
+ const attempts = num(resolved?.attempts);
20901
+ const assignments = items.reduce((acc, item) => {
20902
+ if (item.assignedCategory != null && item.assignedCategory !== "") acc[item.id] = item.assignedCategory;
20733
20903
  return acc;
20734
- }, {}) : localAssignments;
20735
- const attempts = entityDrivesResult ? num(resolved?.attempts) : localAttempts;
20736
- const submitted = entityDrivesResult || localSubmitted;
20904
+ }, {});
20737
20905
  const unassignedItems = items.filter((item) => !assignments[item.id]);
20738
20906
  const allAssigned = items.length > 0 && Object.keys(assignments).length === items.length;
20739
20907
  const results = submitted ? items.map((item) => ({
@@ -20741,54 +20909,25 @@ function ClassifierBoard({
20741
20909
  assigned: assignments[item.id],
20742
20910
  correct: assignments[item.id] === item.correctCategory
20743
20911
  })) : [];
20744
- const allCorrect = entityDrivesResult ? entityResult === "success" : results.length > 0 && results.every((r) => r.correct);
20912
+ const allCorrect = result === "win";
20745
20913
  const correctCount = results.filter((r) => r.correct).length;
20914
+ const showHint = attempts >= 2 && Boolean(str(resolved?.hint));
20746
20915
  const handleAssign = (itemId, categoryId) => {
20747
20916
  if (submitted) return;
20748
- if (assignEvent) {
20749
- emit(`UI:${assignEvent}`, { itemId, categoryId });
20750
- }
20751
- if (!entityHasAssignments) {
20752
- setLocalAssignments((prev) => ({ ...prev, [itemId]: categoryId }));
20753
- }
20917
+ if (assignEvent) emit(`UI:${assignEvent}`, { itemId, categoryId });
20754
20918
  };
20755
20919
  const handleUnassign = (itemId) => {
20756
20920
  if (submitted) return;
20757
- if (!entityHasAssignments) {
20758
- setLocalAssignments((prev) => {
20759
- const next = { ...prev };
20760
- delete next[itemId];
20761
- return next;
20762
- });
20763
- }
20921
+ if (assignEvent) emit(`UI:${assignEvent}`, { itemId, categoryId: "" });
20764
20922
  };
20765
- const handleSubmit = useCallback(() => {
20766
- if (checkEvent) {
20767
- emit(`UI:${checkEvent}`, {});
20768
- }
20769
- if (!entityDrivesResult) {
20770
- setLocalSubmitted(true);
20771
- setLocalAttempts((a) => a + 1);
20772
- const correct = items.every((item) => assignments[item.id] === item.correctCategory);
20773
- if (correct) {
20774
- emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
20775
- }
20776
- }
20777
- }, [checkEvent, entityDrivesResult, items, assignments, attempts, completeEvent, emit]);
20778
- const handleReset = () => {
20779
- if (!entityDrivesResult) setLocalSubmitted(false);
20780
- if (attempts >= 2 && str(resolved?.hint)) {
20781
- setShowHint(true);
20923
+ const handleSubmit = () => {
20924
+ if (checkEvent) emit(`UI:${checkEvent}`, {});
20925
+ if (allAssigned && items.every((item) => assignments[item.id] === item.correctCategory)) {
20926
+ emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
20782
20927
  }
20783
20928
  };
20784
20929
  const handleFullReset = () => {
20785
- if (playAgainEvent) {
20786
- emit(`UI:${playAgainEvent}`, {});
20787
- }
20788
- setLocalAssignments({});
20789
- setLocalSubmitted(false);
20790
- setLocalAttempts(0);
20791
- setShowHint(false);
20930
+ if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
20792
20931
  };
20793
20932
  if (!resolved) return null;
20794
20933
  const theme = resolved.theme ?? void 0;
@@ -20827,17 +20966,17 @@ function ClassifierBoard({
20827
20966
  /* @__PURE__ */ jsx(Badge, { size: "sm", children: catItems.length })
20828
20967
  ] }),
20829
20968
  /* @__PURE__ */ jsx(HStack, { gap: "xs", className: "flex-wrap min-h-[2rem]", children: catItems.map((item) => {
20830
- const result = results.find((r) => r.item.id === item.id);
20969
+ const result2 = results.find((r) => r.item.id === item.id);
20831
20970
  return /* @__PURE__ */ jsxs(
20832
20971
  Badge,
20833
20972
  {
20834
20973
  size: "sm",
20835
- className: `cursor-pointer ${result ? result.correct ? "border-success bg-success/10" : "border-error bg-error/10" : ""}`,
20974
+ className: `cursor-pointer ${result2 ? result2.correct ? "border-success bg-success/10" : "border-error bg-error/10" : ""}`,
20836
20975
  onClick: () => handleUnassign(item.id),
20837
20976
  children: [
20838
20977
  item.iconUrl && /* @__PURE__ */ jsx("img", { src: item.iconUrl, alt: "", className: "w-3 h-3 object-contain inline-block" }),
20839
20978
  item.label,
20840
- result && /* @__PURE__ */ jsx(Icon, { icon: result.correct ? CheckCircle : XCircle, size: "xs", className: result.correct ? "text-success" : "text-error" })
20979
+ result2 && /* @__PURE__ */ jsx(Icon, { icon: result2.correct ? CheckCircle : XCircle, size: "xs", className: result2.correct ? "text-success" : "text-error" })
20841
20980
  ]
20842
20981
  },
20843
20982
  item.id
@@ -20866,10 +21005,10 @@ function ClassifierBoard({
20866
21005
  ] }) }),
20867
21006
  showHint && hint && /* @__PURE__ */ jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsx(Typography, { variant: "body", children: hint }) }),
20868
21007
  /* @__PURE__ */ jsxs(HStack, { gap: "sm", justify: "center", children: [
20869
- !submitted ? /* @__PURE__ */ jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: !allAssigned, children: [
21008
+ !submitted && /* @__PURE__ */ jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: !allAssigned, children: [
20870
21009
  /* @__PURE__ */ jsx(Icon, { icon: Send, size: "sm" }),
20871
21010
  t("classifier.check")
20872
- ] }) : !allCorrect ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleReset, children: t("classifier.tryAgain") }) : null,
21011
+ ] }),
20873
21012
  /* @__PURE__ */ jsxs(Button, { variant: "secondary", onClick: handleFullReset, children: [
20874
21013
  /* @__PURE__ */ jsx(Icon, { icon: RotateCcw, size: "sm" }),
20875
21014
  t("classifier.reset")
@@ -24587,7 +24726,7 @@ var init_Flex = __esm({
24587
24726
  basis,
24588
24727
  className,
24589
24728
  children,
24590
- as: Component = "div"
24729
+ as: Component2 = "div"
24591
24730
  }) => {
24592
24731
  const flexStyle = {};
24593
24732
  if (grow !== void 0 || shrink !== void 0 || basis !== void 0) {
@@ -24599,7 +24738,7 @@ var init_Flex = __esm({
24599
24738
  flexStyle.flexBasis = typeof basis === "number" ? `${basis}px` : basis;
24600
24739
  }
24601
24740
  }
24602
- const Comp = Component;
24741
+ const Comp = Component2;
24603
24742
  return /* @__PURE__ */ jsx(
24604
24743
  Comp,
24605
24744
  {
@@ -24719,11 +24858,11 @@ var init_Grid = __esm({
24719
24858
  className,
24720
24859
  style,
24721
24860
  children,
24722
- as: Component = "div"
24861
+ as: Component2 = "div"
24723
24862
  }) => {
24724
24863
  const mergedStyle = rows2 ? { gridTemplateRows: `repeat(${rows2}, minmax(0, 1fr))`, ...style } : style;
24725
24864
  return React82__default.createElement(
24726
- Component,
24865
+ Component2,
24727
24866
  {
24728
24867
  className: cn(
24729
24868
  "grid",
@@ -26549,6 +26688,7 @@ var init_ProgressDots = __esm({
26549
26688
  }
26550
26689
  });
26551
26690
  function StatBadge({
26691
+ assetUrl = DEFAULT_ASSET_URL12,
26552
26692
  label,
26553
26693
  value = 0,
26554
26694
  max,
@@ -26572,7 +26712,17 @@ function StatBadge({
26572
26712
  className
26573
26713
  ),
26574
26714
  children: [
26575
- 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" }) }),
26715
+ assetUrl ? /* @__PURE__ */ jsx(
26716
+ "img",
26717
+ {
26718
+ src: assetUrl,
26719
+ alt: "",
26720
+ width: 16,
26721
+ height: 16,
26722
+ style: { imageRendering: "pixelated", objectFit: "contain" },
26723
+ className: "flex-shrink-0"
26724
+ }
26725
+ ) : 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,
26576
26726
  /* @__PURE__ */ jsx("span", { className: "text-muted-foreground font-medium", children: label }),
26577
26727
  format === "hearts" && max && /* @__PURE__ */ jsx(
26578
26728
  HealthBar,
@@ -26605,7 +26755,7 @@ function StatBadge({
26605
26755
  }
26606
26756
  );
26607
26757
  }
26608
- var sizeMap17, variantMap2;
26758
+ var sizeMap17, variantMap2, DEFAULT_ASSET_URL12;
26609
26759
  var init_StatBadge = __esm({
26610
26760
  "components/game/molecules/StatBadge.tsx"() {
26611
26761
  init_cn();
@@ -26624,6 +26774,7 @@ var init_StatBadge = __esm({
26624
26774
  warning: "bg-warning/15 border-warning/40 text-foreground",
26625
26775
  danger: "bg-error/15 border-error/40 text-foreground"
26626
26776
  };
26777
+ DEFAULT_ASSET_URL12 = "https://almadar-kflow-assets.web.app/shared/effects/particles/light_01.png";
26627
26778
  StatBadge.displayName = "StatBadge";
26628
26779
  }
26629
26780
  });
@@ -27280,6 +27431,7 @@ var init_TurnPanel = __esm({
27280
27431
  }
27281
27432
  });
27282
27433
  function EnemyPlate({
27434
+ assetUrl = DEFAULT_ASSET_URL13,
27283
27435
  name = "Shadow Guard",
27284
27436
  health = 80,
27285
27437
  maxHealth = 100,
@@ -27297,15 +27449,28 @@ function EnemyPlate({
27297
27449
  ),
27298
27450
  children: [
27299
27451
  /* @__PURE__ */ jsxs(Box, { className: "flex items-center justify-between gap-2", children: [
27300
- /* @__PURE__ */ jsx(
27301
- Typography,
27302
- {
27303
- variant: "caption",
27304
- weight: "bold",
27305
- className: "text-[var(--color-foreground)] truncate",
27306
- children: name
27307
- }
27308
- ),
27452
+ /* @__PURE__ */ jsxs(Box, { className: "flex items-center gap-1.5 min-w-0", children: [
27453
+ assetUrl && /* @__PURE__ */ jsx(
27454
+ "img",
27455
+ {
27456
+ src: assetUrl,
27457
+ alt: name,
27458
+ width: 24,
27459
+ height: 24,
27460
+ style: { objectFit: "cover", borderRadius: "50%" },
27461
+ className: "flex-shrink-0"
27462
+ }
27463
+ ),
27464
+ /* @__PURE__ */ jsx(
27465
+ Typography,
27466
+ {
27467
+ variant: "caption",
27468
+ weight: "bold",
27469
+ className: "text-[var(--color-foreground)] truncate",
27470
+ children: name
27471
+ }
27472
+ )
27473
+ ] }),
27309
27474
  level != null && /* @__PURE__ */ jsxs(Badge, { variant: "neutral", size: "sm", children: [
27310
27475
  "Lv.",
27311
27476
  level
@@ -27350,7 +27515,7 @@ function EnemyPlate({
27350
27515
  }
27351
27516
  );
27352
27517
  }
27353
- var effectVariantMap2, DEFAULT_ENEMY_EFFECTS;
27518
+ var effectVariantMap2, DEFAULT_ENEMY_EFFECTS, DEFAULT_ASSET_URL13;
27354
27519
  var init_EnemyPlate = __esm({
27355
27520
  "components/game/molecules/EnemyPlate.tsx"() {
27356
27521
  "use client";
@@ -27367,6 +27532,7 @@ var init_EnemyPlate = __esm({
27367
27532
  DEFAULT_ENEMY_EFFECTS = [
27368
27533
  { icon: "flame", label: "Burn", variant: "debuff" }
27369
27534
  ];
27535
+ DEFAULT_ASSET_URL13 = "https://almadar-kflow-assets.web.app/shared/portraits/shadow-legion.png";
27370
27536
  EnemyPlate.displayName = "EnemyPlate";
27371
27537
  }
27372
27538
  });
@@ -28898,13 +29064,13 @@ var init_MapView = __esm({
28898
29064
  shadowSize: [41, 41]
28899
29065
  });
28900
29066
  L.Marker.prototype.options.icon = defaultIcon;
28901
- const { useEffect: useEffect74, useRef: useRef68, useCallback: useCallback112, useState: useState107 } = React82__default;
29067
+ const { useEffect: useEffect80, useRef: useRef74, useCallback: useCallback118, useState: useState112 } = React82__default;
28902
29068
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
28903
- const { useEventBus: useEventBus3 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
29069
+ const { useEventBus: useEventBus4 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
28904
29070
  function MapUpdater({ centerLat, centerLng, zoom }) {
28905
29071
  const map = useMap();
28906
- const prevRef = useRef68({ centerLat, centerLng, zoom });
28907
- useEffect74(() => {
29072
+ const prevRef = useRef74({ centerLat, centerLng, zoom });
29073
+ useEffect80(() => {
28908
29074
  const prev = prevRef.current;
28909
29075
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
28910
29076
  map.setView([centerLat, centerLng], zoom);
@@ -28915,7 +29081,7 @@ var init_MapView = __esm({
28915
29081
  }
28916
29082
  function MapClickHandler({ onMapClick }) {
28917
29083
  const map = useMap();
28918
- useEffect74(() => {
29084
+ useEffect80(() => {
28919
29085
  if (!onMapClick) return;
28920
29086
  const handler = (e) => {
28921
29087
  onMapClick(e.latlng.lat, e.latlng.lng);
@@ -28942,9 +29108,9 @@ var init_MapView = __esm({
28942
29108
  className,
28943
29109
  showAttribution = true
28944
29110
  }) {
28945
- const eventBus = useEventBus3();
28946
- const [clickedPosition, setClickedPosition] = useState107(null);
28947
- const handleMapClick = useCallback112((lat, lng) => {
29111
+ const eventBus = useEventBus4();
29112
+ const [clickedPosition, setClickedPosition] = useState112(null);
29113
+ const handleMapClick = useCallback118((lat, lng) => {
28948
29114
  if (showClickedPin) {
28949
29115
  setClickedPosition({ lat, lng });
28950
29116
  }
@@ -28953,7 +29119,7 @@ var init_MapView = __esm({
28953
29119
  eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
28954
29120
  }
28955
29121
  }, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
28956
- const handleMarkerClick = useCallback112((marker) => {
29122
+ const handleMarkerClick = useCallback118((marker) => {
28957
29123
  onMarkerClick?.(marker);
28958
29124
  if (markerClickEvent) {
28959
29125
  eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
@@ -36080,10 +36246,10 @@ var init_Section = __esm({
36080
36246
  children,
36081
36247
  headerClassName,
36082
36248
  contentClassName,
36083
- as: Component = "section"
36249
+ as: Component2 = "section"
36084
36250
  }) => {
36085
36251
  const hasHeader = title || description || action;
36086
- const Comp = Component;
36252
+ const Comp = Component2;
36087
36253
  return /* @__PURE__ */ jsxs(
36088
36254
  Comp,
36089
36255
  {
@@ -38184,44 +38350,27 @@ function DebuggerBoard({
38184
38350
  const { t } = useTranslate();
38185
38351
  const resolved = boardEntity(entity);
38186
38352
  const [headerError, setHeaderError] = useState(false);
38187
- const [showHint, setShowHint] = useState(false);
38188
38353
  const lines = Array.isArray(resolved?.lines) ? resolved.lines : [];
38189
- const result = resolved?.result ?? null;
38354
+ const result = str(resolved?.result) || "none";
38190
38355
  const attempts = num(resolved?.attempts);
38191
- const submitted = result != null;
38356
+ const submitted = result === "win";
38357
+ const showHint = !submitted && attempts >= 2 && Boolean(str(resolved?.hint));
38192
38358
  const bugLines = lines.filter((l) => l.isBug);
38193
38359
  const flaggedLines = lines.filter((l) => l.isFlagged);
38194
38360
  const correctFlags = lines.filter((l) => l.isBug && l.isFlagged);
38195
38361
  const falseFlags = lines.filter((l) => !l.isBug && l.isFlagged);
38196
- const allCorrect = result === "win";
38362
+ const allCorrect = submitted;
38197
38363
  const toggleLine = (lineId) => {
38198
38364
  if (submitted) return;
38199
- if (toggleFlagEvent) {
38200
- emit(`UI:${toggleFlagEvent}`, { lineId });
38201
- }
38365
+ if (toggleFlagEvent) emit(`UI:${toggleFlagEvent}`, { lineId });
38202
38366
  };
38203
38367
  const handleSubmit = useCallback(() => {
38204
- if (checkEvent) {
38205
- emit(`UI:${checkEvent}`, {});
38206
- }
38368
+ if (checkEvent) emit(`UI:${checkEvent}`, {});
38207
38369
  const correct = correctFlags.length === bugLines.length && falseFlags.length === 0;
38208
- if (correct) {
38209
- emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
38210
- }
38370
+ if (correct) emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
38211
38371
  }, [checkEvent, correctFlags.length, bugLines.length, falseFlags.length, attempts, completeEvent, emit]);
38212
38372
  const handleReset = () => {
38213
- if (playAgainEvent) {
38214
- emit(`UI:${playAgainEvent}`, {});
38215
- }
38216
- if (attempts >= 2 && str(resolved?.hint)) {
38217
- setShowHint(true);
38218
- }
38219
- };
38220
- const handleFullReset = () => {
38221
- if (playAgainEvent) {
38222
- emit(`UI:${playAgainEvent}`, {});
38223
- }
38224
- setShowHint(false);
38373
+ if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
38225
38374
  };
38226
38375
  if (!resolved) return null;
38227
38376
  const theme = resolved.theme ?? void 0;
@@ -38245,7 +38394,7 @@ function DebuggerBoard({
38245
38394
  /* @__PURE__ */ jsx(Typography, { variant: "h4", weight: "bold", children: str(resolved.title) })
38246
38395
  ] }),
38247
38396
  /* @__PURE__ */ jsx(Typography, { variant: "body", children: str(resolved.description) }),
38248
- /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-muted-foreground", children: t("debugger.findBugs", { count: String(num(resolved.bugCount)) }) })
38397
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-muted-foreground", children: t("debugger.findBugs", { count: String(lines.filter((l) => l.isBug).length) }) })
38249
38398
  ] }) }),
38250
38399
  /* @__PURE__ */ jsx(Card, { className: "p-0 overflow-hidden", children: /* @__PURE__ */ jsx(VStack, { gap: "none", children: lines.map((line, i) => {
38251
38400
  const isFlagged = !!line.isFlagged;
@@ -38295,11 +38444,11 @@ function DebuggerBoard({
38295
38444
  ] }) }),
38296
38445
  showHint && hint && /* @__PURE__ */ jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsx(Typography, { variant: "body", children: hint }) }),
38297
38446
  /* @__PURE__ */ jsxs(HStack, { gap: "sm", justify: "center", children: [
38298
- !submitted ? /* @__PURE__ */ jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: flaggedLines.length === 0, children: [
38447
+ !submitted && /* @__PURE__ */ jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: flaggedLines.length === 0, children: [
38299
38448
  /* @__PURE__ */ jsx(Icon, { icon: Send, size: "sm" }),
38300
38449
  t("debugger.submit")
38301
- ] }) : !allCorrect ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleReset, children: t("debugger.tryAgain") }) : null,
38302
- /* @__PURE__ */ jsxs(Button, { variant: "secondary", onClick: handleFullReset, children: [
38450
+ ] }),
38451
+ /* @__PURE__ */ jsxs(Button, { variant: "secondary", onClick: handleReset, children: [
38303
38452
  /* @__PURE__ */ jsx(Icon, { icon: RotateCcw, size: "sm" }),
38304
38453
  t("debugger.reset")
38305
38454
  ] })
@@ -39300,39 +39449,40 @@ function EventHandlerBoard({
39300
39449
  stepDurationMs = 800,
39301
39450
  playEvent,
39302
39451
  completeEvent,
39452
+ editRuleEvent,
39453
+ playAgainEvent,
39303
39454
  className
39304
39455
  }) {
39305
39456
  const { emit } = useEventBus();
39306
39457
  const { t } = useTranslate();
39307
39458
  const resolved = boardEntity(entity);
39308
- const entityObjects = rows(resolved?.objects);
39309
- const [objects, setObjects] = useState(() => [...entityObjects]);
39459
+ const objects = rows(resolved?.objects);
39460
+ const entityResult = str(resolved?.result) || "none";
39461
+ const isSuccess = entityResult === "win";
39462
+ const attempts = typeof resolved?.attempts === "number" ? resolved.attempts : 0;
39310
39463
  const [selectedObjectId, setSelectedObjectId] = useState(
39311
- entityObjects[0] ? objId(entityObjects[0]) : null
39464
+ objects[0] ? objId(objects[0]) : null
39312
39465
  );
39313
39466
  const [headerError, setHeaderError] = useState(false);
39314
- const [playState, setPlayState] = useState("editing");
39467
+ const [isPlaying, setIsPlaying] = useState(false);
39315
39468
  const [eventLog, setEventLog] = useState([]);
39316
- const [attempts, setAttempts] = useState(0);
39317
39469
  const timerRef = useRef(null);
39318
39470
  const logIdCounter = useRef(0);
39319
39471
  useEffect(() => () => {
39320
39472
  if (timerRef.current) clearTimeout(timerRef.current);
39321
39473
  }, []);
39322
- const selectedObject = objects.find((o) => objId(o) === selectedObjectId) || null;
39474
+ const selectedObject = objects.find((o) => objId(o) === selectedObjectId) ?? null;
39323
39475
  const handleRulesChange = useCallback((objectId, rules) => {
39324
- setObjects((prev) => prev.map(
39325
- (o) => objId(o) === objectId ? { ...o, rules } : o
39326
- ));
39327
- }, []);
39476
+ if (editRuleEvent) emit(`UI:${editRuleEvent}`, { objectId, rules });
39477
+ }, [editRuleEvent, emit]);
39328
39478
  const addLogEntry = useCallback((icon, message, status = "done") => {
39329
39479
  const id = `log-${logIdCounter.current++}`;
39330
39480
  setEventLog((prev) => [...prev, { id, timestamp: Date.now(), icon, message, status }]);
39331
39481
  }, []);
39332
39482
  const handlePlay = useCallback(() => {
39333
- if (playState !== "editing") return;
39483
+ if (isPlaying || isSuccess) return;
39334
39484
  if (playEvent) emit(`UI:${playEvent}`, {});
39335
- setPlayState("playing");
39485
+ setIsPlaying(true);
39336
39486
  setEventLog([]);
39337
39487
  const allRules = [];
39338
39488
  objects.forEach((obj) => {
@@ -39348,15 +39498,8 @@ function EventHandlerBoard({
39348
39498
  let goalReached = false;
39349
39499
  const processNext = () => {
39350
39500
  if (eventQueue.length === 0 || stepIdx > 20) {
39351
- if (goalReached) {
39352
- setPlayState("success");
39353
- if (completeEvent) {
39354
- emit(`UI:${completeEvent}`, { success: true });
39355
- }
39356
- } else {
39357
- setAttempts((prev) => prev + 1);
39358
- setPlayState("fail");
39359
- }
39501
+ setIsPlaying(false);
39502
+ if (goalReached && completeEvent) emit(`UI:${completeEvent}`, { success: true });
39360
39503
  return;
39361
39504
  }
39362
39505
  const currentEvent = eventQueue.shift();
@@ -39387,21 +39530,19 @@ function EventHandlerBoard({
39387
39530
  addLogEntry("\u{1F3AC}", t("eventHandler.simulationStarted", { events: triggers.join(", ") }), "active");
39388
39531
  }
39389
39532
  timerRef.current = setTimeout(processNext, stepDurationMs);
39390
- }, [playState, objects, resolved, stepDurationMs, playEvent, completeEvent, emit, addLogEntry, t]);
39533
+ }, [isPlaying, isSuccess, objects, resolved, stepDurationMs, playEvent, completeEvent, emit, addLogEntry, t]);
39391
39534
  const handleTryAgain = useCallback(() => {
39392
39535
  if (timerRef.current) clearTimeout(timerRef.current);
39393
- setPlayState("editing");
39536
+ setIsPlaying(false);
39394
39537
  setEventLog([]);
39395
39538
  }, []);
39396
39539
  const handleReset = useCallback(() => {
39397
39540
  if (timerRef.current) clearTimeout(timerRef.current);
39398
- const resetObjects = rows(resolved?.objects);
39399
- setObjects([...resetObjects]);
39400
- setPlayState("editing");
39541
+ setIsPlaying(false);
39401
39542
  setEventLog([]);
39402
- setSelectedObjectId(resetObjects[0] ? objId(resetObjects[0]) : null);
39403
- setAttempts(0);
39404
- }, [resolved?.objects]);
39543
+ setSelectedObjectId(objects[0] ? objId(objects[0]) : null);
39544
+ if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
39545
+ }, [objects, playAgainEvent, emit]);
39405
39546
  if (!resolved) return null;
39406
39547
  const objectViewers = objects.map((obj) => {
39407
39548
  const states = objStates(obj);
@@ -39470,12 +39611,12 @@ function EventHandlerBoard({
39470
39611
  {
39471
39612
  object: selectedObject,
39472
39613
  onRulesChange: handleRulesChange,
39473
- disabled: playState !== "editing"
39614
+ disabled: isPlaying
39474
39615
  }
39475
39616
  ),
39476
39617
  eventLog.length > 0 && /* @__PURE__ */ jsx(EventLog, { entries: eventLog }),
39477
- 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") }) }),
39478
- playState === "fail" && /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
39618
+ 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") }) }),
39619
+ !isPlaying && !isSuccess && attempts > 0 && /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
39479
39620
  /* @__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) }) }),
39480
39621
  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: [
39481
39622
  /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-accent font-bold shrink-0", children: "\u{1F4A1} " + t("game.hint") + ":" }),
@@ -39483,12 +39624,12 @@ function EventHandlerBoard({
39483
39624
  ] }) })
39484
39625
  ] }),
39485
39626
  /* @__PURE__ */ jsxs(HStack, { gap: "sm", children: [
39486
- playState === "fail" ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleTryAgain, children: "\u{1F504} " + t("puzzle.tryAgainButton") }) : /* @__PURE__ */ jsx(
39627
+ !isPlaying && !isSuccess && attempts > 0 ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleTryAgain, children: "\u{1F504} " + t("puzzle.tryAgainButton") }) : /* @__PURE__ */ jsx(
39487
39628
  Button,
39488
39629
  {
39489
39630
  variant: "primary",
39490
39631
  onClick: handlePlay,
39491
- disabled: playState !== "editing",
39632
+ disabled: isPlaying || isSuccess,
39492
39633
  children: "\u25B6 " + t("game.play")
39493
39634
  }
39494
39635
  ),
@@ -40673,6 +40814,1568 @@ var init_GameAudioToggle = __esm({
40673
40814
  GameAudioToggle.displayName = "GameAudioToggle";
40674
40815
  }
40675
40816
  });
40817
+ function detectAssetRoot(modelUrl) {
40818
+ const idx = modelUrl.indexOf("/3d/");
40819
+ if (idx !== -1) {
40820
+ return modelUrl.substring(0, idx + 4);
40821
+ }
40822
+ return modelUrl.substring(0, modelUrl.lastIndexOf("/") + 1);
40823
+ }
40824
+ function createGLTFLoaderForUrl(url) {
40825
+ const loader = new GLTFLoader();
40826
+ loader.setResourcePath(detectAssetRoot(url));
40827
+ return loader;
40828
+ }
40829
+ var AssetLoader;
40830
+ var init_AssetLoader = __esm({
40831
+ "components/game/molecules/three/loaders/AssetLoader.ts"() {
40832
+ AssetLoader = class {
40833
+ constructor() {
40834
+ __publicField(this, "objLoader");
40835
+ __publicField(this, "textureLoader");
40836
+ __publicField(this, "modelCache");
40837
+ __publicField(this, "textureCache");
40838
+ __publicField(this, "loadingPromises");
40839
+ this.objLoader = new OBJLoader();
40840
+ this.textureLoader = new THREE.TextureLoader();
40841
+ this.modelCache = /* @__PURE__ */ new Map();
40842
+ this.textureCache = /* @__PURE__ */ new Map();
40843
+ this.loadingPromises = /* @__PURE__ */ new Map();
40844
+ }
40845
+ /**
40846
+ * Load a GLB/GLTF model
40847
+ * @param url - URL to the .glb or .gltf file
40848
+ * @returns Promise with loaded model scene and animations
40849
+ */
40850
+ async loadModel(url) {
40851
+ if (this.modelCache.has(url)) {
40852
+ return this.modelCache.get(url);
40853
+ }
40854
+ if (this.loadingPromises.has(url)) {
40855
+ return this.loadingPromises.get(url);
40856
+ }
40857
+ const loader = createGLTFLoaderForUrl(url);
40858
+ const loadPromise = loader.loadAsync(url).then((gltf) => {
40859
+ const result = {
40860
+ scene: gltf.scene,
40861
+ animations: gltf.animations || []
40862
+ };
40863
+ this.modelCache.set(url, result);
40864
+ this.loadingPromises.delete(url);
40865
+ return result;
40866
+ }).catch((error) => {
40867
+ this.loadingPromises.delete(url);
40868
+ throw new Error(`Failed to load model ${url}: ${error.message}`);
40869
+ });
40870
+ this.loadingPromises.set(url, loadPromise);
40871
+ return loadPromise;
40872
+ }
40873
+ /**
40874
+ * Load an OBJ model (fallback for non-GLB assets)
40875
+ * @param url - URL to the .obj file
40876
+ * @returns Promise with loaded object group
40877
+ */
40878
+ async loadOBJ(url) {
40879
+ if (this.modelCache.has(url)) {
40880
+ return this.modelCache.get(url).scene;
40881
+ }
40882
+ if (this.loadingPromises.has(url)) {
40883
+ const result = await this.loadingPromises.get(url);
40884
+ return result.scene;
40885
+ }
40886
+ const loadPromise = this.objLoader.loadAsync(url).then((group) => {
40887
+ const result = {
40888
+ scene: group,
40889
+ animations: []
40890
+ };
40891
+ this.modelCache.set(url, result);
40892
+ this.loadingPromises.delete(url);
40893
+ return result;
40894
+ }).catch((error) => {
40895
+ this.loadingPromises.delete(url);
40896
+ throw new Error(`Failed to load OBJ ${url}: ${error.message}`);
40897
+ });
40898
+ this.loadingPromises.set(url, loadPromise);
40899
+ return (await loadPromise).scene;
40900
+ }
40901
+ /**
40902
+ * Load a texture
40903
+ * @param url - URL to the texture image
40904
+ * @returns Promise with loaded texture
40905
+ */
40906
+ async loadTexture(url) {
40907
+ if (this.textureCache.has(url)) {
40908
+ return this.textureCache.get(url);
40909
+ }
40910
+ if (this.loadingPromises.has(`texture:${url}`)) {
40911
+ return this.loadingPromises.get(`texture:${url}`);
40912
+ }
40913
+ const loadPromise = this.textureLoader.loadAsync(url).then((texture) => {
40914
+ texture.colorSpace = THREE.SRGBColorSpace;
40915
+ this.textureCache.set(url, texture);
40916
+ this.loadingPromises.delete(`texture:${url}`);
40917
+ return texture;
40918
+ }).catch((error) => {
40919
+ this.loadingPromises.delete(`texture:${url}`);
40920
+ throw new Error(`Failed to load texture ${url}: ${error.message}`);
40921
+ });
40922
+ this.loadingPromises.set(`texture:${url}`, loadPromise);
40923
+ return loadPromise;
40924
+ }
40925
+ /**
40926
+ * Preload multiple assets
40927
+ * @param urls - Array of asset URLs to preload
40928
+ * @returns Promise that resolves when all assets are loaded
40929
+ */
40930
+ async preload(urls) {
40931
+ const promises = urls.map((url) => {
40932
+ if (url.endsWith(".glb") || url.endsWith(".gltf")) {
40933
+ return this.loadModel(url).catch(() => null);
40934
+ } else if (url.endsWith(".obj")) {
40935
+ return this.loadOBJ(url).catch(() => null);
40936
+ } else if (/\.(png|jpg|jpeg|webp)$/i.test(url)) {
40937
+ return this.loadTexture(url).catch(() => null);
40938
+ }
40939
+ return Promise.resolve(null);
40940
+ });
40941
+ await Promise.all(promises);
40942
+ }
40943
+ /**
40944
+ * Check if a model is cached
40945
+ * @param url - Model URL
40946
+ */
40947
+ hasModel(url) {
40948
+ return this.modelCache.has(url);
40949
+ }
40950
+ /**
40951
+ * Check if a texture is cached
40952
+ * @param url - Texture URL
40953
+ */
40954
+ hasTexture(url) {
40955
+ return this.textureCache.has(url);
40956
+ }
40957
+ /**
40958
+ * Get cached model (throws if not cached)
40959
+ * @param url - Model URL
40960
+ */
40961
+ getModel(url) {
40962
+ const model = this.modelCache.get(url);
40963
+ if (!model) {
40964
+ throw new Error(`Model ${url} not in cache`);
40965
+ }
40966
+ return model;
40967
+ }
40968
+ /**
40969
+ * Get cached texture (throws if not cached)
40970
+ * @param url - Texture URL
40971
+ */
40972
+ getTexture(url) {
40973
+ const texture = this.textureCache.get(url);
40974
+ if (!texture) {
40975
+ throw new Error(`Texture ${url} not in cache`);
40976
+ }
40977
+ return texture;
40978
+ }
40979
+ /**
40980
+ * Clear all caches
40981
+ */
40982
+ clearCache() {
40983
+ this.textureCache.forEach((texture) => {
40984
+ texture.dispose();
40985
+ });
40986
+ this.modelCache.forEach((model) => {
40987
+ model.scene.traverse((child) => {
40988
+ if (child instanceof THREE.Mesh) {
40989
+ child.geometry.dispose();
40990
+ if (Array.isArray(child.material)) {
40991
+ child.material.forEach((m) => m.dispose());
40992
+ } else {
40993
+ child.material.dispose();
40994
+ }
40995
+ }
40996
+ });
40997
+ });
40998
+ this.modelCache.clear();
40999
+ this.textureCache.clear();
41000
+ this.loadingPromises.clear();
41001
+ }
41002
+ /**
41003
+ * Get cache statistics
41004
+ */
41005
+ getStats() {
41006
+ return {
41007
+ models: this.modelCache.size,
41008
+ textures: this.textureCache.size,
41009
+ loading: this.loadingPromises.size
41010
+ };
41011
+ }
41012
+ };
41013
+ new AssetLoader();
41014
+ }
41015
+ });
41016
+ function useAssetLoader(options = {}) {
41017
+ const { preloadUrls = [], loader: customLoader } = options;
41018
+ const loaderRef = useRef(customLoader || new AssetLoader());
41019
+ const [state, setState] = useState({
41020
+ isLoading: false,
41021
+ progress: 0,
41022
+ loaded: 0,
41023
+ total: 0,
41024
+ errors: []
41025
+ });
41026
+ useEffect(() => {
41027
+ if (preloadUrls.length > 0) {
41028
+ preload(preloadUrls);
41029
+ }
41030
+ }, []);
41031
+ const updateProgress = useCallback((loaded, total) => {
41032
+ setState((prev) => ({
41033
+ ...prev,
41034
+ loaded,
41035
+ total,
41036
+ progress: total > 0 ? Math.round(loaded / total * 100) : 0
41037
+ }));
41038
+ }, []);
41039
+ const loadModel = useCallback(
41040
+ async (url) => {
41041
+ setState((prev) => ({ ...prev, isLoading: true }));
41042
+ try {
41043
+ const model = await loaderRef.current.loadModel(url);
41044
+ setState((prev) => ({
41045
+ ...prev,
41046
+ isLoading: false,
41047
+ loaded: prev.loaded + 1
41048
+ }));
41049
+ return model;
41050
+ } catch (error) {
41051
+ const errorMsg = error instanceof Error ? error.message : String(error);
41052
+ setState((prev) => ({
41053
+ ...prev,
41054
+ isLoading: false,
41055
+ errors: [...prev.errors, errorMsg]
41056
+ }));
41057
+ throw error;
41058
+ }
41059
+ },
41060
+ []
41061
+ );
41062
+ const loadOBJ = useCallback(
41063
+ async (url) => {
41064
+ setState((prev) => ({ ...prev, isLoading: true }));
41065
+ try {
41066
+ const model = await loaderRef.current.loadOBJ(url);
41067
+ setState((prev) => ({
41068
+ ...prev,
41069
+ isLoading: false,
41070
+ loaded: prev.loaded + 1
41071
+ }));
41072
+ return model;
41073
+ } catch (error) {
41074
+ const errorMsg = error instanceof Error ? error.message : String(error);
41075
+ setState((prev) => ({
41076
+ ...prev,
41077
+ isLoading: false,
41078
+ errors: [...prev.errors, errorMsg]
41079
+ }));
41080
+ throw error;
41081
+ }
41082
+ },
41083
+ []
41084
+ );
41085
+ const loadTexture = useCallback(
41086
+ async (url) => {
41087
+ setState((prev) => ({ ...prev, isLoading: true }));
41088
+ try {
41089
+ const texture = await loaderRef.current.loadTexture(url);
41090
+ setState((prev) => ({
41091
+ ...prev,
41092
+ isLoading: false,
41093
+ loaded: prev.loaded + 1
41094
+ }));
41095
+ return texture;
41096
+ } catch (error) {
41097
+ const errorMsg = error instanceof Error ? error.message : String(error);
41098
+ setState((prev) => ({
41099
+ ...prev,
41100
+ isLoading: false,
41101
+ errors: [...prev.errors, errorMsg]
41102
+ }));
41103
+ throw error;
41104
+ }
41105
+ },
41106
+ []
41107
+ );
41108
+ const preload = useCallback(
41109
+ async (urls) => {
41110
+ setState((prev) => ({
41111
+ ...prev,
41112
+ isLoading: true,
41113
+ total: urls.length,
41114
+ loaded: 0,
41115
+ errors: []
41116
+ }));
41117
+ let completed = 0;
41118
+ const errors = [];
41119
+ await Promise.all(
41120
+ urls.map(async (url) => {
41121
+ try {
41122
+ if (url.endsWith(".glb") || url.endsWith(".gltf")) {
41123
+ await loaderRef.current.loadModel(url);
41124
+ } else if (url.endsWith(".obj")) {
41125
+ await loaderRef.current.loadOBJ(url);
41126
+ } else if (/\.(png|jpg|jpeg|webp)$/i.test(url)) {
41127
+ await loaderRef.current.loadTexture(url);
41128
+ }
41129
+ completed++;
41130
+ updateProgress(completed, urls.length);
41131
+ } catch (error) {
41132
+ const errorMsg = error instanceof Error ? error.message : String(error);
41133
+ errors.push(`${url}: ${errorMsg}`);
41134
+ completed++;
41135
+ updateProgress(completed, urls.length);
41136
+ }
41137
+ })
41138
+ );
41139
+ setState((prev) => ({
41140
+ ...prev,
41141
+ isLoading: false,
41142
+ errors
41143
+ }));
41144
+ },
41145
+ [updateProgress]
41146
+ );
41147
+ const hasModel = useCallback((url) => {
41148
+ return loaderRef.current.hasModel(url);
41149
+ }, []);
41150
+ const hasTexture = useCallback((url) => {
41151
+ return loaderRef.current.hasTexture(url);
41152
+ }, []);
41153
+ const getModel = useCallback((url) => {
41154
+ try {
41155
+ return loaderRef.current.getModel(url);
41156
+ } catch {
41157
+ return void 0;
41158
+ }
41159
+ }, []);
41160
+ const getTexture = useCallback((url) => {
41161
+ try {
41162
+ return loaderRef.current.getTexture(url);
41163
+ } catch {
41164
+ return void 0;
41165
+ }
41166
+ }, []);
41167
+ const clearCache = useCallback(() => {
41168
+ loaderRef.current.clearCache();
41169
+ setState({
41170
+ isLoading: false,
41171
+ progress: 0,
41172
+ loaded: 0,
41173
+ total: 0,
41174
+ errors: []
41175
+ });
41176
+ }, []);
41177
+ return {
41178
+ ...state,
41179
+ loadModel,
41180
+ loadOBJ,
41181
+ loadTexture,
41182
+ preload,
41183
+ hasModel,
41184
+ hasTexture,
41185
+ getModel,
41186
+ getTexture,
41187
+ clearCache
41188
+ };
41189
+ }
41190
+ var init_useAssetLoader = __esm({
41191
+ "components/game/molecules/three/hooks/useAssetLoader.ts"() {
41192
+ "use client";
41193
+ init_AssetLoader();
41194
+ }
41195
+ });
41196
+ function useGameCanvas3DEvents(options) {
41197
+ const {
41198
+ tileClickEvent,
41199
+ unitClickEvent,
41200
+ featureClickEvent,
41201
+ canvasClickEvent,
41202
+ tileHoverEvent,
41203
+ tileLeaveEvent,
41204
+ unitAnimationEvent,
41205
+ cameraChangeEvent,
41206
+ onTileClick,
41207
+ onUnitClick,
41208
+ onFeatureClick,
41209
+ onCanvasClick,
41210
+ onTileHover,
41211
+ onUnitAnimation
41212
+ } = options;
41213
+ const emit = useEmitEvent();
41214
+ const optionsRef = useRef(options);
41215
+ optionsRef.current = options;
41216
+ const handleTileClick = useCallback(
41217
+ (tile, event) => {
41218
+ if (tileClickEvent) {
41219
+ emit(tileClickEvent, {
41220
+ tileId: tile.id,
41221
+ x: tile.x,
41222
+ z: tile.z ?? tile.y ?? 0,
41223
+ type: tile.type,
41224
+ terrain: tile.terrain,
41225
+ elevation: tile.elevation
41226
+ });
41227
+ }
41228
+ optionsRef.current.onTileClick?.(tile, event);
41229
+ },
41230
+ [tileClickEvent, emit]
41231
+ );
41232
+ const handleUnitClick = useCallback(
41233
+ (unit, event) => {
41234
+ if (unitClickEvent) {
41235
+ emit(unitClickEvent, {
41236
+ unitId: unit.id,
41237
+ x: unit.x,
41238
+ z: unit.z ?? unit.y ?? 0,
41239
+ unitType: unit.unitType,
41240
+ name: unit.name,
41241
+ team: unit.team,
41242
+ faction: unit.faction,
41243
+ health: unit.health,
41244
+ maxHealth: unit.maxHealth
41245
+ });
41246
+ }
41247
+ optionsRef.current.onUnitClick?.(unit, event);
41248
+ },
41249
+ [unitClickEvent, emit]
41250
+ );
41251
+ const handleFeatureClick = useCallback(
41252
+ (feature, event) => {
41253
+ if (featureClickEvent) {
41254
+ emit(featureClickEvent, {
41255
+ featureId: feature.id,
41256
+ x: feature.x,
41257
+ z: feature.z ?? feature.y ?? 0,
41258
+ type: feature.type,
41259
+ elevation: feature.elevation
41260
+ });
41261
+ }
41262
+ optionsRef.current.onFeatureClick?.(feature, event);
41263
+ },
41264
+ [featureClickEvent, emit]
41265
+ );
41266
+ const handleCanvasClick = useCallback(
41267
+ (event) => {
41268
+ if (canvasClickEvent) {
41269
+ emit(canvasClickEvent, {
41270
+ clientX: event.clientX,
41271
+ clientY: event.clientY,
41272
+ button: event.button
41273
+ });
41274
+ }
41275
+ optionsRef.current.onCanvasClick?.(event);
41276
+ },
41277
+ [canvasClickEvent, emit]
41278
+ );
41279
+ const handleTileHover = useCallback(
41280
+ (tile, event) => {
41281
+ if (tile) {
41282
+ if (tileHoverEvent) {
41283
+ emit(tileHoverEvent, {
41284
+ tileId: tile.id,
41285
+ x: tile.x,
41286
+ z: tile.z ?? tile.y ?? 0,
41287
+ type: tile.type
41288
+ });
41289
+ }
41290
+ } else {
41291
+ if (tileLeaveEvent) {
41292
+ emit(tileLeaveEvent, {});
41293
+ }
41294
+ }
41295
+ optionsRef.current.onTileHover?.(tile, event);
41296
+ },
41297
+ [tileHoverEvent, tileLeaveEvent, emit]
41298
+ );
41299
+ const handleUnitAnimation = useCallback(
41300
+ (unitId, state) => {
41301
+ if (unitAnimationEvent) {
41302
+ emit(unitAnimationEvent, {
41303
+ unitId,
41304
+ state,
41305
+ timestamp: Date.now()
41306
+ });
41307
+ }
41308
+ optionsRef.current.onUnitAnimation?.(unitId, state);
41309
+ },
41310
+ [unitAnimationEvent, emit]
41311
+ );
41312
+ const handleCameraChange = useCallback(
41313
+ (position) => {
41314
+ if (cameraChangeEvent) {
41315
+ emit(cameraChangeEvent, {
41316
+ position,
41317
+ timestamp: Date.now()
41318
+ });
41319
+ }
41320
+ },
41321
+ [cameraChangeEvent, emit]
41322
+ );
41323
+ return {
41324
+ handleTileClick,
41325
+ handleUnitClick,
41326
+ handleFeatureClick,
41327
+ handleCanvasClick,
41328
+ handleTileHover,
41329
+ handleUnitAnimation,
41330
+ handleCameraChange
41331
+ };
41332
+ }
41333
+ var init_useGameCanvas3DEvents = __esm({
41334
+ "components/game/molecules/three/hooks/useGameCanvas3DEvents.ts"() {
41335
+ "use client";
41336
+ init_useEventBus();
41337
+ }
41338
+ });
41339
+
41340
+ // components/game/molecules/three/components/Canvas3DLoadingState.css
41341
+ var init_Canvas3DLoadingState = __esm({
41342
+ "components/game/molecules/three/components/Canvas3DLoadingState.css"() {
41343
+ }
41344
+ });
41345
+ function Canvas3DLoadingState({
41346
+ progress = 0,
41347
+ loaded = 0,
41348
+ total = 0,
41349
+ message = "Loading 3D Scene...",
41350
+ details,
41351
+ showSpinner = true,
41352
+ className
41353
+ }) {
41354
+ const clampedProgress = Math.max(0, Math.min(100, progress));
41355
+ const hasProgress = total > 0;
41356
+ return /* @__PURE__ */ jsxs("div", { className: `canvas-3d-loading ${className || ""}`, children: [
41357
+ /* @__PURE__ */ jsxs("div", { className: "canvas-3d-loading__content", children: [
41358
+ showSpinner && /* @__PURE__ */ jsxs("div", { className: "canvas-3d-loading__spinner", children: [
41359
+ /* @__PURE__ */ jsx("div", { className: "spinner__ring" }),
41360
+ /* @__PURE__ */ jsx("div", { className: "spinner__ring spinner__ring--secondary" })
41361
+ ] }),
41362
+ /* @__PURE__ */ jsx("div", { className: "canvas-3d-loading__message", children: message }),
41363
+ details && /* @__PURE__ */ jsx("div", { className: "canvas-3d-loading__details", children: details }),
41364
+ hasProgress && /* @__PURE__ */ jsxs("div", { className: "canvas-3d-loading__progress", children: [
41365
+ /* @__PURE__ */ jsx("div", { className: "progress__bar", children: /* @__PURE__ */ jsx(
41366
+ "div",
41367
+ {
41368
+ className: "progress__fill",
41369
+ style: { width: `${clampedProgress}%` }
41370
+ }
41371
+ ) }),
41372
+ /* @__PURE__ */ jsxs("div", { className: "progress__text", children: [
41373
+ /* @__PURE__ */ jsxs("span", { className: "progress__percentage", children: [
41374
+ clampedProgress,
41375
+ "%"
41376
+ ] }),
41377
+ /* @__PURE__ */ jsxs("span", { className: "progress__count", children: [
41378
+ "(",
41379
+ loaded,
41380
+ "/",
41381
+ total,
41382
+ ")"
41383
+ ] })
41384
+ ] })
41385
+ ] })
41386
+ ] }),
41387
+ /* @__PURE__ */ jsx("div", { className: "canvas-3d-loading__background", children: /* @__PURE__ */ jsx("div", { className: "bg__grid" }) })
41388
+ ] });
41389
+ }
41390
+ var init_Canvas3DLoadingState2 = __esm({
41391
+ "components/game/molecules/three/components/Canvas3DLoadingState.tsx"() {
41392
+ init_Canvas3DLoadingState();
41393
+ }
41394
+ });
41395
+
41396
+ // components/game/molecules/three/components/Canvas3DErrorBoundary.css
41397
+ var init_Canvas3DErrorBoundary = __esm({
41398
+ "components/game/molecules/three/components/Canvas3DErrorBoundary.css"() {
41399
+ }
41400
+ });
41401
+ var log10, Canvas3DErrorBoundary;
41402
+ var init_Canvas3DErrorBoundary2 = __esm({
41403
+ "components/game/molecules/three/components/Canvas3DErrorBoundary.tsx"() {
41404
+ init_Canvas3DErrorBoundary();
41405
+ log10 = createLogger("almadar:ui:game:canvas3d:error-boundary");
41406
+ Canvas3DErrorBoundary = class extends Component {
41407
+ constructor(props) {
41408
+ super(props);
41409
+ __publicField(this, "handleReset", () => {
41410
+ this.setState({
41411
+ hasError: false,
41412
+ error: null,
41413
+ errorInfo: null
41414
+ });
41415
+ this.props.onReset?.();
41416
+ });
41417
+ this.state = {
41418
+ hasError: false,
41419
+ error: null,
41420
+ errorInfo: null
41421
+ };
41422
+ }
41423
+ static getDerivedStateFromError(error) {
41424
+ return {
41425
+ hasError: true,
41426
+ error,
41427
+ errorInfo: null
41428
+ };
41429
+ }
41430
+ componentDidCatch(error, errorInfo) {
41431
+ this.setState({ errorInfo });
41432
+ this.props.onError?.(error, errorInfo);
41433
+ log10.error("Error caught", { error });
41434
+ log10.error("Component stack", { componentStack: errorInfo.componentStack ?? "<none>" });
41435
+ }
41436
+ render() {
41437
+ if (this.state.hasError) {
41438
+ if (this.props.fallback) {
41439
+ return this.props.fallback;
41440
+ }
41441
+ return /* @__PURE__ */ jsx("div", { className: "canvas-3d-error", children: /* @__PURE__ */ jsxs("div", { className: "canvas-3d-error__content", children: [
41442
+ /* @__PURE__ */ jsx("div", { className: "canvas-3d-error__icon", children: "\u26A0\uFE0F" }),
41443
+ /* @__PURE__ */ jsx("h2", { className: "canvas-3d-error__title", children: "3D Scene Error" }),
41444
+ /* @__PURE__ */ jsx("p", { className: "canvas-3d-error__message", children: "Something went wrong while rendering the 3D scene." }),
41445
+ this.state.error && /* @__PURE__ */ jsxs("details", { className: "canvas-3d-error__details", children: [
41446
+ /* @__PURE__ */ jsx("summary", { children: "Error Details" }),
41447
+ /* @__PURE__ */ jsxs("pre", { className: "error__stack", children: [
41448
+ this.state.error.message,
41449
+ "\n",
41450
+ this.state.error.stack
41451
+ ] }),
41452
+ this.state.errorInfo && /* @__PURE__ */ jsx("pre", { className: "error__component-stack", children: this.state.errorInfo.componentStack })
41453
+ ] }),
41454
+ /* @__PURE__ */ jsxs("div", { className: "canvas-3d-error__actions", children: [
41455
+ /* @__PURE__ */ jsx(
41456
+ "button",
41457
+ {
41458
+ className: "error__button error__button--primary",
41459
+ onClick: this.handleReset,
41460
+ children: "Try Again"
41461
+ }
41462
+ ),
41463
+ /* @__PURE__ */ jsx(
41464
+ "button",
41465
+ {
41466
+ className: "error__button error__button--secondary",
41467
+ onClick: () => window.location.reload(),
41468
+ children: "Reload Page"
41469
+ }
41470
+ )
41471
+ ] })
41472
+ ] }) });
41473
+ }
41474
+ return this.props.children;
41475
+ }
41476
+ };
41477
+ }
41478
+ });
41479
+ function detectAssetRoot2(modelUrl) {
41480
+ const idx = modelUrl.indexOf("/3d/");
41481
+ if (idx !== -1) {
41482
+ return modelUrl.substring(0, idx + 4);
41483
+ }
41484
+ return modelUrl.substring(0, modelUrl.lastIndexOf("/") + 1);
41485
+ }
41486
+ function useGLTFModel(url, resourceBasePath) {
41487
+ const [state, setState] = useState({
41488
+ model: null,
41489
+ isLoading: false,
41490
+ error: null
41491
+ });
41492
+ useEffect(() => {
41493
+ if (!url) {
41494
+ setState({ model: null, isLoading: false, error: null });
41495
+ return;
41496
+ }
41497
+ log11.debug("Loading", { url });
41498
+ setState((prev) => ({ ...prev, isLoading: true, error: null }));
41499
+ const assetRoot = resourceBasePath || detectAssetRoot2(url);
41500
+ const loader = new GLTFLoader$1();
41501
+ loader.setResourcePath(assetRoot);
41502
+ loader.load(
41503
+ url,
41504
+ (gltf) => {
41505
+ log11.debug("Loaded", { url });
41506
+ setState({
41507
+ model: gltf.scene,
41508
+ isLoading: false,
41509
+ error: null
41510
+ });
41511
+ },
41512
+ void 0,
41513
+ (err) => {
41514
+ log11.warn("Failed", { url, error: err instanceof Error ? err : String(err) });
41515
+ setState({
41516
+ model: null,
41517
+ isLoading: false,
41518
+ error: err instanceof Error ? err : new Error(String(err))
41519
+ });
41520
+ }
41521
+ );
41522
+ }, [url, resourceBasePath]);
41523
+ return state;
41524
+ }
41525
+ function ModelLoader({
41526
+ url,
41527
+ position = [0, 0, 0],
41528
+ scale = 1,
41529
+ rotation = [0, 0, 0],
41530
+ isSelected = false,
41531
+ isHovered = false,
41532
+ onClick,
41533
+ onHover,
41534
+ fallbackGeometry = "box",
41535
+ castShadow = true,
41536
+ receiveShadow = true,
41537
+ resourceBasePath
41538
+ }) {
41539
+ const { model: loadedModel, isLoading, error } = useGLTFModel(url, resourceBasePath);
41540
+ const model = useMemo(() => {
41541
+ if (!loadedModel) return null;
41542
+ const cloned = loadedModel.clone();
41543
+ cloned.traverse((child) => {
41544
+ if (child instanceof THREE.Mesh) {
41545
+ child.castShadow = castShadow;
41546
+ child.receiveShadow = receiveShadow;
41547
+ }
41548
+ });
41549
+ return cloned;
41550
+ }, [loadedModel, castShadow, receiveShadow]);
41551
+ const scaleArray = useMemo(() => {
41552
+ if (typeof scale === "number") {
41553
+ return [scale, scale, scale];
41554
+ }
41555
+ return scale;
41556
+ }, [scale]);
41557
+ const rotationRad = useMemo(() => {
41558
+ return [
41559
+ rotation[0] * Math.PI / 180,
41560
+ rotation[1] * Math.PI / 180,
41561
+ rotation[2] * Math.PI / 180
41562
+ ];
41563
+ }, [rotation]);
41564
+ if (isLoading) {
41565
+ return /* @__PURE__ */ jsx("group", { position, children: /* @__PURE__ */ jsxs("mesh", { rotation: [Math.PI / 2, 0, 0], children: [
41566
+ /* @__PURE__ */ jsx("ringGeometry", { args: [0.3, 0.35, 16] }),
41567
+ /* @__PURE__ */ jsx("meshBasicMaterial", { color: "#4a90d9", transparent: true, opacity: 0.8 })
41568
+ ] }) });
41569
+ }
41570
+ if (error || !model) {
41571
+ if (fallbackGeometry === "none") {
41572
+ return /* @__PURE__ */ jsx("group", { position });
41573
+ }
41574
+ const fallbackProps = {
41575
+ onClick,
41576
+ onPointerOver: () => onHover?.(true),
41577
+ onPointerOut: () => onHover?.(false)
41578
+ };
41579
+ return /* @__PURE__ */ jsxs("group", { position, children: [
41580
+ (isSelected || isHovered) && /* @__PURE__ */ jsxs("mesh", { position: [0, 0.02, 0], rotation: [-Math.PI / 2, 0, 0], children: [
41581
+ /* @__PURE__ */ jsx("ringGeometry", { args: [0.6, 0.7, 32] }),
41582
+ /* @__PURE__ */ jsx(
41583
+ "meshBasicMaterial",
41584
+ {
41585
+ color: isSelected ? 16755200 : 16777215,
41586
+ transparent: true,
41587
+ opacity: 0.5
41588
+ }
41589
+ )
41590
+ ] }),
41591
+ fallbackGeometry === "box" && /* @__PURE__ */ jsxs("mesh", { ...fallbackProps, position: [0, 0.5, 0], children: [
41592
+ /* @__PURE__ */ jsx("boxGeometry", { args: [0.8, 0.8, 0.8] }),
41593
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color: error ? 16729156 : 8947848 })
41594
+ ] }),
41595
+ fallbackGeometry === "sphere" && /* @__PURE__ */ jsxs("mesh", { ...fallbackProps, position: [0, 0.5, 0], children: [
41596
+ /* @__PURE__ */ jsx("sphereGeometry", { args: [0.4, 16, 16] }),
41597
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color: error ? 16729156 : 8947848 })
41598
+ ] }),
41599
+ fallbackGeometry === "cylinder" && /* @__PURE__ */ jsxs("mesh", { ...fallbackProps, position: [0, 0.5, 0], children: [
41600
+ /* @__PURE__ */ jsx("cylinderGeometry", { args: [0.3, 0.3, 0.8, 16] }),
41601
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color: error ? 16729156 : 8947848 })
41602
+ ] })
41603
+ ] });
41604
+ }
41605
+ return /* @__PURE__ */ jsxs(
41606
+ "group",
41607
+ {
41608
+ position,
41609
+ rotation: rotationRad,
41610
+ onClick,
41611
+ onPointerOver: () => onHover?.(true),
41612
+ onPointerOut: () => onHover?.(false),
41613
+ children: [
41614
+ (isSelected || isHovered) && /* @__PURE__ */ jsxs("mesh", { position: [0, 0.02, 0], rotation: [-Math.PI / 2, 0, 0], children: [
41615
+ /* @__PURE__ */ jsx("ringGeometry", { args: [0.6, 0.7, 32] }),
41616
+ /* @__PURE__ */ jsx(
41617
+ "meshBasicMaterial",
41618
+ {
41619
+ color: isSelected ? 16755200 : 16777215,
41620
+ transparent: true,
41621
+ opacity: 0.5
41622
+ }
41623
+ )
41624
+ ] }),
41625
+ /* @__PURE__ */ jsx("primitive", { object: model, scale: scaleArray })
41626
+ ]
41627
+ }
41628
+ );
41629
+ }
41630
+ var log11;
41631
+ var init_ModelLoader = __esm({
41632
+ "components/game/molecules/three/components/ModelLoader.tsx"() {
41633
+ "use client";
41634
+ log11 = createLogger("almadar:ui:game:model-loader");
41635
+ }
41636
+ });
41637
+
41638
+ // components/game/molecules/GameCanvas3D.css
41639
+ var init_GameCanvas3D = __esm({
41640
+ "components/game/molecules/GameCanvas3D.css"() {
41641
+ }
41642
+ });
41643
+ function CameraController({
41644
+ onCameraChange
41645
+ }) {
41646
+ const { camera } = useThree();
41647
+ useEffect(() => {
41648
+ if (onCameraChange) {
41649
+ onCameraChange({
41650
+ x: camera.position.x,
41651
+ y: camera.position.y,
41652
+ z: camera.position.z
41653
+ });
41654
+ }
41655
+ }, [camera.position, onCameraChange]);
41656
+ return null;
41657
+ }
41658
+ var DEFAULT_GRID_CONFIG, GameCanvas3D;
41659
+ var init_GameCanvas3D2 = __esm({
41660
+ "components/game/molecules/GameCanvas3D.tsx"() {
41661
+ "use client";
41662
+ init_AssetLoader();
41663
+ init_useAssetLoader();
41664
+ init_useGameCanvas3DEvents();
41665
+ init_Canvas3DLoadingState2();
41666
+ init_Canvas3DErrorBoundary2();
41667
+ init_ModelLoader();
41668
+ init_cn();
41669
+ init_GameCanvas3D();
41670
+ DEFAULT_GRID_CONFIG = {
41671
+ cellSize: 1,
41672
+ offsetX: 0,
41673
+ offsetZ: 0
41674
+ };
41675
+ GameCanvas3D = forwardRef(
41676
+ ({
41677
+ tiles = [],
41678
+ units = [],
41679
+ features = [],
41680
+ events: events2 = [],
41681
+ orientation = "standard",
41682
+ cameraMode = "isometric",
41683
+ showGrid = true,
41684
+ showCoordinates = false,
41685
+ showTileInfo = false,
41686
+ overlay = "default",
41687
+ shadows = true,
41688
+ backgroundColor = "#1a1a2e",
41689
+ onTileClick,
41690
+ onUnitClick,
41691
+ onFeatureClick,
41692
+ onCanvasClick,
41693
+ onTileHover,
41694
+ onUnitAnimation,
41695
+ assetLoader: customAssetLoader,
41696
+ tileRenderer: CustomTileRenderer,
41697
+ unitRenderer: CustomUnitRenderer,
41698
+ featureRenderer: CustomFeatureRenderer,
41699
+ className,
41700
+ isLoading: externalLoading,
41701
+ error: externalError,
41702
+ entity,
41703
+ preloadAssets = [],
41704
+ tileClickEvent,
41705
+ unitClickEvent,
41706
+ featureClickEvent,
41707
+ canvasClickEvent,
41708
+ tileHoverEvent,
41709
+ tileLeaveEvent,
41710
+ unitAnimationEvent,
41711
+ cameraChangeEvent,
41712
+ loadingMessage = "Loading 3D Scene...",
41713
+ useInstancing = true,
41714
+ validMoves = [],
41715
+ attackTargets = [],
41716
+ selectedTileIds = [],
41717
+ selectedUnitId = null,
41718
+ children
41719
+ }, ref) => {
41720
+ const containerRef = useRef(null);
41721
+ const controlsRef = useRef(null);
41722
+ const [hoveredTile, setHoveredTile] = useState(null);
41723
+ const [internalError, setInternalError] = useState(null);
41724
+ const { isLoading: assetsLoading, progress, loaded, total } = useAssetLoader({
41725
+ preloadUrls: preloadAssets,
41726
+ loader: customAssetLoader
41727
+ });
41728
+ const eventHandlers = useGameCanvas3DEvents({
41729
+ tileClickEvent,
41730
+ unitClickEvent,
41731
+ featureClickEvent,
41732
+ canvasClickEvent,
41733
+ tileHoverEvent,
41734
+ tileLeaveEvent,
41735
+ unitAnimationEvent,
41736
+ cameraChangeEvent,
41737
+ onTileClick,
41738
+ onUnitClick,
41739
+ onFeatureClick,
41740
+ onCanvasClick,
41741
+ onTileHover,
41742
+ onUnitAnimation
41743
+ });
41744
+ const gridBounds = useMemo(() => {
41745
+ if (tiles.length === 0) {
41746
+ return { minX: 0, maxX: 10, minZ: 0, maxZ: 10 };
41747
+ }
41748
+ const xs = tiles.map((t) => t.x);
41749
+ const zs = tiles.map((t) => t.z || t.y || 0);
41750
+ return {
41751
+ minX: Math.min(...xs),
41752
+ maxX: Math.max(...xs),
41753
+ minZ: Math.min(...zs),
41754
+ maxZ: Math.max(...zs)
41755
+ };
41756
+ }, [tiles]);
41757
+ const cameraTarget = useMemo(() => {
41758
+ return [
41759
+ (gridBounds.minX + gridBounds.maxX) / 2,
41760
+ 0,
41761
+ (gridBounds.minZ + gridBounds.maxZ) / 2
41762
+ ];
41763
+ }, [gridBounds]);
41764
+ const gridConfig = useMemo(
41765
+ () => ({
41766
+ ...DEFAULT_GRID_CONFIG,
41767
+ offsetX: -(gridBounds.maxX - gridBounds.minX) / 2,
41768
+ offsetZ: -(gridBounds.maxZ - gridBounds.minZ) / 2
41769
+ }),
41770
+ [gridBounds]
41771
+ );
41772
+ const gridToWorld = useCallback(
41773
+ (x, z, y = 0) => {
41774
+ const worldX = (x - gridBounds.minX) * gridConfig.cellSize;
41775
+ const worldZ = (z - gridBounds.minZ) * gridConfig.cellSize;
41776
+ return [worldX, y * gridConfig.cellSize, worldZ];
41777
+ },
41778
+ [gridBounds, gridConfig]
41779
+ );
41780
+ useImperativeHandle(ref, () => ({
41781
+ getCameraPosition: () => {
41782
+ if (controlsRef.current) {
41783
+ const pos = controlsRef.current.object.position;
41784
+ return new THREE.Vector3(pos.x, pos.y, pos.z);
41785
+ }
41786
+ return null;
41787
+ },
41788
+ setCameraPosition: (x, y, z) => {
41789
+ if (controlsRef.current) {
41790
+ controlsRef.current.object.position.set(x, y, z);
41791
+ controlsRef.current.update();
41792
+ }
41793
+ },
41794
+ lookAt: (x, y, z) => {
41795
+ if (controlsRef.current) {
41796
+ controlsRef.current.target.set(x, y, z);
41797
+ controlsRef.current.update();
41798
+ }
41799
+ },
41800
+ resetCamera: () => {
41801
+ if (controlsRef.current) {
41802
+ controlsRef.current.reset();
41803
+ }
41804
+ },
41805
+ screenshot: () => {
41806
+ const canvas = containerRef.current?.querySelector("canvas");
41807
+ if (canvas) {
41808
+ return canvas.toDataURL("image/png");
41809
+ }
41810
+ return null;
41811
+ },
41812
+ export: () => ({
41813
+ tiles,
41814
+ units,
41815
+ features
41816
+ })
41817
+ }));
41818
+ const handleTileClick = useCallback(
41819
+ (tile, event) => {
41820
+ eventHandlers.handleTileClick(tile, event);
41821
+ },
41822
+ [eventHandlers]
41823
+ );
41824
+ const handleUnitClick = useCallback(
41825
+ (unit, event) => {
41826
+ eventHandlers.handleUnitClick(unit, event);
41827
+ },
41828
+ [eventHandlers]
41829
+ );
41830
+ const handleFeatureClick = useCallback(
41831
+ (feature, event) => {
41832
+ if (event) {
41833
+ eventHandlers.handleFeatureClick(feature, event);
41834
+ }
41835
+ },
41836
+ [eventHandlers]
41837
+ );
41838
+ const handleTileHover = useCallback(
41839
+ (tile, event) => {
41840
+ setHoveredTile(tile);
41841
+ if (event) {
41842
+ eventHandlers.handleTileHover(tile, event);
41843
+ }
41844
+ },
41845
+ [eventHandlers]
41846
+ );
41847
+ const cameraConfig = useMemo(() => {
41848
+ const size = Math.max(
41849
+ gridBounds.maxX - gridBounds.minX,
41850
+ gridBounds.maxZ - gridBounds.minZ
41851
+ );
41852
+ const distance = size * 1.5;
41853
+ switch (cameraMode) {
41854
+ case "isometric":
41855
+ return {
41856
+ position: [distance, distance * 0.8, distance],
41857
+ fov: 45
41858
+ };
41859
+ case "top-down":
41860
+ return {
41861
+ position: [0, distance * 2, 0],
41862
+ fov: 45
41863
+ };
41864
+ case "perspective":
41865
+ default:
41866
+ return {
41867
+ position: [distance, distance, distance],
41868
+ fov: 45
41869
+ };
41870
+ }
41871
+ }, [cameraMode, gridBounds]);
41872
+ const DefaultTileRenderer = useCallback(
41873
+ ({ tile, position }) => {
41874
+ const isSelected = tile.id ? selectedTileIds.includes(tile.id) : false;
41875
+ const isHovered = hoveredTile?.id === tile.id;
41876
+ const isValidMove = validMoves.some(
41877
+ (m) => m.x === tile.x && m.z === (tile.z ?? tile.y ?? 0)
41878
+ );
41879
+ const isAttackTarget = attackTargets.some(
41880
+ (m) => m.x === tile.x && m.z === (tile.z ?? tile.y ?? 0)
41881
+ );
41882
+ let color = 8421504;
41883
+ if (tile.type === "water") color = 4491468;
41884
+ else if (tile.type === "grass") color = 4500036;
41885
+ else if (tile.type === "sand") color = 14535816;
41886
+ else if (tile.type === "rock") color = 8947848;
41887
+ else if (tile.type === "snow") color = 15658734;
41888
+ let emissive = 0;
41889
+ if (isSelected) emissive = 4473924;
41890
+ else if (isAttackTarget) emissive = 4456448;
41891
+ else if (isValidMove) emissive = 17408;
41892
+ else if (isHovered) emissive = 2236962;
41893
+ if (tile.modelUrl) {
41894
+ return /* @__PURE__ */ jsx(
41895
+ "group",
41896
+ {
41897
+ position,
41898
+ onClick: (e) => handleTileClick(tile, e),
41899
+ onPointerEnter: (e) => handleTileHover(tile, e),
41900
+ onPointerLeave: (e) => handleTileHover(null, e),
41901
+ userData: { type: "tile", tileId: tile.id, gridX: tile.x, gridZ: tile.z ?? tile.y },
41902
+ children: /* @__PURE__ */ jsx(
41903
+ ModelLoader,
41904
+ {
41905
+ url: tile.modelUrl,
41906
+ scale: 0.95,
41907
+ fallbackGeometry: "box",
41908
+ castShadow: true,
41909
+ receiveShadow: true
41910
+ }
41911
+ )
41912
+ }
41913
+ );
41914
+ }
41915
+ return /* @__PURE__ */ jsxs(
41916
+ "mesh",
41917
+ {
41918
+ position,
41919
+ onClick: (e) => handleTileClick(tile, e),
41920
+ onPointerEnter: (e) => handleTileHover(tile, e),
41921
+ onPointerLeave: (e) => handleTileHover(null, e),
41922
+ userData: { type: "tile", tileId: tile.id, gridX: tile.x, gridZ: tile.z ?? tile.y },
41923
+ children: [
41924
+ /* @__PURE__ */ jsx("boxGeometry", { args: [0.95, 0.2, 0.95] }),
41925
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color, emissive })
41926
+ ]
41927
+ }
41928
+ );
41929
+ },
41930
+ [selectedTileIds, hoveredTile, validMoves, attackTargets, handleTileClick, handleTileHover]
41931
+ );
41932
+ const DefaultUnitRenderer = useCallback(
41933
+ ({ unit, position }) => {
41934
+ const isSelected = selectedUnitId === unit.id;
41935
+ const color = unit.faction === "player" ? 4491519 : unit.faction === "enemy" ? 16729156 : 16777028;
41936
+ return /* @__PURE__ */ jsxs(
41937
+ "group",
41938
+ {
41939
+ position,
41940
+ onClick: (e) => handleUnitClick(unit, e),
41941
+ userData: { type: "unit", unitId: unit.id },
41942
+ children: [
41943
+ isSelected && /* @__PURE__ */ jsxs("mesh", { position: [0, 0.05, 0], rotation: [-Math.PI / 2, 0, 0], children: [
41944
+ /* @__PURE__ */ jsx("ringGeometry", { args: [0.4, 0.5, 32] }),
41945
+ /* @__PURE__ */ jsx("meshBasicMaterial", { color: "#ffff00", transparent: true, opacity: 0.8 })
41946
+ ] }),
41947
+ unit.modelUrl ? (
41948
+ /* GLB unit model (box fallback while loading / on error) */
41949
+ /* @__PURE__ */ jsx(
41950
+ ModelLoader,
41951
+ {
41952
+ url: unit.modelUrl,
41953
+ scale: 0.5,
41954
+ fallbackGeometry: "box",
41955
+ castShadow: true
41956
+ }
41957
+ )
41958
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
41959
+ /* @__PURE__ */ jsxs("mesh", { position: [0, 0.3, 0], children: [
41960
+ /* @__PURE__ */ jsx("cylinderGeometry", { args: [0.3, 0.3, 0.1, 8] }),
41961
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color })
41962
+ ] }),
41963
+ /* @__PURE__ */ jsxs("mesh", { position: [0, 0.6, 0], children: [
41964
+ /* @__PURE__ */ jsx("capsuleGeometry", { args: [0.2, 0.4, 4, 8] }),
41965
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color })
41966
+ ] }),
41967
+ /* @__PURE__ */ jsxs("mesh", { position: [0, 0.9, 0], children: [
41968
+ /* @__PURE__ */ jsx("sphereGeometry", { args: [0.12, 8, 8] }),
41969
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color })
41970
+ ] })
41971
+ ] }),
41972
+ unit.health !== void 0 && unit.maxHealth !== void 0 && /* @__PURE__ */ jsxs("group", { position: [0, 1.2, 0], children: [
41973
+ /* @__PURE__ */ jsxs("mesh", { position: [-0.25, 0, 0], children: [
41974
+ /* @__PURE__ */ jsx("planeGeometry", { args: [0.5, 0.05] }),
41975
+ /* @__PURE__ */ jsx("meshBasicMaterial", { color: 3355443 })
41976
+ ] }),
41977
+ /* @__PURE__ */ jsxs(
41978
+ "mesh",
41979
+ {
41980
+ position: [
41981
+ -0.25 + 0.5 * (unit.health / unit.maxHealth) / 2,
41982
+ 0,
41983
+ 0.01
41984
+ ],
41985
+ children: [
41986
+ /* @__PURE__ */ jsx("planeGeometry", { args: [0.5 * (unit.health / unit.maxHealth), 0.05] }),
41987
+ /* @__PURE__ */ jsx(
41988
+ "meshBasicMaterial",
41989
+ {
41990
+ color: unit.health / unit.maxHealth > 0.5 ? 4500036 : unit.health / unit.maxHealth > 0.25 ? 11184708 : 16729156
41991
+ }
41992
+ )
41993
+ ]
41994
+ }
41995
+ )
41996
+ ] })
41997
+ ]
41998
+ }
41999
+ );
42000
+ },
42001
+ [selectedUnitId, handleUnitClick]
42002
+ );
42003
+ const DefaultFeatureRenderer = useCallback(
42004
+ ({
42005
+ feature,
42006
+ position
42007
+ }) => {
42008
+ if (feature.assetUrl) {
42009
+ return /* @__PURE__ */ jsx(
42010
+ ModelLoader,
42011
+ {
42012
+ url: feature.assetUrl,
42013
+ position,
42014
+ scale: 0.5,
42015
+ rotation: [0, feature.rotation ?? 0, 0],
42016
+ onClick: () => handleFeatureClick(feature, null),
42017
+ fallbackGeometry: "box"
42018
+ },
42019
+ feature.id
42020
+ );
42021
+ }
42022
+ if (feature.type === "tree") {
42023
+ return /* @__PURE__ */ jsxs(
42024
+ "group",
42025
+ {
42026
+ position,
42027
+ onClick: (e) => handleFeatureClick(feature, e),
42028
+ userData: { type: "feature", featureId: feature.id },
42029
+ children: [
42030
+ /* @__PURE__ */ jsxs("mesh", { position: [0, 0.4, 0], children: [
42031
+ /* @__PURE__ */ jsx("cylinderGeometry", { args: [0.1, 0.15, 0.8, 6] }),
42032
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color: 9127187 })
42033
+ ] }),
42034
+ /* @__PURE__ */ jsxs("mesh", { position: [0, 0.9, 0], children: [
42035
+ /* @__PURE__ */ jsx("coneGeometry", { args: [0.5, 0.8, 8] }),
42036
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color: 2263842 })
42037
+ ] })
42038
+ ]
42039
+ }
42040
+ );
42041
+ }
42042
+ if (feature.type === "rock") {
42043
+ return /* @__PURE__ */ jsxs(
42044
+ "mesh",
42045
+ {
42046
+ position: [position[0], position[1] + 0.3, position[2]],
42047
+ onClick: (e) => handleFeatureClick(feature, e),
42048
+ userData: { type: "feature", featureId: feature.id },
42049
+ children: [
42050
+ /* @__PURE__ */ jsx("dodecahedronGeometry", { args: [0.3, 0] }),
42051
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color: 8421504 })
42052
+ ]
42053
+ }
42054
+ );
42055
+ }
42056
+ return null;
42057
+ },
42058
+ [handleFeatureClick]
42059
+ );
42060
+ if (externalLoading || assetsLoading && preloadAssets.length > 0) {
42061
+ return /* @__PURE__ */ jsx(
42062
+ Canvas3DLoadingState,
42063
+ {
42064
+ progress,
42065
+ loaded,
42066
+ total,
42067
+ message: loadingMessage,
42068
+ className
42069
+ }
42070
+ );
42071
+ }
42072
+ const displayError = externalError || internalError;
42073
+ if (displayError) {
42074
+ 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: [
42075
+ "Error: ",
42076
+ displayError
42077
+ ] }) }) });
42078
+ }
42079
+ return /* @__PURE__ */ jsx(
42080
+ Canvas3DErrorBoundary,
42081
+ {
42082
+ onError: (err) => setInternalError(err.message),
42083
+ onReset: () => setInternalError(null),
42084
+ children: /* @__PURE__ */ jsxs(
42085
+ "div",
42086
+ {
42087
+ ref: containerRef,
42088
+ className: cn("game-canvas-3d relative w-full h-full min-h-[85vh] overflow-hidden", className),
42089
+ "data-orientation": orientation,
42090
+ "data-camera-mode": cameraMode,
42091
+ "data-overlay": overlay,
42092
+ children: [
42093
+ /* @__PURE__ */ jsxs(
42094
+ Canvas,
42095
+ {
42096
+ shadows,
42097
+ camera: {
42098
+ position: cameraConfig.position,
42099
+ fov: cameraConfig.fov,
42100
+ near: 0.1,
42101
+ far: 1e3
42102
+ },
42103
+ style: { background: backgroundColor },
42104
+ onClick: (e) => {
42105
+ if (e.target === e.currentTarget) {
42106
+ eventHandlers.handleCanvasClick(e);
42107
+ }
42108
+ },
42109
+ children: [
42110
+ /* @__PURE__ */ jsx(CameraController, { onCameraChange: eventHandlers.handleCameraChange }),
42111
+ /* @__PURE__ */ jsx("ambientLight", { intensity: 0.6 }),
42112
+ /* @__PURE__ */ jsx(
42113
+ "directionalLight",
42114
+ {
42115
+ position: [10, 20, 10],
42116
+ intensity: 0.8,
42117
+ castShadow: shadows,
42118
+ "shadow-mapSize": [2048, 2048]
42119
+ }
42120
+ ),
42121
+ /* @__PURE__ */ jsx("hemisphereLight", { intensity: 0.3, color: "#87ceeb", groundColor: "#362d1d" }),
42122
+ showGrid && /* @__PURE__ */ jsx(
42123
+ Grid$1,
42124
+ {
42125
+ args: [
42126
+ Math.max(gridBounds.maxX - gridBounds.minX + 2, 10),
42127
+ Math.max(gridBounds.maxZ - gridBounds.minZ + 2, 10)
42128
+ ],
42129
+ position: [
42130
+ (gridBounds.maxX - gridBounds.minX) / 2 - 0.5,
42131
+ 0,
42132
+ (gridBounds.maxZ - gridBounds.minZ) / 2 - 0.5
42133
+ ],
42134
+ cellSize: 1,
42135
+ cellThickness: 1,
42136
+ cellColor: "#444444",
42137
+ sectionSize: 5,
42138
+ sectionThickness: 1.5,
42139
+ sectionColor: "#666666",
42140
+ fadeDistance: 50,
42141
+ fadeStrength: 1
42142
+ }
42143
+ ),
42144
+ tiles.map((tile, index) => {
42145
+ const position = gridToWorld(
42146
+ tile.x,
42147
+ tile.z ?? tile.y ?? 0,
42148
+ tile.elevation ?? 0
42149
+ );
42150
+ const Renderer = CustomTileRenderer || DefaultTileRenderer;
42151
+ return /* @__PURE__ */ jsx(Renderer, { tile, position }, tile.id ?? `tile-${index}`);
42152
+ }),
42153
+ features.map((feature, index) => {
42154
+ const position = gridToWorld(
42155
+ feature.x,
42156
+ feature.z ?? feature.y ?? 0,
42157
+ (feature.elevation ?? 0) + 0.5
42158
+ );
42159
+ const Renderer = CustomFeatureRenderer || DefaultFeatureRenderer;
42160
+ return /* @__PURE__ */ jsx(Renderer, { feature, position }, feature.id ?? `feature-${index}`);
42161
+ }),
42162
+ units.map((unit) => {
42163
+ const position = gridToWorld(
42164
+ unit.x ?? 0,
42165
+ unit.z ?? unit.y ?? 0,
42166
+ (unit.elevation ?? 0) + 0.5
42167
+ );
42168
+ const Renderer = CustomUnitRenderer || DefaultUnitRenderer;
42169
+ return /* @__PURE__ */ jsx(Renderer, { unit, position }, unit.id);
42170
+ }),
42171
+ children,
42172
+ /* @__PURE__ */ jsx(
42173
+ OrbitControls,
42174
+ {
42175
+ ref: controlsRef,
42176
+ target: cameraTarget,
42177
+ enableDamping: true,
42178
+ dampingFactor: 0.05,
42179
+ minDistance: 2,
42180
+ maxDistance: 100,
42181
+ maxPolarAngle: Math.PI / 2 - 0.1
42182
+ }
42183
+ )
42184
+ ]
42185
+ }
42186
+ ),
42187
+ showCoordinates && hoveredTile && /* @__PURE__ */ jsxs("div", { className: "game-canvas-3d__coordinates", children: [
42188
+ "X: ",
42189
+ hoveredTile.x,
42190
+ ", Z: ",
42191
+ hoveredTile.z ?? hoveredTile.y ?? 0
42192
+ ] }),
42193
+ showTileInfo && hoveredTile && /* @__PURE__ */ jsxs("div", { className: "game-canvas-3d__tile-info", children: [
42194
+ /* @__PURE__ */ jsx("div", { className: "tile-info__type", children: hoveredTile.type }),
42195
+ hoveredTile.terrain && /* @__PURE__ */ jsx("div", { className: "tile-info__terrain", children: hoveredTile.terrain })
42196
+ ] })
42197
+ ]
42198
+ }
42199
+ )
42200
+ }
42201
+ );
42202
+ }
42203
+ );
42204
+ GameCanvas3D.displayName = "GameCanvas3D";
42205
+ }
42206
+ });
42207
+ function GameBoard3D({
42208
+ entity,
42209
+ tiles = [],
42210
+ features = [],
42211
+ cameraMode = "perspective",
42212
+ backgroundColor = "#2a1a1a",
42213
+ tileClickEvent,
42214
+ unitClickEvent,
42215
+ attackEvent,
42216
+ endTurnEvent,
42217
+ cancelEvent,
42218
+ playAgainEvent,
42219
+ gameEndEvent,
42220
+ className
42221
+ }) {
42222
+ const row = boardEntity(entity);
42223
+ const eventBus = useEventBus();
42224
+ const entityUnits = row ? rows(row.units) : [];
42225
+ const units = entityUnits;
42226
+ const selectedUnitId = row ? str(row.selectedUnitId) || null : null;
42227
+ const phase = row ? str(row.phase) : "observation";
42228
+ const result = row ? str(row.result) : "none";
42229
+ const validMoves = row && Array.isArray(row.validMoves) ? row.validMoves : [];
42230
+ const attackTargets = row && Array.isArray(row.attackTargets) ? row.attackTargets : [];
42231
+ const turn = row ? num(row.turn) : 0;
42232
+ const currentTeam = row ? str(row.currentTeam) : "player";
42233
+ const isGameOver = result !== "none";
42234
+ const gameEndEmittedRef = useRef(false);
42235
+ useEffect(() => {
42236
+ if ((result === "victory" || result === "defeat") && gameEndEvent) {
42237
+ if (!gameEndEmittedRef.current) {
42238
+ gameEndEmittedRef.current = true;
42239
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
42240
+ }
42241
+ } else {
42242
+ gameEndEmittedRef.current = false;
42243
+ }
42244
+ }, [result, gameEndEvent, eventBus]);
42245
+ const checkGameEnd = useCallback(() => {
42246
+ const alivePlayer = entityUnits.filter((u) => unitTeam(u) === "player" && unitHealth(u) > 0);
42247
+ const aliveEnemy = entityUnits.filter((u) => unitTeam(u) === "enemy" && unitHealth(u) > 0);
42248
+ if (alivePlayer.length === 0 && gameEndEvent) {
42249
+ eventBus.emit(`UI:${gameEndEvent}`, { result: "defeat" });
42250
+ } else if (aliveEnemy.length === 0 && gameEndEvent) {
42251
+ eventBus.emit(`UI:${gameEndEvent}`, { result: "victory" });
42252
+ }
42253
+ }, [entityUnits, gameEndEvent, eventBus]);
42254
+ const handleUnitClickCallback = useCallback((isoUnit) => {
42255
+ if (phase !== "action" || !selectedUnitId || !attackEvent) return;
42256
+ const attackerRow = entityUnits.find((u) => str(u.id) === selectedUnitId);
42257
+ const targetRow = entityUnits.find((u) => str(u.id) === isoUnit.id);
42258
+ if (!attackerRow || !targetRow) return;
42259
+ if (unitTeam(targetRow) !== "enemy" || unitHealth(targetRow) <= 0) return;
42260
+ const ap = unitPosition(attackerRow);
42261
+ const tp = unitPosition(targetRow);
42262
+ const dx = Math.abs(ap.x - tp.x);
42263
+ const dy = Math.abs(ap.y - tp.y);
42264
+ if (dx <= 1 && dy <= 1 && dx + dy > 0) {
42265
+ const damage = Math.max(1, num(attackerRow.attack) - num(targetRow.defense));
42266
+ eventBus.emit(`UI:${attackEvent}`, {
42267
+ attackerId: str(attackerRow.id),
42268
+ targetId: str(targetRow.id),
42269
+ damage
42270
+ });
42271
+ setTimeout(checkGameEnd, 100);
42272
+ }
42273
+ }, [phase, selectedUnitId, entityUnits, attackEvent, eventBus, checkGameEnd]);
42274
+ const handleEndTurn = useCallback(() => {
42275
+ if (endTurnEvent) eventBus.emit(`UI:${endTurnEvent}`, {});
42276
+ }, [endTurnEvent, eventBus]);
42277
+ const handleCancel = useCallback(() => {
42278
+ if (cancelEvent) eventBus.emit(`UI:${cancelEvent}`, {});
42279
+ }, [cancelEvent, eventBus]);
42280
+ const handlePlayAgain = useCallback(() => {
42281
+ if (playAgainEvent) eventBus.emit(`UI:${playAgainEvent}`, {});
42282
+ }, [playAgainEvent, eventBus]);
42283
+ return /* @__PURE__ */ jsxs(VStack, { className: cn("game-board-3d block w-full min-h-[85vh] relative", className), children: [
42284
+ /* @__PURE__ */ jsxs("div", { className: "game-board-3d__status absolute top-3 left-3 z-10 flex gap-2 items-center", children: [
42285
+ /* @__PURE__ */ jsx(Typography, { variant: "small", className: "status__phase capitalize", children: phase.replace("_", " ") }),
42286
+ currentTeam && !isGameOver && /* @__PURE__ */ jsxs(Typography, { variant: "small", color: "muted", className: "status__team", children: [
42287
+ "\u2014 ",
42288
+ currentTeam === "player" ? "Your Turn" : "Enemy's Turn"
42289
+ ] }),
42290
+ /* @__PURE__ */ jsxs(Typography, { variant: "small", color: "muted", className: "status__turn", children: [
42291
+ "Turn ",
42292
+ turn
42293
+ ] })
42294
+ ] }),
42295
+ /* @__PURE__ */ jsx(
42296
+ GameCanvas3D,
42297
+ {
42298
+ tiles,
42299
+ units,
42300
+ features,
42301
+ cameraMode,
42302
+ showGrid: true,
42303
+ showCoordinates: false,
42304
+ showTileInfo: false,
42305
+ shadows: true,
42306
+ backgroundColor,
42307
+ tileClickEvent,
42308
+ unitClickEvent,
42309
+ onUnitClick: handleUnitClickCallback,
42310
+ selectedUnitId,
42311
+ validMoves,
42312
+ attackTargets,
42313
+ className: "game-board-3d__canvas w-full min-h-[85vh]"
42314
+ }
42315
+ ),
42316
+ !isGameOver && /* @__PURE__ */ jsxs(HStack, { className: "fixed bottom-6 right-6 z-50", gap: "sm", children: [
42317
+ (phase === "selection" || phase === "movement" || phase === "action") && /* @__PURE__ */ jsx(
42318
+ Button,
42319
+ {
42320
+ variant: "secondary",
42321
+ className: "shadow-xl",
42322
+ onClick: handleCancel,
42323
+ children: "Cancel"
42324
+ }
42325
+ ),
42326
+ phase !== "enemy_turn" && /* @__PURE__ */ jsx(
42327
+ Button,
42328
+ {
42329
+ variant: "primary",
42330
+ className: "shadow-xl",
42331
+ onClick: handleEndTurn,
42332
+ children: "End Turn"
42333
+ }
42334
+ )
42335
+ ] }),
42336
+ 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: [
42337
+ /* @__PURE__ */ jsx(
42338
+ Typography,
42339
+ {
42340
+ variant: "h2",
42341
+ className: cn(
42342
+ "overlay__result",
42343
+ result === "victory" ? "text-yellow-400" : "text-red-500"
42344
+ ),
42345
+ children: result === "victory" ? "Victory!" : "Defeat"
42346
+ }
42347
+ ),
42348
+ /* @__PURE__ */ jsxs(Typography, { variant: "body", color: "muted", className: "overlay__turn-count", children: [
42349
+ "Completed in ",
42350
+ turn,
42351
+ " turn",
42352
+ turn !== 1 ? "s" : ""
42353
+ ] }),
42354
+ /* @__PURE__ */ jsx(
42355
+ Button,
42356
+ {
42357
+ variant: "primary",
42358
+ className: "px-8 py-3 font-semibold",
42359
+ onClick: handlePlayAgain,
42360
+ children: "Play Again"
42361
+ }
42362
+ )
42363
+ ] }) })
42364
+ ] });
42365
+ }
42366
+ var init_GameBoard3D = __esm({
42367
+ "components/game/organisms/GameBoard3D.tsx"() {
42368
+ "use client";
42369
+ init_cn();
42370
+ init_Button();
42371
+ init_Typography();
42372
+ init_Stack();
42373
+ init_useEventBus();
42374
+ init_GameCanvas3D2();
42375
+ init_boardEntity();
42376
+ GameBoard3D.displayName = "GameBoard3D";
42377
+ }
42378
+ });
40676
42379
  var GameShell;
40677
42380
  var init_GameShell = __esm({
40678
42381
  "components/game/templates/GameShell.tsx"() {
@@ -42041,21 +43744,6 @@ var init_ModalSlot = __esm({
42041
43744
  ModalSlot.displayName = "ModalSlot";
42042
43745
  }
42043
43746
  });
42044
- function getOpponentAction(strategy, actions, history) {
42045
- const actionIds = actions.map((a) => a.id);
42046
- switch (strategy) {
42047
- case "always-cooperate":
42048
- return actionIds[0];
42049
- case "always-defect":
42050
- return actionIds[actionIds.length - 1];
42051
- case "tit-for-tat":
42052
- if (history.length === 0) return actionIds[0];
42053
- return history[history.length - 1].playerAction;
42054
- case "random":
42055
- default:
42056
- return actionIds[Math.floor(Math.random() * actionIds.length)];
42057
- }
42058
- }
42059
43747
  function NegotiatorBoard({
42060
43748
  entity,
42061
43749
  completeEvent = "PUZZLE_COMPLETE",
@@ -42077,28 +43765,35 @@ function NegotiatorBoard({
42077
43765
  const playerTotal = num(resolved?.score);
42078
43766
  const isComplete = result !== "none" || totalRounds > 0 && currentRound >= totalRounds;
42079
43767
  const won = result === "win";
42080
- const opponentTotal = history.reduce((s, r) => s + r.opponentPayoff, 0);
43768
+ const lastPlayerAction = str(resolved?.lastPlayerAction);
43769
+ const lastOpponentAction = str(resolved?.lastOpponentAction);
43770
+ const lastPayoff = num(resolved?.lastPayoff);
42081
43771
  const actions = Array.isArray(resolved?.actions) ? resolved.actions : [];
42082
43772
  const payoffMatrix = Array.isArray(resolved?.payoffMatrix) ? resolved.payoffMatrix : [];
43773
+ const prevRoundRef = useRef(currentRound);
43774
+ useEffect(() => {
43775
+ if (currentRound > prevRoundRef.current && lastPlayerAction) {
43776
+ const opponentPayoffEntry = payoffMatrix.find(
43777
+ (p2) => p2.playerAction === lastPlayerAction && p2.opponentAction === lastOpponentAction
43778
+ );
43779
+ setHistory((prev) => [
43780
+ ...prev,
43781
+ {
43782
+ round: currentRound,
43783
+ playerAction: lastPlayerAction,
43784
+ opponentAction: lastOpponentAction,
43785
+ playerPayoff: lastPayoff,
43786
+ opponentPayoff: opponentPayoffEntry?.opponentPayoff ?? 0
43787
+ }
43788
+ ]);
43789
+ }
43790
+ prevRoundRef.current = currentRound;
43791
+ }, [currentRound, lastPlayerAction, lastOpponentAction, lastPayoff, payoffMatrix]);
43792
+ const opponentTotal = useMemo(() => history.reduce((s, r) => s + r.opponentPayoff, 0), [history]);
42083
43793
  const handleAction = useCallback((actionId) => {
42084
43794
  if (isComplete) return;
42085
- const opponentAction = getOpponentAction(str(resolved?.opponentStrategy) || "random", actions, history);
42086
- const payoff = payoffMatrix.find(
42087
- (p2) => p2.playerAction === actionId && p2.opponentAction === opponentAction
42088
- );
42089
- const playerPayoff = payoff?.playerPayoff ?? 0;
42090
- setHistory((prev) => [
42091
- ...prev,
42092
- {
42093
- round: prev.length + 1,
42094
- playerAction: actionId,
42095
- opponentAction,
42096
- playerPayoff,
42097
- opponentPayoff: payoff?.opponentPayoff ?? 0
42098
- }
42099
- ]);
42100
43795
  if (playRoundEvent) {
42101
- emit(`UI:${playRoundEvent}`, { playerAction: actionId, payoff: playerPayoff });
43796
+ emit(`UI:${playRoundEvent}`, { playerAction: actionId, payoff: 0 });
42102
43797
  }
42103
43798
  if (totalRounds > 0 && currentRound + 1 >= totalRounds) {
42104
43799
  if (finishEvent) {
@@ -42108,10 +43803,11 @@ function NegotiatorBoard({
42108
43803
  setShowHint(true);
42109
43804
  }
42110
43805
  }
42111
- }, [isComplete, resolved, totalRounds, currentRound, actions, payoffMatrix, history, playRoundEvent, finishEvent, emit]);
43806
+ }, [isComplete, resolved, totalRounds, currentRound, playRoundEvent, finishEvent, emit]);
42112
43807
  const handleReset = useCallback(() => {
42113
43808
  setHistory([]);
42114
43809
  setShowHint(false);
43810
+ prevRoundRef.current = 0;
42115
43811
  if (playAgainEvent) {
42116
43812
  emit(`UI:${playAgainEvent}`, {});
42117
43813
  }
@@ -42222,6 +43918,236 @@ var init_NegotiatorBoard = __esm({
42222
43918
  NegotiatorBoard.displayName = "NegotiatorBoard";
42223
43919
  }
42224
43920
  });
43921
+ function resolveEntity(entity) {
43922
+ if (!entity) return {};
43923
+ if (Array.isArray(entity)) return entity[0] ?? {};
43924
+ return entity;
43925
+ }
43926
+ function numField(row, key, fallback) {
43927
+ const v = row[key];
43928
+ return typeof v === "number" ? v : fallback;
43929
+ }
43930
+ function strField(row, key, fallback) {
43931
+ const v = row[key];
43932
+ return typeof v === "string" ? v : fallback;
43933
+ }
43934
+ function PlatformerBoard({
43935
+ entity,
43936
+ player: propPlayer,
43937
+ platforms: propPlatforms,
43938
+ result: propResult,
43939
+ lives: propLives,
43940
+ score: propScore,
43941
+ level: propLevel,
43942
+ worldWidth = 800,
43943
+ worldHeight = 400,
43944
+ canvasWidth = 800,
43945
+ canvasHeight = 400,
43946
+ playerSprite,
43947
+ tileSprites,
43948
+ bgColor,
43949
+ leftEvent = "LEFT",
43950
+ rightEvent = "RIGHT",
43951
+ jumpEvent = "JUMP",
43952
+ stopEvent = "STOP",
43953
+ playAgainEvent = "PLAY_AGAIN",
43954
+ gameEndEvent = "GAME_END",
43955
+ className
43956
+ }) {
43957
+ const row = resolveEntity(entity);
43958
+ const eventBus = useEventBus();
43959
+ const { t } = useTranslate();
43960
+ const result = propResult ?? strField(row, "result", "none");
43961
+ propLives ?? numField(row, "lives", 3);
43962
+ const score = propScore ?? numField(row, "score", 0);
43963
+ const level = propLevel ?? numField(row, "level", 1);
43964
+ const entityPlayer = row["player"];
43965
+ const player = propPlayer ?? entityPlayer ?? {
43966
+ x: 80,
43967
+ y: 320,
43968
+ width: 32,
43969
+ height: 48,
43970
+ vx: 0,
43971
+ vy: 0,
43972
+ grounded: false,
43973
+ facingRight: true
43974
+ };
43975
+ const entityPlatforms = Array.isArray(row["platforms"]) ? row["platforms"] : void 0;
43976
+ const platforms = propPlatforms ?? entityPlatforms ?? [
43977
+ { x: 0, y: 368, width: 800, height: 32, type: "ground" },
43978
+ { x: 150, y: 280, width: 160, height: 16, type: "platform" },
43979
+ { x: 420, y: 220, width: 160, height: 16, type: "platform" },
43980
+ { x: 580, y: 300, width: 80, height: 16, type: "hazard" },
43981
+ { x: 700, y: 340, width: 64, height: 28, type: "goal" }
43982
+ ];
43983
+ const handleRestart = useCallback(() => {
43984
+ if (playAgainEvent) {
43985
+ eventBus.emit(`UI:${playAgainEvent}`, {});
43986
+ }
43987
+ }, [playAgainEvent, eventBus]);
43988
+ const isGameOver = result === "won" || result === "lost";
43989
+ const gameEndEmittedRef = useRef(false);
43990
+ useEffect(() => {
43991
+ if ((result === "won" || result === "lost") && gameEndEvent) {
43992
+ if (!gameEndEmittedRef.current) {
43993
+ gameEndEmittedRef.current = true;
43994
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
43995
+ }
43996
+ } else {
43997
+ gameEndEmittedRef.current = false;
43998
+ }
43999
+ }, [result, gameEndEvent, eventBus]);
44000
+ return /* @__PURE__ */ jsx(
44001
+ VStack,
44002
+ {
44003
+ className: cn("platformer-board relative bg-background", className),
44004
+ gap: "none",
44005
+ children: /* @__PURE__ */ jsxs(Box, { className: "relative", children: [
44006
+ /* @__PURE__ */ jsx(
44007
+ PlatformerCanvas,
44008
+ {
44009
+ player,
44010
+ platforms,
44011
+ worldWidth,
44012
+ worldHeight,
44013
+ canvasWidth,
44014
+ canvasHeight,
44015
+ followCamera: true,
44016
+ bgColor,
44017
+ playerSprite,
44018
+ tileSprites,
44019
+ leftEvent,
44020
+ rightEvent,
44021
+ jumpEvent,
44022
+ stopEvent
44023
+ }
44024
+ ),
44025
+ 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: [
44026
+ /* @__PURE__ */ jsx(
44027
+ Typography,
44028
+ {
44029
+ variant: "h2",
44030
+ className: cn(
44031
+ "text-4xl font-black tracking-widest uppercase",
44032
+ result === "won" ? "text-warning" : "text-error"
44033
+ ),
44034
+ children: result === "won" ? t("platformer.won") : t("platformer.lost")
44035
+ }
44036
+ ),
44037
+ result === "won" && /* @__PURE__ */ jsxs(Typography, { variant: "body1", className: "text-muted-foreground", children: [
44038
+ t("platformer.level"),
44039
+ " ",
44040
+ level,
44041
+ " \u2014 ",
44042
+ t("platformer.score"),
44043
+ ": ",
44044
+ score
44045
+ ] }),
44046
+ result === "lost" && /* @__PURE__ */ jsx(Typography, { variant: "body1", className: "text-muted-foreground", children: t("platformer.noLives") }),
44047
+ /* @__PURE__ */ jsx(
44048
+ Button,
44049
+ {
44050
+ variant: "primary",
44051
+ className: "px-8 py-3 font-semibold",
44052
+ onClick: handleRestart,
44053
+ children: result === "won" ? t("platformer.nextLevel") : t("platformer.playAgain")
44054
+ }
44055
+ )
44056
+ ] }) })
44057
+ ] })
44058
+ }
44059
+ );
44060
+ }
44061
+ var init_PlatformerBoard = __esm({
44062
+ "components/game/organisms/PlatformerBoard.tsx"() {
44063
+ "use client";
44064
+ init_cn();
44065
+ init_useEventBus();
44066
+ init_Box();
44067
+ init_Button();
44068
+ init_Typography();
44069
+ init_Stack();
44070
+ init_PlatformerCanvas();
44071
+ PlatformerBoard.displayName = "PlatformerBoard";
44072
+ }
44073
+ });
44074
+ function PlatformerTemplate({
44075
+ entity,
44076
+ platforms = DEFAULT_PLATFORMS,
44077
+ worldWidth = 800,
44078
+ worldHeight = 400,
44079
+ canvasWidth = 800,
44080
+ canvasHeight = 400,
44081
+ playerSprite = `${CDN3}/characters/platformChar_idle.png`,
44082
+ tileSprites = DEFAULT_TILE_SPRITES,
44083
+ bgColor = "#5c94fc",
44084
+ gameEndEvent = "GAME_END",
44085
+ className
44086
+ }) {
44087
+ const row = entity && !Array.isArray(entity) ? entity : void 0;
44088
+ const lives = typeof row?.["lives"] === "number" ? row["lives"] : 3;
44089
+ const score = typeof row?.["score"] === "number" ? row["score"] : 0;
44090
+ const level = typeof row?.["level"] === "number" ? row["level"] : 1;
44091
+ return /* @__PURE__ */ jsxs(VStack, { className: cn("platformer-template w-full", className), gap: "none", children: [
44092
+ /* @__PURE__ */ jsx(Box, { className: "relative", children: /* @__PURE__ */ jsx(
44093
+ GameHud,
44094
+ {
44095
+ position: "top",
44096
+ stats: [
44097
+ { label: "Lives", value: lives, format: "number", variant: "danger" },
44098
+ { label: "Level", value: level, format: "number", variant: "default" }
44099
+ ]
44100
+ }
44101
+ ) }),
44102
+ /* @__PURE__ */ jsx(
44103
+ PlatformerBoard,
44104
+ {
44105
+ entity,
44106
+ platforms,
44107
+ worldWidth,
44108
+ worldHeight,
44109
+ canvasWidth,
44110
+ canvasHeight,
44111
+ playerSprite,
44112
+ tileSprites,
44113
+ bgColor,
44114
+ leftEvent: "LEFT",
44115
+ rightEvent: "RIGHT",
44116
+ jumpEvent: "JUMP",
44117
+ stopEvent: "STOP",
44118
+ playAgainEvent: "PLAY_AGAIN",
44119
+ gameEndEvent
44120
+ }
44121
+ ),
44122
+ /* @__PURE__ */ jsx(Box, { className: "px-4 py-2", children: /* @__PURE__ */ jsx(ScoreBoard, { score, level }) })
44123
+ ] });
44124
+ }
44125
+ var CDN3, DEFAULT_TILE_SPRITES, DEFAULT_PLATFORMS;
44126
+ var init_PlatformerTemplate = __esm({
44127
+ "components/game/templates/PlatformerTemplate.tsx"() {
44128
+ init_cn();
44129
+ init_Box();
44130
+ init_Stack();
44131
+ init_PlatformerBoard();
44132
+ init_GameHud();
44133
+ init_ScoreBoard();
44134
+ CDN3 = "https://almadar-kflow-assets.web.app/shared/platformer";
44135
+ DEFAULT_TILE_SPRITES = {
44136
+ ground: `${CDN3}/tiles/platformPack_tile001.png`,
44137
+ platform: `${CDN3}/tiles/platformPack_tile004.png`,
44138
+ hazard: `${CDN3}/tiles/platformPack_tile017.png`,
44139
+ goal: `${CDN3}/tiles/platformPack_tile007.png`
44140
+ };
44141
+ DEFAULT_PLATFORMS = [
44142
+ { x: 0, y: 368, width: 800, height: 32, type: "ground" },
44143
+ { x: 150, y: 280, width: 160, height: 16, type: "platform" },
44144
+ { x: 420, y: 220, width: 160, height: 16, type: "platform" },
44145
+ { x: 580, y: 300, width: 80, height: 16, type: "hazard" },
44146
+ { x: 700, y: 340, width: 64, height: 28, type: "goal" }
44147
+ ];
44148
+ PlatformerTemplate.displayName = "PlatformerTemplate";
44149
+ }
44150
+ });
42225
44151
  var PricingOrganism;
42226
44152
  var init_PricingOrganism = __esm({
42227
44153
  "components/marketing/organisms/PricingOrganism.tsx"() {
@@ -42344,6 +44270,356 @@ var init_PricingPageTemplate = __esm({
42344
44270
  PricingPageTemplate.displayName = "PricingPageTemplate";
42345
44271
  }
42346
44272
  });
44273
+ function RoguelikeBoard({
44274
+ entity,
44275
+ tiles: propTiles,
44276
+ enemies: propEnemies,
44277
+ items: propItems,
44278
+ player: propPlayer,
44279
+ playerHp: propPlayerHp,
44280
+ playerMaxHp: propPlayerMaxHp,
44281
+ playerAttack: propPlayerAttack,
44282
+ depth: propDepth,
44283
+ result: propResult,
44284
+ phase: propPhase,
44285
+ stairsX: propStairsX,
44286
+ stairsY: propStairsY,
44287
+ assetManifest: propAssetManifest,
44288
+ scale = 0.45,
44289
+ unitScale = 1,
44290
+ moveEvent,
44291
+ playAgainEvent,
44292
+ gameEndEvent,
44293
+ className
44294
+ }) {
44295
+ const board = boardEntity(entity) ?? {};
44296
+ const tiles = propTiles ?? (Array.isArray(board.tiles) ? board.tiles : DEFAULT_TILES2);
44297
+ const enemies = propEnemies ?? (Array.isArray(board.enemies) ? board.enemies : DEFAULT_ENEMIES);
44298
+ const items = propItems ?? (Array.isArray(board.items) ? board.items : DEFAULT_ITEMS);
44299
+ const player = propPlayer ?? board.player ?? { x: 1, y: 1 };
44300
+ const playerHp = propPlayerHp ?? num(board.playerHp, 10);
44301
+ const playerMaxHp = propPlayerMaxHp ?? num(board.playerMaxHp, 10);
44302
+ const playerAttack = propPlayerAttack ?? num(board.playerAttack, 3);
44303
+ const depth = propDepth ?? num(board.depth, 1);
44304
+ const result = propResult ?? (str(board.result) || "none");
44305
+ const phase = propPhase ?? (str(board.phase) || "player_turn");
44306
+ propStairsX ?? num(board.stairsX, 3);
44307
+ propStairsY ?? num(board.stairsY, 3);
44308
+ const eventBus = useEventBus();
44309
+ const { t } = useTranslate();
44310
+ const [hoveredTile, setHoveredTile] = useState(null);
44311
+ const emitMove = useCallback((dx, dy) => {
44312
+ if (!moveEvent) return;
44313
+ eventBus.emit(`UI:${moveEvent}`, { dx, dy });
44314
+ }, [moveEvent, eventBus]);
44315
+ useEffect(() => {
44316
+ const handler = (e) => {
44317
+ if (result !== "none" || phase !== "player_turn") return;
44318
+ if (e.key === "ArrowUp" || e.key === "w") emitMove(0, -1);
44319
+ if (e.key === "ArrowDown" || e.key === "s") emitMove(0, 1);
44320
+ if (e.key === "ArrowLeft" || e.key === "a") emitMove(-1, 0);
44321
+ if (e.key === "ArrowRight" || e.key === "d") emitMove(1, 0);
44322
+ };
44323
+ window.addEventListener("keydown", handler);
44324
+ return () => window.removeEventListener("keydown", handler);
44325
+ }, [result, phase, emitMove]);
44326
+ useEffect(() => {
44327
+ if ((result === "won" || result === "lost") && gameEndEvent) {
44328
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
44329
+ }
44330
+ }, [result, gameEndEvent, eventBus]);
44331
+ const handleTileClick = useCallback((x, y) => {
44332
+ if (result !== "none" || phase !== "player_turn") return;
44333
+ const dx = x - player.x;
44334
+ const dy = y - player.y;
44335
+ if (Math.abs(dx) + Math.abs(dy) === 1) {
44336
+ emitMove(dx, dy);
44337
+ }
44338
+ }, [result, phase, player, emitMove]);
44339
+ const handleReset = useCallback(() => {
44340
+ if (!playAgainEvent) return;
44341
+ eventBus.emit(`UI:${playAgainEvent}`, {});
44342
+ }, [playAgainEvent, eventBus]);
44343
+ const isoUnits = useMemo(() => {
44344
+ const liveEnemies = enemies.filter((e) => e.hp > 0);
44345
+ return [
44346
+ {
44347
+ id: "player",
44348
+ position: player,
44349
+ name: t("roguelike.player") || "Hero",
44350
+ team: "player",
44351
+ health: playerHp,
44352
+ maxHealth: playerMaxHp,
44353
+ unitType: "player",
44354
+ sprite: `${CDN4}/isometric-dungeon/Characters/Male/Male_0_Idle0.png`
44355
+ },
44356
+ ...liveEnemies.map((e) => ({
44357
+ id: e.id,
44358
+ position: { x: e.x, y: e.y },
44359
+ name: t("roguelike.enemy") || "Enemy",
44360
+ team: "enemy",
44361
+ health: e.hp,
44362
+ maxHealth: 6,
44363
+ unitType: "enemy",
44364
+ sprite: `${CDN4}/sprite-sheets/shadow-legion-sprite-sheet-sw.png`
44365
+ }))
44366
+ ];
44367
+ }, [player, enemies, playerHp, playerMaxHp, t]);
44368
+ const isoFeatures = useMemo(
44369
+ () => items.map((it) => ({
44370
+ id: it.id,
44371
+ x: it.x,
44372
+ y: it.y,
44373
+ type: it.kind,
44374
+ sprite: FEATURE_SPRITE[it.kind] ?? FEATURE_SPRITE.health_potion
44375
+ })),
44376
+ [items]
44377
+ );
44378
+ const validMoves = useMemo(() => {
44379
+ if (result !== "none" || phase !== "player_turn") return [];
44380
+ const dirs = [{ dx: 0, dy: -1 }, { dx: 0, dy: 1 }, { dx: -1, dy: 0 }, { dx: 1, dy: 0 }];
44381
+ return dirs.map(({ dx, dy }) => ({ x: player.x + dx, y: player.y + dy })).filter(({ x, y }) => {
44382
+ const tile = tiles.find((t2) => t2.x === x && t2.y === y);
44383
+ return tile?.passable ?? false;
44384
+ });
44385
+ }, [result, phase, player, tiles]);
44386
+ const isGameOver = result === "won" || result === "lost";
44387
+ return /* @__PURE__ */ jsxs(VStack, { className: cn("roguelike-board relative min-h-[600px] bg-background", className), gap: "none", children: [
44388
+ /* @__PURE__ */ jsxs(HStack, { className: "items-center justify-between px-4 py-2 border-b border-border", gap: "md", children: [
44389
+ /* @__PURE__ */ jsxs(HStack, { gap: "sm", className: "items-center", children: [
44390
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: t("roguelike.depth") || "Depth" }),
44391
+ /* @__PURE__ */ jsx(Typography, { variant: "body1", className: "font-bold tabular-nums", children: depth })
44392
+ ] }),
44393
+ /* @__PURE__ */ jsxs(HStack, { gap: "sm", className: "items-center", children: [
44394
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: t("roguelike.hp") || "HP" }),
44395
+ /* @__PURE__ */ jsxs(
44396
+ Typography,
44397
+ {
44398
+ variant: "body1",
44399
+ className: cn(
44400
+ "font-bold tabular-nums",
44401
+ playerHp <= Math.floor(playerMaxHp * 0.25) ? "text-error" : "text-success"
44402
+ ),
44403
+ children: [
44404
+ playerHp,
44405
+ " / ",
44406
+ playerMaxHp
44407
+ ]
44408
+ }
44409
+ )
44410
+ ] }),
44411
+ /* @__PURE__ */ jsxs(HStack, { gap: "sm", className: "items-center", children: [
44412
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: t("roguelike.atk") || "ATK" }),
44413
+ /* @__PURE__ */ jsx(Typography, { variant: "body1", className: "font-bold tabular-nums text-foreground", children: playerAttack })
44414
+ ] }),
44415
+ /* @__PURE__ */ jsxs(HStack, { gap: "sm", className: "items-center", children: [
44416
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: t("roguelike.phase") || "Phase" }),
44417
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "capitalize text-foreground", children: phase.replace("_", " ") })
44418
+ ] })
44419
+ ] }),
44420
+ /* @__PURE__ */ jsx(Box, { className: "relative flex-1", children: /* @__PURE__ */ jsx(
44421
+ IsometricCanvas_default,
44422
+ {
44423
+ tiles,
44424
+ units: isoUnits,
44425
+ features: isoFeatures,
44426
+ selectedUnitId: null,
44427
+ validMoves,
44428
+ attackTargets: [],
44429
+ hoveredTile,
44430
+ onTileClick: handleTileClick,
44431
+ onUnitClick: () => void 0,
44432
+ onTileHover: (x, y) => setHoveredTile({ x, y }),
44433
+ onTileLeave: () => setHoveredTile(null),
44434
+ scale,
44435
+ assetBaseUrl: propAssetManifest?.baseUrl ?? CDN4,
44436
+ assetManifest: propAssetManifest,
44437
+ unitScale
44438
+ }
44439
+ ) }),
44440
+ !isGameOver && /* @__PURE__ */ jsx(HStack, { className: "justify-center gap-2 p-3", gap: "none", children: [
44441
+ { label: "\u2191", dx: 0, dy: -1 },
44442
+ { label: "\u2193", dx: 0, dy: 1 },
44443
+ { label: "\u2190", dx: -1, dy: 0 },
44444
+ { label: "\u2192", dx: 1, dy: 0 }
44445
+ ].map(({ label, dx, dy }) => /* @__PURE__ */ jsx(
44446
+ Button,
44447
+ {
44448
+ variant: "secondary",
44449
+ className: "w-9 h-9 p-0 text-base font-mono",
44450
+ onClick: () => emitMove(dx, dy),
44451
+ disabled: phase !== "player_turn",
44452
+ children: label
44453
+ },
44454
+ label
44455
+ )) }),
44456
+ 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: [
44457
+ /* @__PURE__ */ jsx(
44458
+ Typography,
44459
+ {
44460
+ variant: "h2",
44461
+ className: cn(
44462
+ "text-4xl font-black tracking-widest uppercase",
44463
+ result === "won" ? "text-warning" : "text-error"
44464
+ ),
44465
+ children: result === "won" ? t("roguelike.victory") || "Victory!" : t("roguelike.defeat") || "Defeated!"
44466
+ }
44467
+ ),
44468
+ result === "won" && /* @__PURE__ */ jsx(Typography, { variant: "body1", className: "text-muted-foreground", children: t("roguelike.cleared") || `Dungeon cleared! Depth reached: ${depth}` }),
44469
+ /* @__PURE__ */ jsx(
44470
+ Button,
44471
+ {
44472
+ variant: "primary",
44473
+ className: "px-8 py-3 font-semibold",
44474
+ onClick: handleReset,
44475
+ children: t("roguelike.playAgain") || "Play Again"
44476
+ }
44477
+ )
44478
+ ] }) })
44479
+ ] });
44480
+ }
44481
+ var CDN4, DEFAULT_TILES2, DEFAULT_ENEMIES, DEFAULT_ITEMS, FEATURE_SPRITE;
44482
+ var init_RoguelikeBoard = __esm({
44483
+ "components/game/organisms/RoguelikeBoard.tsx"() {
44484
+ "use client";
44485
+ init_cn();
44486
+ init_useEventBus();
44487
+ init_Box();
44488
+ init_Button();
44489
+ init_Typography();
44490
+ init_Stack();
44491
+ init_IsometricCanvas();
44492
+ init_boardEntity();
44493
+ CDN4 = "https://almadar-kflow-assets.web.app/shared";
44494
+ DEFAULT_TILES2 = [
44495
+ { x: 0, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44496
+ { x: 1, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44497
+ { x: 2, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44498
+ { x: 3, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44499
+ { x: 4, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44500
+ { x: 0, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44501
+ { x: 1, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
44502
+ { x: 2, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
44503
+ { x: 3, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
44504
+ { x: 4, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44505
+ { x: 0, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44506
+ { x: 1, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirtTiles_E.png` },
44507
+ { x: 2, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirtTiles_E.png` },
44508
+ { x: 3, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
44509
+ { x: 4, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44510
+ { x: 0, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44511
+ { x: 1, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
44512
+ { x: 2, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirtTiles_E.png` },
44513
+ { x: 3, y: 3, terrain: "stairs", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stairs_E.png` },
44514
+ { x: 4, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44515
+ { x: 0, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44516
+ { x: 1, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44517
+ { x: 2, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44518
+ { x: 3, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
44519
+ { x: 4, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` }
44520
+ ];
44521
+ DEFAULT_ENEMIES = [
44522
+ { id: "e1", x: 3, y: 1, hp: 5, attack: 2 },
44523
+ { id: "e2", x: 1, y: 3, hp: 4, attack: 1 }
44524
+ ];
44525
+ DEFAULT_ITEMS = [
44526
+ { id: "i1", x: 2, y: 2, kind: "health_potion" }
44527
+ ];
44528
+ FEATURE_SPRITE = {
44529
+ health_potion: `${CDN4}/isometric-dungeon/Isometric/chestClosed_E.png`,
44530
+ sword: `${CDN4}/isometric-dungeon/Isometric/barrel_E.png`,
44531
+ shield: `${CDN4}/isometric-dungeon/Isometric/barrel_E.png`
44532
+ };
44533
+ RoguelikeBoard.displayName = "RoguelikeBoard";
44534
+ }
44535
+ });
44536
+ function RoguelikeTemplate({
44537
+ entity,
44538
+ scale = 0.45,
44539
+ unitScale = 1,
44540
+ tiles = DEFAULT_ROGUELIKE_TILES,
44541
+ enemies,
44542
+ items,
44543
+ stairsX,
44544
+ stairsY,
44545
+ assetManifest = DEFAULT_ROGUELIKE_MANIFEST,
44546
+ gameEndEvent,
44547
+ className
44548
+ }) {
44549
+ const resolved = entity && typeof entity === "object" && !Array.isArray(entity) ? entity : void 0;
44550
+ if (!resolved && !tiles && !assetManifest) return null;
44551
+ return /* @__PURE__ */ jsx(
44552
+ RoguelikeBoard,
44553
+ {
44554
+ entity: resolved,
44555
+ tiles,
44556
+ enemies,
44557
+ items,
44558
+ stairsX,
44559
+ stairsY,
44560
+ assetManifest,
44561
+ scale,
44562
+ unitScale,
44563
+ moveEvent: "MOVE",
44564
+ playAgainEvent: "PLAY_AGAIN",
44565
+ gameEndEvent: gameEndEvent ?? "GAME_END",
44566
+ className
44567
+ }
44568
+ );
44569
+ }
44570
+ var CDN5, DEFAULT_ROGUELIKE_TILES, DEFAULT_ROGUELIKE_MANIFEST;
44571
+ var init_RoguelikeTemplate = __esm({
44572
+ "components/game/templates/RoguelikeTemplate.tsx"() {
44573
+ init_RoguelikeBoard();
44574
+ CDN5 = "https://almadar-kflow-assets.web.app/shared";
44575
+ DEFAULT_ROGUELIKE_TILES = [
44576
+ { x: 0, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44577
+ { x: 1, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44578
+ { x: 2, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44579
+ { x: 3, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44580
+ { x: 4, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44581
+ { x: 0, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44582
+ { x: 1, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
44583
+ { x: 2, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
44584
+ { x: 3, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
44585
+ { x: 4, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44586
+ { x: 0, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44587
+ { x: 1, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirtTiles_E.png` },
44588
+ { x: 2, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirtTiles_E.png` },
44589
+ { x: 3, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
44590
+ { x: 4, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44591
+ { x: 0, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44592
+ { x: 1, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
44593
+ { x: 2, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirtTiles_E.png` },
44594
+ { x: 3, y: 3, terrain: "stairs", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stairs_E.png` },
44595
+ { x: 4, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44596
+ { x: 0, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44597
+ { x: 1, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44598
+ { x: 2, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44599
+ { x: 3, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
44600
+ { x: 4, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` }
44601
+ ];
44602
+ DEFAULT_ROGUELIKE_MANIFEST = {
44603
+ baseUrl: CDN5,
44604
+ terrains: {
44605
+ floor: "/isometric-dungeon/Isometric/dirt_E.png",
44606
+ wall: "/isometric-dungeon/Isometric/stoneInset_E.png",
44607
+ stairs: "/isometric-dungeon/Isometric/stairs_E.png",
44608
+ planks: "/isometric-dungeon/Isometric/planks_E.png"
44609
+ },
44610
+ units: {
44611
+ player: "/isometric-dungeon/Characters/Male/Male_0_Idle0.png",
44612
+ enemy: "/sprite-sheets/shadow-legion-sprite-sheet-sw.png"
44613
+ },
44614
+ features: {
44615
+ health_potion: "/isometric-dungeon/Isometric/chestClosed_E.png",
44616
+ sword: "/isometric-dungeon/Isometric/barrel_E.png",
44617
+ shield: "/isometric-dungeon/Isometric/barrel_E.png"
44618
+ }
44619
+ };
44620
+ RoguelikeTemplate.displayName = "RoguelikeTemplate";
44621
+ }
44622
+ });
42347
44623
 
42348
44624
  // lib/traitRegistry.ts
42349
44625
  function notifyListeners2() {
@@ -43597,7 +45873,7 @@ function getAllEvents(traits2) {
43597
45873
  function EventDispatcherTab({ traits: traits2, schema }) {
43598
45874
  const eventBus = useEventBus();
43599
45875
  const { t } = useTranslate();
43600
- const [log12, setLog] = React82.useState([]);
45876
+ const [log14, setLog] = React82.useState([]);
43601
45877
  const prevStatesRef = React82.useRef(/* @__PURE__ */ new Map());
43602
45878
  React82.useEffect(() => {
43603
45879
  for (const trait of traits2) {
@@ -43661,9 +45937,9 @@ function EventDispatcherTab({ traits: traits2, schema }) {
43661
45937
  /* @__PURE__ */ jsx(Typography, { variant: "small", weight: "medium", className: "text-gray-500 mb-1", children: t("debug.otherEvents") }),
43662
45938
  /* @__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)) })
43663
45939
  ] }),
43664
- log12.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
45940
+ log14.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
43665
45941
  /* @__PURE__ */ jsx(Typography, { variant: "small", weight: "medium", className: "text-gray-500 mb-1", children: t("debug.recentTransitions") }),
43666
- /* @__PURE__ */ jsx(Stack, { gap: "xs", children: log12.map((entry, i) => /* @__PURE__ */ jsxs(Typography, { variant: "small", className: "font-mono text-xs", children: [
45942
+ /* @__PURE__ */ jsx(Stack, { gap: "xs", children: log14.map((entry, i) => /* @__PURE__ */ jsxs(Typography, { variant: "small", className: "font-mono text-xs", children: [
43667
45943
  /* @__PURE__ */ jsx("span", { className: "text-purple-400", children: entry.traitName }),
43668
45944
  " ",
43669
45945
  /* @__PURE__ */ jsx("span", { className: "text-gray-500", children: entry.from }),
@@ -44466,17 +46742,24 @@ function SequencerBoard({
44466
46742
  const { emit } = useEventBus();
44467
46743
  const { t } = useTranslate();
44468
46744
  const resolved = boardEntity(entity);
44469
- const maxSlots = num(resolved?.maxSlots);
46745
+ const maxSlots = num(resolved?.maxSlots) || 3;
44470
46746
  const solutions = Array.isArray(resolved?.solutions) ? resolved.solutions : [];
44471
46747
  const availableActions = Array.isArray(resolved?.availableActions) ? resolved.availableActions : [];
44472
46748
  const allowDuplicates = resolved?.allowDuplicates !== false;
46749
+ const entitySlots = Array.isArray(resolved?.slots) ? resolved.slots : [];
46750
+ const entityResult = str(resolved?.result);
46751
+ const entityAttempts = num(resolved?.attempts);
46752
+ const entityCurrentStep = typeof resolved?.currentStep === "number" ? resolved.currentStep : -1;
46753
+ const slots = Array.from({ length: maxSlots }, (_, i) => {
46754
+ const entitySlot = entitySlots.find((s) => s.index === i);
46755
+ if (!entitySlot?.placedActionId) return void 0;
46756
+ return availableActions.find((a) => a.id === entitySlot.placedActionId);
46757
+ });
46758
+ const isSuccess = entityResult === "win";
46759
+ const attempts = entityAttempts;
46760
+ const isPlayingBack = entityCurrentStep >= 0 && !isSuccess;
46761
+ const currentStep = entityCurrentStep;
44473
46762
  const [headerError, setHeaderError] = useState(false);
44474
- const [slots, setSlots] = useState(
44475
- () => Array.from({ length: maxSlots }, () => void 0)
44476
- );
44477
- const [playState, setPlayState] = useState("idle");
44478
- const [currentStep, setCurrentStep] = useState(-1);
44479
- const [attempts, setAttempts] = useState(0);
44480
46763
  const [slotFeedback, setSlotFeedback] = useState(
44481
46764
  () => Array.from({ length: maxSlots }, () => null)
44482
46765
  );
@@ -44485,11 +46768,6 @@ function SequencerBoard({
44485
46768
  if (timerRef.current) clearTimeout(timerRef.current);
44486
46769
  }, []);
44487
46770
  const handleSlotDrop = useCallback((index, item) => {
44488
- setSlots((prev) => {
44489
- const next = [...prev];
44490
- next[index] = item;
44491
- return next;
44492
- });
44493
46771
  setSlotFeedback((prev) => {
44494
46772
  const next = [...prev];
44495
46773
  next[index] = null;
@@ -44499,11 +46777,6 @@ function SequencerBoard({
44499
46777
  if (placeEvent) emit(`UI:${placeEvent}`, { slotIndex: index, actionId: item.id });
44500
46778
  }, [emit, placeEvent]);
44501
46779
  const handleSlotRemove = useCallback((index) => {
44502
- setSlots((prev) => {
44503
- const next = [...prev];
44504
- next[index] = void 0;
44505
- return next;
44506
- });
44507
46780
  setSlotFeedback((prev) => {
44508
46781
  const next = [...prev];
44509
46782
  next[index] = null;
@@ -44514,48 +46787,34 @@ function SequencerBoard({
44514
46787
  }, [emit, removeEvent]);
44515
46788
  const handleReset = useCallback(() => {
44516
46789
  if (timerRef.current) clearTimeout(timerRef.current);
44517
- setSlots(Array.from({ length: maxSlots }, () => void 0));
44518
- setPlayState("idle");
44519
- setCurrentStep(-1);
44520
- setAttempts(0);
44521
46790
  setSlotFeedback(Array.from({ length: maxSlots }, () => null));
44522
46791
  if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
44523
46792
  }, [maxSlots, playAgainEvent, emit]);
44524
46793
  const filledSlots = slots.filter((s) => !!s);
44525
- const canPlay = filledSlots.length > 0 && playState === "idle";
46794
+ const canPlay = filledSlots.length > 0 && !isPlayingBack && !isSuccess;
44526
46795
  const handlePlay = useCallback(() => {
44527
46796
  if (!canPlay) return;
44528
46797
  setSlotFeedback(Array.from({ length: maxSlots }, () => null));
44529
46798
  emit("UI:PLAY_SOUND", { key: "confirm" });
44530
46799
  const sequence = slots.map((s) => s?.id || "");
44531
- if (playEvent) {
44532
- emit(`UI:${playEvent}`, { sequence });
44533
- }
44534
- setPlayState("playing");
44535
- setCurrentStep(0);
46800
+ const playerIds = slots.filter(Boolean).map((s) => s?.id || "");
46801
+ if (playEvent) emit(`UI:${playEvent}`, { sequence });
44536
46802
  let step = 0;
44537
46803
  const advance = () => {
44538
46804
  step++;
46805
+ emit("UI:STEP", { step });
44539
46806
  if (step >= maxSlots) {
46807
+ if (checkEvent) emit(`UI:${checkEvent}`, { sequence: playerIds });
44540
46808
  const playerSeq = slots.map((s) => s?.id);
44541
- const playerIds = slots.filter(Boolean).map((s) => s?.id || "");
44542
46809
  const success = solutions.some(
44543
46810
  (sol) => sol.length === playerIds.length && sol.every((id, i) => id === playerIds[i])
44544
46811
  );
44545
- if (checkEvent) emit(`UI:${checkEvent}`, { sequence: playerIds });
44546
46812
  if (success) {
44547
- setPlayState("success");
44548
- setCurrentStep(-1);
44549
46813
  emit("UI:PLAY_SOUND", { key: "levelComplete" });
44550
- if (completeEvent) {
44551
- emit(`UI:${completeEvent}`, { success: true, sequence: playerIds });
44552
- }
46814
+ if (completeEvent) emit(`UI:${completeEvent}`, { success: true, sequence: playerIds });
44553
46815
  } else {
44554
- setAttempts((prev) => prev + 1);
44555
46816
  const feedback = computeSlotFeedback(playerSeq, solutions);
44556
46817
  setSlotFeedback(feedback);
44557
- setPlayState("idle");
44558
- setCurrentStep(-1);
44559
46818
  emit("UI:PLAY_SOUND", { key: "fail" });
44560
46819
  const correctCount2 = feedback.filter((f3) => f3 === "correct").length;
44561
46820
  for (let ci = 0; ci < correctCount2; ci++) {
@@ -44565,7 +46824,6 @@ function SequencerBoard({
44565
46824
  }
44566
46825
  }
44567
46826
  } else {
44568
- setCurrentStep(step);
44569
46827
  timerRef.current = setTimeout(advance, stepDurationMs);
44570
46828
  }
44571
46829
  };
@@ -44615,7 +46873,7 @@ function SequencerBoard({
44615
46873
  /* @__PURE__ */ jsxs(VStack, { gap: "xs", children: [
44616
46874
  /* @__PURE__ */ jsxs(HStack, { className: "items-center justify-between", children: [
44617
46875
  /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("sequencer.yourSequence") + ":" }),
44618
- hasFeedback && playState === "idle" && /* @__PURE__ */ jsxs(Typography, { variant: "caption", className: "text-muted-foreground", children: [
46876
+ hasFeedback && !isPlayingBack && !isSuccess && /* @__PURE__ */ jsxs(Typography, { variant: "caption", className: "text-muted-foreground", children: [
44619
46877
  `${correctCount}/${maxSlots} `,
44620
46878
  "\u2705"
44621
46879
  ] })
@@ -44627,7 +46885,7 @@ function SequencerBoard({
44627
46885
  maxSlots,
44628
46886
  onSlotDrop: handleSlotDrop,
44629
46887
  onSlotRemove: handleSlotRemove,
44630
- playing: playState === "playing",
46888
+ playing: isPlayingBack,
44631
46889
  currentStep,
44632
46890
  categoryColors,
44633
46891
  slotFeedback,
@@ -44635,7 +46893,7 @@ function SequencerBoard({
44635
46893
  }
44636
46894
  )
44637
46895
  ] }),
44638
- playState !== "playing" && /* @__PURE__ */ jsx(
46896
+ !isPlayingBack && /* @__PURE__ */ jsx(
44639
46897
  ActionPalette,
44640
46898
  {
44641
46899
  actions: availableActions,
@@ -44645,8 +46903,8 @@ function SequencerBoard({
44645
46903
  label: t("sequencer.dragActions")
44646
46904
  }
44647
46905
  ),
44648
- 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) }) }),
44649
- 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") }) }),
46906
+ 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) }) }),
46907
+ 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") }) }),
44650
46908
  /* @__PURE__ */ jsxs(HStack, { gap: "sm", children: [
44651
46909
  /* @__PURE__ */ jsx(
44652
46910
  Button,
@@ -45602,9 +47860,9 @@ function VariablePanel({
45602
47860
  /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("stateArchitect.variables", { name: entityName }) }),
45603
47861
  variables.map((v) => {
45604
47862
  const name = v.name == null ? "" : String(v.name);
45605
- const value = numField(v.value);
45606
- const max = numField(v.max, 100);
45607
- const min = numField(v.min, 0);
47863
+ const value = numField2(v.value);
47864
+ const max = numField2(v.max, 100);
47865
+ const min = numField2(v.min, 0);
45608
47866
  const unit = v.unit == null ? "" : String(v.unit);
45609
47867
  const pct = Math.round((value - min) / (max - min) * 100);
45610
47868
  const isHigh = pct > 80;
@@ -45634,12 +47892,12 @@ function VariablePanel({
45634
47892
  })
45635
47893
  ] });
45636
47894
  }
45637
- var numField;
47895
+ var numField2;
45638
47896
  var init_VariablePanel = __esm({
45639
47897
  "components/game/organisms/puzzles/state-architect/VariablePanel.tsx"() {
45640
47898
  init_atoms2();
45641
47899
  init_cn();
45642
- numField = (v, fallback = 0) => {
47900
+ numField2 = (v, fallback = 0) => {
45643
47901
  const n = Number(v);
45644
47902
  return Number.isFinite(n) ? n : fallback;
45645
47903
  };
@@ -45696,6 +47954,9 @@ function StateArchitectBoard({
45696
47954
  stepDurationMs = 600,
45697
47955
  testEvent,
45698
47956
  completeEvent,
47957
+ addTransitionEvent,
47958
+ removeTransitionEvent,
47959
+ playAgainEvent,
45699
47960
  className
45700
47961
  }) {
45701
47962
  const { emit } = useEventBus();
@@ -45708,14 +47969,16 @@ function StateArchitectBoard({
45708
47969
  const testCases = Array.isArray(resolved?.testCases) ? resolved.testCases : [];
45709
47970
  const entityTransitions = Array.isArray(resolved?.transitions) ? resolved.transitions : [];
45710
47971
  const entityVariables = rows(resolved?.variables);
45711
- const [transitions, setTransitions] = useState(entityTransitions);
47972
+ const transitions = entityTransitions;
47973
+ const attempts = typeof resolved?.attempts === "number" ? resolved.attempts : 0;
47974
+ const entityResult = str(resolved?.result) || "none";
47975
+ const isSuccess = entityResult === "win";
47976
+ const [isTesting, setIsTesting] = useState(false);
45712
47977
  const [headerError, setHeaderError] = useState(false);
45713
- const [playState, setPlayState] = useState("editing");
45714
47978
  const [currentState, setCurrentState] = useState(initialState);
45715
47979
  const [selectedState, setSelectedState] = useState(null);
45716
47980
  const [testResults, setTestResults] = useState([]);
45717
47981
  const [variables, setVariables] = useState(() => [...entityVariables]);
45718
- const [attempts, setAttempts] = useState(0);
45719
47982
  const timerRef = useRef(null);
45720
47983
  const [addingFrom, setAddingFrom] = useState(null);
45721
47984
  useEffect(() => () => {
@@ -45725,7 +47988,7 @@ function StateArchitectBoard({
45725
47988
  const GRAPH_H = 400;
45726
47989
  const positions = useMemo(() => layoutStates(entityStates, GRAPH_W, GRAPH_H), [entityStates]);
45727
47990
  const handleStateClick = useCallback((state) => {
45728
- if (playState !== "editing") return;
47991
+ if (isTesting) return;
45729
47992
  if (addingFrom) {
45730
47993
  if (addingFrom !== state) {
45731
47994
  const event = availableEvents[0] || "EVENT";
@@ -45735,20 +47998,20 @@ function StateArchitectBoard({
45735
47998
  to: state,
45736
47999
  event
45737
48000
  };
45738
- setTransitions((prev) => [...prev, newTrans]);
48001
+ if (addTransitionEvent) emit(`UI:${addTransitionEvent}`, { id: newTrans.id, from: newTrans.from, to: newTrans.to, event: newTrans.event });
45739
48002
  }
45740
48003
  setAddingFrom(null);
45741
48004
  } else {
45742
48005
  setSelectedState(state);
45743
48006
  }
45744
- }, [playState, addingFrom, availableEvents]);
48007
+ }, [isTesting, addingFrom, availableEvents, addTransitionEvent, emit]);
45745
48008
  const handleStartAddTransition = useCallback(() => {
45746
48009
  if (!selectedState) return;
45747
48010
  setAddingFrom(selectedState);
45748
48011
  }, [selectedState]);
45749
48012
  const handleRemoveTransition = useCallback((transId) => {
45750
- setTransitions((prev) => prev.filter((t2) => t2.id !== transId));
45751
- }, []);
48013
+ if (removeTransitionEvent) emit(`UI:${removeTransitionEvent}`, { id: transId });
48014
+ }, [removeTransitionEvent, emit]);
45752
48015
  const machine = useMemo(() => ({
45753
48016
  name: entityName,
45754
48017
  description: str(resolved?.description),
@@ -45762,16 +48025,16 @@ function StateArchitectBoard({
45762
48025
  }))
45763
48026
  }), [entityName, resolved, entityStates, currentState, transitions]);
45764
48027
  const handleTest = useCallback(() => {
45765
- if (playState !== "editing") return;
48028
+ if (isTesting) return;
45766
48029
  if (testEvent) emit(`UI:${testEvent}`, {});
45767
- setPlayState("testing");
48030
+ setIsTesting(true);
45768
48031
  setTestResults([]);
45769
48032
  const results = [];
45770
48033
  let testIdx = 0;
45771
48034
  const runNextTest = () => {
45772
48035
  if (testIdx >= testCases.length) {
45773
48036
  const allPassed = results.every((r) => r.passed);
45774
- setPlayState(allPassed ? "success" : "fail");
48037
+ setIsTesting(false);
45775
48038
  setTestResults(results);
45776
48039
  if (allPassed && completeEvent) {
45777
48040
  emit(`UI:${completeEvent}`, {
@@ -45779,9 +48042,6 @@ function StateArchitectBoard({
45779
48042
  passedTests: results.filter((r) => r.passed).length
45780
48043
  });
45781
48044
  }
45782
- if (!allPassed) {
45783
- setAttempts((prev) => prev + 1);
45784
- }
45785
48045
  return;
45786
48046
  }
45787
48047
  const testCase = testCases[testIdx];
@@ -45804,24 +48064,23 @@ function StateArchitectBoard({
45804
48064
  timerRef.current = setTimeout(runNextTest, stepDurationMs);
45805
48065
  };
45806
48066
  timerRef.current = setTimeout(runNextTest, stepDurationMs);
45807
- }, [playState, transitions, testCases, initialState, stepDurationMs, testEvent, completeEvent, emit]);
48067
+ }, [isTesting, transitions, testCases, initialState, stepDurationMs, testEvent, completeEvent, emit]);
45808
48068
  const handleTryAgain = useCallback(() => {
45809
48069
  if (timerRef.current) clearTimeout(timerRef.current);
45810
- setPlayState("editing");
48070
+ setIsTesting(false);
45811
48071
  setCurrentState(initialState);
45812
48072
  setTestResults([]);
45813
48073
  }, [initialState]);
45814
48074
  const handleReset = useCallback(() => {
45815
48075
  if (timerRef.current) clearTimeout(timerRef.current);
45816
- setTransitions(entityTransitions);
45817
- setPlayState("editing");
48076
+ setIsTesting(false);
45818
48077
  setCurrentState(initialState);
45819
48078
  setTestResults([]);
45820
48079
  setVariables([...entityVariables]);
45821
48080
  setSelectedState(null);
45822
48081
  setAddingFrom(null);
45823
- setAttempts(0);
45824
- }, [entityTransitions, initialState, entityVariables]);
48082
+ if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
48083
+ }, [initialState, entityVariables, playAgainEvent, emit]);
45825
48084
  const codeData = useMemo(() => ({
45826
48085
  name: entityName,
45827
48086
  states: entityStates,
@@ -45917,7 +48176,7 @@ function StateArchitectBoard({
45917
48176
  ]
45918
48177
  }
45919
48178
  ),
45920
- playState === "editing" && /* @__PURE__ */ jsx(HStack, { gap: "sm", children: /* @__PURE__ */ jsx(
48179
+ !isTesting && /* @__PURE__ */ jsx(HStack, { gap: "sm", children: /* @__PURE__ */ jsx(
45921
48180
  Button,
45922
48181
  {
45923
48182
  variant: "ghost",
@@ -45939,7 +48198,7 @@ function StateArchitectBoard({
45939
48198
  t2.guardHint,
45940
48199
  ")"
45941
48200
  ] }),
45942
- playState === "editing" && /* @__PURE__ */ jsx(
48201
+ !isTesting && /* @__PURE__ */ jsx(
45943
48202
  Button,
45944
48203
  {
45945
48204
  variant: "ghost",
@@ -45971,21 +48230,21 @@ function StateArchitectBoard({
45971
48230
  resolved.showCodeView !== false && /* @__PURE__ */ jsx(StateJsonView, { data: codeData, label: "View Code" })
45972
48231
  ] })
45973
48232
  ] }),
45974
- 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") }) }),
45975
- playState === "fail" && /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
48233
+ 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") }) }),
48234
+ !isTesting && !isSuccess && testResults.some((r) => !r.passed) && /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
45976
48235
  /* @__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]) }) }),
45977
- 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: [
48236
+ !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: [
45978
48237
  /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-accent font-bold shrink-0", children: "\u{1F4A1} " + t("game.hint") + ":" }),
45979
48238
  /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-foreground", children: hint })
45980
48239
  ] }) })
45981
48240
  ] }),
45982
48241
  /* @__PURE__ */ jsxs(HStack, { gap: "sm", children: [
45983
- playState === "fail" ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleTryAgain, children: "\u{1F504} " + t("puzzle.tryAgainButton") }) : /* @__PURE__ */ jsx(
48242
+ !isTesting && !isSuccess && testResults.some((r) => !r.passed) ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleTryAgain, children: "\u{1F504} " + t("puzzle.tryAgainButton") }) : /* @__PURE__ */ jsx(
45984
48243
  Button,
45985
48244
  {
45986
48245
  variant: "primary",
45987
48246
  onClick: handleTest,
45988
- disabled: playState !== "editing",
48247
+ disabled: isTesting,
45989
48248
  children: "\u25B6 " + t("game.runTests")
45990
48249
  }
45991
48250
  ),
@@ -46491,6 +48750,289 @@ var init_ToastSlot = __esm({
46491
48750
  ToastSlot.displayName = "ToastSlot";
46492
48751
  }
46493
48752
  });
48753
+ function tilesToIso(tiles) {
48754
+ return tiles.map((t) => ({
48755
+ x: t.x,
48756
+ y: t.y,
48757
+ terrain: t.terrain,
48758
+ terrainSprite: t.terrainSprite ?? TERRAIN_SPRITES[t.terrain ?? "ground"],
48759
+ passable: t.passable
48760
+ }));
48761
+ }
48762
+ function towersToUnits(towers) {
48763
+ return towers.map((t) => ({
48764
+ id: t.id,
48765
+ position: { x: t.x, y: t.y },
48766
+ name: "Tower",
48767
+ team: "player",
48768
+ sprite: TOWER_SPRITE,
48769
+ unitType: "guardian",
48770
+ health: 1,
48771
+ maxHealth: 1
48772
+ }));
48773
+ }
48774
+ function creepsToUnits(creeps) {
48775
+ return creeps.map((c) => ({
48776
+ id: c.id,
48777
+ position: { x: c.x, y: c.y },
48778
+ name: "Creep",
48779
+ team: "enemy",
48780
+ sprite: CREEP_SPRITE,
48781
+ unitType: "scrapper",
48782
+ health: c.hp,
48783
+ maxHealth: c.maxHp
48784
+ }));
48785
+ }
48786
+ function pathToFeatures(path) {
48787
+ return path.map((p2, i) => ({
48788
+ id: `path-${i}`,
48789
+ x: p2.x,
48790
+ y: p2.y,
48791
+ type: "path-marker",
48792
+ sprite: `${CDN6}world-map/road_straight.png`
48793
+ }));
48794
+ }
48795
+ function TowerDefenseBoard({
48796
+ entity,
48797
+ tiles: propTiles,
48798
+ path: propPath,
48799
+ towers: propTowers,
48800
+ creeps: propCreeps,
48801
+ gold: propGold,
48802
+ lives: propLives,
48803
+ wave: propWave,
48804
+ maxWaves: propMaxWaves,
48805
+ result: propResult,
48806
+ waveActive: propWaveActive,
48807
+ towerCost = 25,
48808
+ scale = 0.45,
48809
+ placeTowerEvent,
48810
+ startWaveEvent,
48811
+ playAgainEvent,
48812
+ gameEndEvent,
48813
+ className
48814
+ }) {
48815
+ const board = boardEntity(entity) ?? {};
48816
+ const eventBus = useEventBus();
48817
+ const { t } = useTranslate();
48818
+ const tiles = propTiles ?? rows(board.tiles);
48819
+ const path = propPath ?? rows(board.path);
48820
+ const towers = propTowers ?? rows(board.towers);
48821
+ const creeps = propCreeps ?? rows(board.creeps);
48822
+ const gold = propGold ?? num(board.gold, 100);
48823
+ const lives = propLives ?? num(board.lives, 20);
48824
+ const wave = propWave ?? num(board.wave, 1);
48825
+ const maxWaves = propMaxWaves ?? num(board.maxWaves, 5);
48826
+ const result = propResult ?? (str(board.result) || "none");
48827
+ const waveActive = propWaveActive ?? Boolean(board.waveActive);
48828
+ const [hoveredTile, setHoveredTile] = useState(null);
48829
+ const emittedGameEnd = useRef(false);
48830
+ if (result !== "none" && !emittedGameEnd.current) {
48831
+ emittedGameEnd.current = true;
48832
+ if (gameEndEvent) {
48833
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
48834
+ }
48835
+ }
48836
+ if (result === "none") {
48837
+ emittedGameEnd.current = false;
48838
+ }
48839
+ const towerPositions = useMemo(() => new Set(towers.map((t2) => `${t2.x},${t2.y}`)), [towers]);
48840
+ const pathPositions = useMemo(() => new Set(path.map((p2) => `${p2.x},${p2.y}`)), [path]);
48841
+ const validMoves = useMemo(() => {
48842
+ if (result !== "none" || gold < towerCost) return [];
48843
+ return tiles.filter(
48844
+ (t2) => t2.passable !== false && !towerPositions.has(`${t2.x},${t2.y}`) && !pathPositions.has(`${t2.x},${t2.y}`)
48845
+ ).map((t2) => ({ x: t2.x, y: t2.y }));
48846
+ }, [tiles, towerPositions, pathPositions, result, gold, towerCost]);
48847
+ const isoTiles = useMemo(() => tilesToIso(tiles), [tiles]);
48848
+ const towerUnits = useMemo(() => towersToUnits(towers), [towers]);
48849
+ const creepUnits = useMemo(() => creepsToUnits(creeps), [creeps]);
48850
+ const isoUnits = useMemo(() => [...towerUnits, ...creepUnits], [towerUnits, creepUnits]);
48851
+ const pathFeatures = useMemo(() => pathToFeatures(path), [path]);
48852
+ const handleTileClick = useCallback((x, y) => {
48853
+ if (result !== "none") return;
48854
+ if (pathPositions.has(`${x},${y}`)) return;
48855
+ if (towerPositions.has(`${x},${y}`)) return;
48856
+ if (gold < towerCost) return;
48857
+ if (placeTowerEvent) {
48858
+ eventBus.emit(`UI:${placeTowerEvent}`, { x, y });
48859
+ }
48860
+ }, [result, pathPositions, towerPositions, gold, towerCost, placeTowerEvent, eventBus]);
48861
+ const handleStartWave = useCallback(() => {
48862
+ if (startWaveEvent) {
48863
+ eventBus.emit(`UI:${startWaveEvent}`, { wave });
48864
+ }
48865
+ }, [startWaveEvent, wave, eventBus]);
48866
+ const handlePlayAgain = useCallback(() => {
48867
+ if (playAgainEvent) {
48868
+ eventBus.emit(`UI:${playAgainEvent}`, {});
48869
+ }
48870
+ }, [playAgainEvent, eventBus]);
48871
+ const canPlaceTower = gold >= towerCost && result === "none";
48872
+ const canStartWave = !waveActive && result === "none";
48873
+ return /* @__PURE__ */ jsxs(VStack, { className: cn("tower-defense-board relative min-h-[600px] bg-background", className), gap: "none", children: [
48874
+ /* @__PURE__ */ jsxs(
48875
+ HStack,
48876
+ {
48877
+ className: "px-4 py-2 border-b border-border bg-surface",
48878
+ gap: "lg",
48879
+ align: "center",
48880
+ children: [
48881
+ /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
48882
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", children: t("td.gold") ?? "Gold" }),
48883
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", weight: "bold", className: "text-warning", children: gold })
48884
+ ] }),
48885
+ /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
48886
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", children: t("td.lives") ?? "Lives" }),
48887
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", weight: "bold", className: lives <= 5 ? "text-error" : "text-success", children: lives })
48888
+ ] }),
48889
+ /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
48890
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", children: t("td.wave") ?? "Wave" }),
48891
+ /* @__PURE__ */ jsxs(Typography, { variant: "body2", weight: "bold", children: [
48892
+ wave,
48893
+ " / ",
48894
+ maxWaves
48895
+ ] })
48896
+ ] }),
48897
+ /* @__PURE__ */ jsxs(Box, { className: "ml-auto", children: [
48898
+ canStartWave && /* @__PURE__ */ jsx(Button, { variant: "primary", size: "sm", onClick: handleStartWave, children: t("td.startWave") ?? "Start Wave" }),
48899
+ waveActive && /* @__PURE__ */ jsx(Typography, { variant: "small", className: "text-warning animate-pulse", children: t("td.waveInProgress") ?? "Wave in progress\u2026" })
48900
+ ] })
48901
+ ]
48902
+ }
48903
+ ),
48904
+ /* @__PURE__ */ jsxs(Box, { className: "relative flex-1", children: [
48905
+ /* @__PURE__ */ jsx(
48906
+ IsometricCanvas_default,
48907
+ {
48908
+ tiles: isoTiles,
48909
+ units: isoUnits,
48910
+ features: pathFeatures,
48911
+ validMoves,
48912
+ hoveredTile,
48913
+ onTileClick: handleTileClick,
48914
+ onTileHover: (x, y) => setHoveredTile({ x, y }),
48915
+ onTileLeave: () => setHoveredTile(null),
48916
+ scale
48917
+ }
48918
+ ),
48919
+ 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)` }) })
48920
+ ] }),
48921
+ 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: [
48922
+ /* @__PURE__ */ jsx(
48923
+ Typography,
48924
+ {
48925
+ variant: "h2",
48926
+ className: cn(
48927
+ "text-4xl font-black tracking-widest uppercase",
48928
+ result === "won" ? "text-warning" : "text-error"
48929
+ ),
48930
+ children: result === "won" ? t("td.victory") ?? "Victory!" : t("td.defeat") ?? "Defeat!"
48931
+ }
48932
+ ),
48933
+ /* @__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." }),
48934
+ /* @__PURE__ */ jsx(
48935
+ Button,
48936
+ {
48937
+ variant: "primary",
48938
+ className: "px-8 py-3 font-semibold",
48939
+ onClick: handlePlayAgain,
48940
+ children: t("td.playAgain") ?? "Play Again"
48941
+ }
48942
+ )
48943
+ ] }) })
48944
+ ] });
48945
+ }
48946
+ var CDN6, TERRAIN_SPRITES, TOWER_SPRITE, CREEP_SPRITE;
48947
+ var init_TowerDefenseBoard = __esm({
48948
+ "components/game/organisms/TowerDefenseBoard.tsx"() {
48949
+ "use client";
48950
+ init_cn();
48951
+ init_useEventBus();
48952
+ init_Box();
48953
+ init_Button();
48954
+ init_Typography();
48955
+ init_Stack();
48956
+ init_IsometricCanvas();
48957
+ init_boardEntity();
48958
+ CDN6 = "https://almadar-kflow-assets.web.app/shared/";
48959
+ TERRAIN_SPRITES = {
48960
+ ground: `${CDN6}isometric-blocks/PNG/Platformer tiles/platformerTile_01.png`,
48961
+ path: `${CDN6}isometric-blocks/PNG/Platformer tiles/platformerTile_09.png`,
48962
+ wall: `${CDN6}isometric-blocks/PNG/Platformer tiles/platformerTile_05.png`
48963
+ };
48964
+ TOWER_SPRITE = `${CDN6}units/guardian.png`;
48965
+ CREEP_SPRITE = `${CDN6}units/scrapper.png`;
48966
+ TowerDefenseBoard.displayName = "TowerDefenseBoard";
48967
+ }
48968
+ });
48969
+ function TowerDefenseTemplate({
48970
+ entity,
48971
+ title = "Tower Defense",
48972
+ tiles,
48973
+ path,
48974
+ towers,
48975
+ gold,
48976
+ lives,
48977
+ maxWaves,
48978
+ towerCost,
48979
+ scale,
48980
+ placeTowerEvent,
48981
+ startWaveEvent,
48982
+ playAgainEvent,
48983
+ gameEndEvent,
48984
+ className
48985
+ }) {
48986
+ const resolved = entity && typeof entity === "object" && !Array.isArray(entity) ? entity : void 0;
48987
+ return /* @__PURE__ */ jsxs(
48988
+ Box,
48989
+ {
48990
+ display: "flex",
48991
+ fullHeight: true,
48992
+ className: cn("tower-defense-template flex-col", className),
48993
+ children: [
48994
+ /* @__PURE__ */ jsx(
48995
+ HStack,
48996
+ {
48997
+ gap: "sm",
48998
+ align: "center",
48999
+ className: "px-4 py-3 border-b-2 border-border bg-surface shrink-0",
49000
+ children: /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title })
49001
+ }
49002
+ ),
49003
+ /* @__PURE__ */ jsx(Box, { className: "flex-1 relative overflow-hidden", children: /* @__PURE__ */ jsx(
49004
+ TowerDefenseBoard,
49005
+ {
49006
+ entity: resolved,
49007
+ tiles,
49008
+ path,
49009
+ towers,
49010
+ gold,
49011
+ lives,
49012
+ maxWaves,
49013
+ towerCost,
49014
+ scale,
49015
+ placeTowerEvent,
49016
+ startWaveEvent,
49017
+ playAgainEvent,
49018
+ gameEndEvent,
49019
+ className: "h-full"
49020
+ }
49021
+ ) })
49022
+ ]
49023
+ }
49024
+ );
49025
+ }
49026
+ var init_TowerDefenseTemplate = __esm({
49027
+ "components/game/templates/TowerDefenseTemplate.tsx"() {
49028
+ init_cn();
49029
+ init_Box();
49030
+ init_Stack();
49031
+ init_Typography();
49032
+ init_TowerDefenseBoard();
49033
+ TowerDefenseTemplate.displayName = "TowerDefenseTemplate";
49034
+ }
49035
+ });
46494
49036
  function useBattleState(initialUnits, eventConfig = {}, callbacks = {}) {
46495
49037
  const eventBus = useEventBus();
46496
49038
  const {
@@ -46689,17 +49231,12 @@ var init_UncontrolledBattleBoard = __esm({
46689
49231
  }
46690
49232
  });
46691
49233
  function heroPosition(h) {
49234
+ if ("position" in h && h.position != null) return h.position;
46692
49235
  return vec2(h.position);
46693
49236
  }
46694
- function heroOwner(h) {
46695
- return str(h.owner);
46696
- }
46697
49237
  function heroMovement(h) {
46698
49238
  return num(h.movement);
46699
49239
  }
46700
- function hexPassable(h) {
46701
- return h.passable !== false;
46702
- }
46703
49240
  function defaultIsInRange(from, to, range) {
46704
49241
  return Math.abs(from.x - to.x) + Math.abs(from.y - to.y) <= range;
46705
49242
  }
@@ -46735,38 +49272,41 @@ function WorldMapBoard({
46735
49272
  }) {
46736
49273
  const eventBus = useEventBus();
46737
49274
  const resolved = boardEntity(entity);
46738
- const hexes = rows(resolved?.hexes);
46739
- const heroes = rows(resolved?.heroes);
49275
+ const entityUnits = rows(resolved?.units);
49276
+ const entityTiles = rows(resolved?.tiles);
46740
49277
  const features = propFeatures ?? (Array.isArray(resolved?.features) ? resolved.features : []);
46741
49278
  const selectedHeroId = resolved?.selectedHeroId ?? null;
46742
49279
  const assetManifest = propAssetManifest ?? resolved?.assetManifest;
46743
49280
  const backgroundImage = resolved?.backgroundImage;
46744
49281
  const [hoveredTile, setHoveredTile] = useState(null);
46745
- const selectedHero = useMemo(
46746
- () => heroes.find((h) => str(h.id) === selectedHeroId) ?? null,
46747
- [heroes, selectedHeroId]
46748
- );
46749
49282
  const derivedTiles = useMemo(
46750
- () => hexes.map((hex) => ({
46751
- x: num(hex.x),
46752
- y: num(hex.y),
46753
- terrain: str(hex.terrain),
46754
- terrainSprite: hex.terrainSprite == null ? void 0 : str(hex.terrainSprite)
49283
+ () => entityTiles.map((t) => ({
49284
+ x: num(t.x),
49285
+ y: num(t.y),
49286
+ terrain: str(t.terrain),
49287
+ terrainSprite: t.terrainSprite == null ? void 0 : str(t.terrainSprite),
49288
+ passable: t.passable !== false
46755
49289
  })),
46756
- [hexes]
49290
+ [entityTiles]
46757
49291
  );
46758
49292
  const tiles = propTiles ?? derivedTiles;
46759
49293
  const baseUnits = useMemo(
46760
- () => propUnits ?? heroes.map((hero) => ({
46761
- id: str(hero.id),
46762
- position: heroPosition(hero),
46763
- name: str(hero.name),
46764
- team: heroOwner(hero) === "enemy" ? "enemy" : "player",
46765
- health: 100,
46766
- maxHealth: 100,
46767
- sprite: hero.sprite == null ? void 0 : str(hero.sprite)
49294
+ () => propUnits ?? entityUnits.map((u) => ({
49295
+ id: str(u.id),
49296
+ position: heroPosition(u),
49297
+ name: str(u.name),
49298
+ // lolo uses `team` field (not `owner`)
49299
+ team: str(u.team) === "enemy" ? "enemy" : "player",
49300
+ health: num(u.health) || 100,
49301
+ maxHealth: num(u.maxHealth) || 100,
49302
+ sprite: u.sprite == null ? void 0 : str(u.sprite)
46768
49303
  })),
46769
- [heroes, propUnits]
49304
+ [entityUnits, propUnits]
49305
+ );
49306
+ const gameUnits = baseUnits;
49307
+ const selectedHero = useMemo(
49308
+ () => gameUnits.find((u) => u.id === selectedHeroId) ?? null,
49309
+ [gameUnits, selectedHeroId]
46770
49310
  );
46771
49311
  const MOVE_SPEED_MS_PER_TILE = 300;
46772
49312
  const movementAnimRef = useRef(null);
@@ -46812,51 +49352,50 @@ function WorldMapBoard({
46812
49352
  const validMoves = useMemo(() => {
46813
49353
  if (!selectedHero || heroMovement(selectedHero) <= 0) return [];
46814
49354
  const sp = heroPosition(selectedHero);
46815
- const sOwner = heroOwner(selectedHero);
49355
+ const sTeam = str(selectedHero.team);
46816
49356
  const range = heroMovement(selectedHero);
46817
49357
  const moves = [];
46818
- hexes.forEach((hex) => {
46819
- const hx = num(hex.x);
46820
- const hy = num(hex.y);
46821
- if (!hexPassable(hex)) return;
46822
- if (hx === sp.x && hy === sp.y) return;
46823
- if (!isInRange(sp, { x: hx, y: hy }, range)) return;
46824
- if (heroes.some((h) => {
46825
- const hp = heroPosition(h);
46826
- return hp.x === hx && hp.y === hy && heroOwner(h) === sOwner;
49358
+ tiles.forEach((t) => {
49359
+ const tx = t.x;
49360
+ const ty = t.y;
49361
+ if (t.passable === false) return;
49362
+ if (tx === sp.x && ty === sp.y) return;
49363
+ if (!isInRange(sp, { x: tx, y: ty }, range)) return;
49364
+ if (gameUnits.some((u) => {
49365
+ const up = u.position ?? { x: u.x ?? -1, y: u.y ?? -1 };
49366
+ return up.x === tx && up.y === ty && str(u.team) === sTeam;
46827
49367
  })) return;
46828
- moves.push({ x: hx, y: hy });
49368
+ moves.push({ x: tx, y: ty });
46829
49369
  });
46830
49370
  return moves;
46831
- }, [selectedHero, hexes, heroes, isInRange]);
49371
+ }, [selectedHero, tiles, gameUnits, isInRange]);
46832
49372
  const attackTargets = useMemo(() => {
46833
49373
  if (!selectedHero || heroMovement(selectedHero) <= 0) return [];
46834
49374
  const sp = heroPosition(selectedHero);
46835
- const sOwner = heroOwner(selectedHero);
49375
+ const sTeam = str(selectedHero.team);
46836
49376
  const range = heroMovement(selectedHero);
46837
- return heroes.filter((h) => heroOwner(h) !== sOwner).filter((h) => isInRange(sp, heroPosition(h), range)).map((h) => heroPosition(h));
46838
- }, [selectedHero, heroes, isInRange]);
46839
- const maxY = Math.max(...hexes.map((h) => num(h.y)), 0);
49377
+ 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 });
49378
+ }, [selectedHero, gameUnits, isInRange]);
49379
+ const maxY = Math.max(...tiles.map((t) => t.y), 0);
46840
49380
  const baseOffsetX = (maxY + 1) * (TILE_WIDTH * scale / 2);
46841
49381
  const tileToScreen = useCallback(
46842
49382
  (tx, ty) => isoToScreen(tx, ty, scale, baseOffsetX),
46843
49383
  [scale, baseOffsetX]
46844
49384
  );
46845
49385
  const hoveredHex = useMemo(
46846
- () => hoveredTile ? hexes.find((h) => num(h.x) === hoveredTile.x && num(h.y) === hoveredTile.y) ?? null : null,
46847
- [hoveredTile, hexes]
49386
+ () => hoveredTile ? tiles.find((t) => t.x === hoveredTile.x && t.y === hoveredTile.y) ?? null : null,
49387
+ [hoveredTile, tiles]
46848
49388
  );
46849
49389
  const hoveredHero = useMemo(
46850
- () => hoveredTile ? heroes.find((h) => {
46851
- const hp = heroPosition(h);
46852
- return hp.x === hoveredTile.x && hp.y === hoveredTile.y;
49390
+ () => hoveredTile ? gameUnits.find((u) => {
49391
+ const up = u.position ?? { x: u.x ?? -1, y: u.y ?? -1 };
49392
+ return up.x === hoveredTile.x && up.y === hoveredTile.y;
46853
49393
  }) ?? null : null,
46854
- [hoveredTile, heroes]
49394
+ [hoveredTile, gameUnits]
46855
49395
  );
46856
49396
  const handleTileClick = useCallback((x, y) => {
46857
49397
  if (movementAnimRef.current) return;
46858
- const hex = hexes.find((h) => num(h.x) === x && num(h.y) === y);
46859
- if (!hex) return;
49398
+ const tile = tiles.find((t) => t.x === x && t.y === y);
46860
49399
  if (tileClickEvent) {
46861
49400
  eventBus.emit(`UI:${tileClickEvent}`, { x, y });
46862
49401
  }
@@ -46867,38 +49406,39 @@ function WorldMapBoard({
46867
49406
  if (heroMoveEvent) {
46868
49407
  eventBus.emit(`UI:${heroMoveEvent}`, { heroId, toX: x, toY: y });
46869
49408
  }
46870
- const feature = str(hex.feature);
49409
+ const feature = tile ? str(tile.feature) : "";
46871
49410
  if (feature && feature !== "none") {
46872
- onFeatureEnter?.(heroId, hex);
49411
+ const tileRow = tile;
49412
+ onFeatureEnter?.(heroId, tileRow);
46873
49413
  if (featureEnterEvent) {
46874
- eventBus.emit(`UI:${featureEnterEvent}`, { heroId, feature, hex });
49414
+ eventBus.emit(`UI:${featureEnterEvent}`, { heroId, feature, hex: tileRow });
46875
49415
  }
46876
49416
  }
46877
49417
  });
46878
49418
  return;
46879
49419
  }
46880
- const enemy = heroes.find((h) => {
46881
- const hp = heroPosition(h);
46882
- return hp.x === x && hp.y === y && heroOwner(h) === "enemy";
49420
+ const enemy = gameUnits.find((u) => {
49421
+ const up = u.position ?? { x: u.x ?? -1, y: u.y ?? -1 };
49422
+ return up.x === x && up.y === y && str(u.team) === "enemy";
46883
49423
  });
46884
49424
  if (selectedHero && enemy && attackTargets.some((t) => t.x === x && t.y === y)) {
46885
49425
  const attackerId = str(selectedHero.id);
46886
- const defenderId = str(enemy.id);
49426
+ const defenderId = enemy.id;
46887
49427
  onBattleEncounter?.(attackerId, defenderId);
46888
49428
  if (battleEncounterEvent) {
46889
49429
  eventBus.emit(`UI:${battleEncounterEvent}`, { attackerId, defenderId });
46890
49430
  }
46891
49431
  }
46892
- }, [hexes, heroes, selectedHero, validMoves, attackTargets, startMoveAnimation, onHeroMove, onFeatureEnter, onBattleEncounter, eventBus, tileClickEvent, heroMoveEvent, featureEnterEvent, battleEncounterEvent]);
49432
+ }, [tiles, gameUnits, selectedHero, validMoves, attackTargets, startMoveAnimation, onHeroMove, onFeatureEnter, onBattleEncounter, eventBus, tileClickEvent, heroMoveEvent, featureEnterEvent, battleEncounterEvent]);
46893
49433
  const handleUnitClick = useCallback((unitId) => {
46894
- const hero = heroes.find((h) => str(h.id) === unitId);
46895
- if (hero && (heroOwner(hero) === "player" || allowMoveAllHeroes)) {
49434
+ const unit = gameUnits.find((u) => u.id === unitId);
49435
+ if (unit && (str(unit.team) === "player" || allowMoveAllHeroes)) {
46896
49436
  onHeroSelect?.(unitId);
46897
49437
  if (heroSelectEvent) {
46898
49438
  eventBus.emit(`UI:${heroSelectEvent}`, { heroId: unitId });
46899
49439
  }
46900
49440
  }
46901
- }, [heroes, onHeroSelect, allowMoveAllHeroes, eventBus, heroSelectEvent]);
49441
+ }, [gameUnits, onHeroSelect, allowMoveAllHeroes, eventBus, heroSelectEvent]);
46902
49442
  const selectHero = useCallback((id) => {
46903
49443
  onHeroSelect?.(id);
46904
49444
  if (heroSelectEvent) {
@@ -47002,48 +49542,48 @@ function WorldMapTemplate({
47002
49542
  }
47003
49543
  );
47004
49544
  }
47005
- var CDN3, DEFAULT_WORLDMAP_TILES, DEFAULT_WORLDMAP_UNITS, DEFAULT_WORLDMAP_FEATURES, DEFAULT_WORLDMAP_MANIFEST;
49545
+ var CDN7, DEFAULT_WORLDMAP_TILES, DEFAULT_WORLDMAP_UNITS, DEFAULT_WORLDMAP_FEATURES, DEFAULT_WORLDMAP_MANIFEST;
47006
49546
  var init_WorldMapTemplate = __esm({
47007
49547
  "components/game/templates/WorldMapTemplate.tsx"() {
47008
49548
  init_WorldMapBoard();
47009
- CDN3 = "https://almadar-kflow-assets.web.app/shared";
49549
+ CDN7 = "https://almadar-kflow-assets.web.app/shared";
47010
49550
  DEFAULT_WORLDMAP_TILES = [
47011
- { x: 0, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47012
- { x: 1, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47013
- { x: 2, y: 0, terrain: "water", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
47014
- { x: 3, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47015
- { x: 4, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47016
- { x: 0, y: 1, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47017
- { x: 1, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47018
- { x: 2, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47019
- { x: 3, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47020
- { x: 4, y: 1, terrain: "water", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
47021
- { x: 0, y: 2, terrain: "water", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
47022
- { x: 1, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47023
- { x: 2, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
47024
- { x: 3, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47025
- { x: 4, y: 2, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47026
- { x: 0, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47027
- { x: 1, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47028
- { x: 2, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47029
- { x: 3, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
47030
- { x: 4, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47031
- { x: 0, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47032
- { x: 1, y: 4, terrain: "water", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
47033
- { x: 2, y: 4, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
47034
- { x: 3, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
47035
- { x: 4, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` }
49551
+ { x: 0, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49552
+ { x: 1, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49553
+ { x: 2, y: 0, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
49554
+ { x: 3, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49555
+ { x: 4, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49556
+ { x: 0, y: 1, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49557
+ { x: 1, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49558
+ { x: 2, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49559
+ { x: 3, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49560
+ { x: 4, y: 1, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
49561
+ { x: 0, y: 2, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
49562
+ { x: 1, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49563
+ { x: 2, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
49564
+ { x: 3, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49565
+ { x: 4, y: 2, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49566
+ { x: 0, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49567
+ { x: 1, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49568
+ { x: 2, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49569
+ { x: 3, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
49570
+ { x: 4, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49571
+ { x: 0, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49572
+ { x: 1, y: 4, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
49573
+ { x: 2, y: 4, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49574
+ { x: 3, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49575
+ { x: 4, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` }
47036
49576
  ];
47037
49577
  DEFAULT_WORLDMAP_UNITS = [
47038
- { 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` },
47039
- { 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` }
49578
+ { 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` },
49579
+ { 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` }
47040
49580
  ];
47041
49581
  DEFAULT_WORLDMAP_FEATURES = [
47042
- { id: "f1", x: 2, y: 2, type: "capital", sprite: `${CDN3}/scenes/world/capital.png` },
47043
- { id: "f2", x: 4, y: 2, type: "power_node", sprite: `${CDN3}/world-map/power_node.png` }
49582
+ { id: "f1", x: 2, y: 2, type: "capital", sprite: `${CDN7}/scenes/world/capital.png` },
49583
+ { id: "f2", x: 4, y: 2, type: "power_node", sprite: `${CDN7}/world-map/power_node.png` }
47044
49584
  ];
47045
49585
  DEFAULT_WORLDMAP_MANIFEST = {
47046
- baseUrl: CDN3,
49586
+ baseUrl: CDN7,
47047
49587
  terrains: {
47048
49588
  grass: "/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png",
47049
49589
  water: "/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png",
@@ -47088,7 +49628,7 @@ function lazyThree(name, loader) {
47088
49628
  ThreeWrapper.displayName = `Lazy(${name})`;
47089
49629
  return ThreeWrapper;
47090
49630
  }
47091
- var ThreeBoundary, FeatureRenderer, GameCanvas3D, GameCanvas3DBattleTemplate, GameCanvas3DCastleTemplate, GameCanvas3DWorldMapTemplate, COMPONENT_REGISTRY;
49631
+ var ThreeBoundary, FeatureRenderer, GameCanvas3D2, GameCanvas3DBattleTemplate, GameCanvas3DCastleTemplate, GameCanvas3DWorldMapTemplate, COMPONENT_REGISTRY;
47092
49632
  var init_component_registry_generated = __esm({
47093
49633
  "components/core/organisms/component-registry.generated.ts"() {
47094
49634
  init_AboutPageTemplate();
@@ -47198,6 +49738,7 @@ var init_component_registry_generated = __esm({
47198
49738
  init_FormSectionHeader();
47199
49739
  init_GameAudioProvider();
47200
49740
  init_GameAudioToggle();
49741
+ init_GameBoard3D();
47201
49742
  init_GameCanvas2D();
47202
49743
  init_GameHud();
47203
49744
  init_GameMenu();
@@ -47255,7 +49796,9 @@ var init_component_registry_generated = __esm({
47255
49796
  init_PageHeader();
47256
49797
  init_Pagination();
47257
49798
  init_PatternTile();
49799
+ init_PlatformerBoard();
47258
49800
  init_PlatformerCanvas();
49801
+ init_PlatformerTemplate();
47259
49802
  init_Popover();
47260
49803
  init_PositionedCanvas();
47261
49804
  init_PowerupSlots();
@@ -47279,6 +49822,8 @@ var init_component_registry_generated = __esm({
47279
49822
  init_ResourceBar();
47280
49823
  init_ResourceCounter();
47281
49824
  init_RichBlockEditor();
49825
+ init_RoguelikeBoard();
49826
+ init_RoguelikeTemplate();
47282
49827
  init_RuntimeDebugger2();
47283
49828
  init_ScaledDiagram();
47284
49829
  init_ScoreBoard();
@@ -47351,6 +49896,8 @@ var init_component_registry_generated = __esm({
47351
49896
  init_Toast();
47352
49897
  init_ToastSlot();
47353
49898
  init_Tooltip();
49899
+ init_TowerDefenseBoard();
49900
+ init_TowerDefenseTemplate();
47354
49901
  init_TraitFrame();
47355
49902
  init_TraitSlot();
47356
49903
  init_TrendIndicator();
@@ -47395,7 +49942,7 @@ var init_component_registry_generated = __esm({
47395
49942
  }
47396
49943
  };
47397
49944
  FeatureRenderer = lazyThree("FeatureRenderer", () => import('@almadar/ui/components/molecules/game/three'));
47398
- GameCanvas3D = lazyThree("GameCanvas3D", () => import('@almadar/ui/components/molecules/game/three'));
49945
+ GameCanvas3D2 = lazyThree("GameCanvas3D", () => import('@almadar/ui/components/molecules/game/three'));
47399
49946
  GameCanvas3DBattleTemplate = lazyThree("GameCanvas3DBattleTemplate", () => import('@almadar/ui/components/molecules/game/three'));
47400
49947
  GameCanvas3DCastleTemplate = lazyThree("GameCanvas3DCastleTemplate", () => import('@almadar/ui/components/molecules/game/three'));
47401
49948
  GameCanvas3DWorldMapTemplate = lazyThree("GameCanvas3DWorldMapTemplate", () => import('@almadar/ui/components/molecules/game/three'));
@@ -47514,8 +50061,9 @@ var init_component_registry_generated = __esm({
47514
50061
  "FormSectionHeader": FormSectionHeader,
47515
50062
  "GameAudioProvider": GameAudioProvider,
47516
50063
  "GameAudioToggle": GameAudioToggle,
50064
+ "GameBoard3D": GameBoard3D,
47517
50065
  "GameCanvas2D": GameCanvas2D,
47518
- "GameCanvas3D": GameCanvas3D,
50066
+ "GameCanvas3D": GameCanvas3D2,
47519
50067
  "GameCanvas3DBattleTemplate": GameCanvas3DBattleTemplate,
47520
50068
  "GameCanvas3DCastleTemplate": GameCanvas3DCastleTemplate,
47521
50069
  "GameCanvas3DWorldMapTemplate": GameCanvas3DWorldMapTemplate,
@@ -47577,7 +50125,9 @@ var init_component_registry_generated = __esm({
47577
50125
  "PageHeader": PageHeader,
47578
50126
  "Pagination": Pagination,
47579
50127
  "PatternTile": PatternTile,
50128
+ "PlatformerBoard": PlatformerBoard,
47580
50129
  "PlatformerCanvas": PlatformerCanvas,
50130
+ "PlatformerTemplate": PlatformerTemplate,
47581
50131
  "Popover": Popover,
47582
50132
  "PositionedCanvas": PositionedCanvas,
47583
50133
  "PowerupSlots": PowerupSlots,
@@ -47601,6 +50151,8 @@ var init_component_registry_generated = __esm({
47601
50151
  "ResourceBar": ResourceBar,
47602
50152
  "ResourceCounter": ResourceCounter,
47603
50153
  "RichBlockEditor": RichBlockEditor,
50154
+ "RoguelikeBoard": RoguelikeBoard,
50155
+ "RoguelikeTemplate": RoguelikeTemplate,
47604
50156
  "RuntimeDebugger": RuntimeDebugger,
47605
50157
  "ScaledDiagram": ScaledDiagram,
47606
50158
  "ScoreBoard": ScoreBoard,
@@ -47676,6 +50228,8 @@ var init_component_registry_generated = __esm({
47676
50228
  "Toast": Toast,
47677
50229
  "ToastSlot": ToastSlot,
47678
50230
  "Tooltip": Tooltip,
50231
+ "TowerDefenseBoard": TowerDefenseBoard,
50232
+ "TowerDefenseTemplate": TowerDefenseTemplate,
47679
50233
  "TraitFrame": TraitFrame,
47680
50234
  "TraitSlot": TraitSlot,
47681
50235
  "TrendIndicator": TrendIndicator,
@@ -49572,7 +52126,7 @@ function clearSchemaCache() {
49572
52126
 
49573
52127
  // runtime/index.ts
49574
52128
  init_EntitySchemaContext();
49575
- var log10 = createLogger("almadar:ui:trait-provider");
52129
+ var log12 = createLogger("almadar:ui:trait-provider");
49576
52130
  var TraitContext = createContext(null);
49577
52131
  function TraitProvider({
49578
52132
  traits: traitBindings,
@@ -49589,7 +52143,7 @@ function TraitProvider({
49589
52143
  currentState: stateName,
49590
52144
  availableEvents: trait.transitions.filter((t) => t.from === stateName).map((t) => t.event),
49591
52145
  dispatch: (eventKey, payload) => {
49592
- log10.debug("Dispatch", () => ({
52146
+ log12.debug("Dispatch", () => ({
49593
52147
  trait: trait.name,
49594
52148
  event: eventKey,
49595
52149
  payloadKeys: payload ? Object.keys(payload) : []
@@ -50173,7 +52727,7 @@ OrbitalThemeProvider.displayName = "OrbitalThemeProvider";
50173
52727
  init_navigation();
50174
52728
  init_verificationRegistry();
50175
52729
  var PERF_NAMESPACE = "almadar:perf:canvas";
50176
- var log11 = createLogger(PERF_NAMESPACE);
52730
+ var log13 = createLogger(PERF_NAMESPACE);
50177
52731
  var RING_SIZE = 50;
50178
52732
  var ring = [];
50179
52733
  var writeIdx = 0;
@@ -50228,7 +52782,7 @@ function perfEnd(name, startToken, detail) {
50228
52782
  }
50229
52783
  }
50230
52784
  push({ name, durationMs, ts: endTs, detail });
50231
- log11.debug(name, () => ({ durationMs, ...detail ?? {} }));
52785
+ log13.debug(name, () => ({ durationMs, ...detail ?? {} }));
50232
52786
  }
50233
52787
  function perfTime(name, fn, detail) {
50234
52788
  const t = perfStart(name);
@@ -50246,7 +52800,7 @@ var profilerOnRender = (id, phase, actualDuration, baseDuration, _startTime, com
50246
52800
  ts: commitTime,
50247
52801
  detail: { baseDuration }
50248
52802
  });
50249
- log11.debug(`profiler:${id}:${phase}`, () => ({ actualDuration, baseDuration }));
52803
+ log13.debug(`profiler:${id}:${phase}`, () => ({ actualDuration, baseDuration }));
50250
52804
  };
50251
52805
  function getPerfSnapshot() {
50252
52806
  if (ring.length < RING_SIZE) return ring.slice();