@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
package/dist/avl/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
2
  import * as React91 from 'react';
3
- import React91__default, { createContext, useState, useMemo, useRef, useEffect, useContext, useCallback, Suspense, useLayoutEffect, Profiler, useReducer, useSyncExternalStore, lazy, useId } from 'react';
3
+ import React91__default, { createContext, useState, useMemo, useRef, useEffect, useContext, useCallback, Suspense, useLayoutEffect, Profiler, useReducer, useSyncExternalStore, lazy, useId, forwardRef, useImperativeHandle, Component } from 'react';
4
4
  import { OrbitalProvider, EventBusContext, useTraitScope, VerificationProvider, TraitScopeProvider } from '@almadar/ui/providers';
5
5
  import { createLogger, isLogLevelEnabled } from '@almadar/logger';
6
6
  import ELK from 'elkjs/lib/elk.bundled.js';
@@ -39,6 +39,12 @@ import { FieldTypeSchema, isInlineTrait, isPageReference, isEntityCall, schemaTo
39
39
  import { useDroppable, useDraggable, DndContext, DragOverlay, useSensors, useSensor, PointerSensor, KeyboardSensor, pointerWithin, rectIntersection, closestCorners } from '@dnd-kit/core';
40
40
  import { sortableKeyboardCoordinates, useSortable, arrayMove, SortableContext, rectSortingStrategy, verticalListSortingStrategy } from '@dnd-kit/sortable';
41
41
  import { CSS } from '@dnd-kit/utilities';
42
+ import * as THREE from 'three';
43
+ import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
44
+ import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
45
+ import { GLTFLoader as GLTFLoader$1 } from 'three/examples/jsm/loaders/GLTFLoader';
46
+ import { Canvas, useThree } from '@react-three/fiber';
47
+ import { Grid as Grid$1, OrbitControls } from '@react-three/drei';
42
48
  import { getPatternDefinition, getComponentForPattern as getComponentForPattern$1, isEntityAwarePattern } from '@almadar/patterns';
43
49
  import { OrbitalServerRuntime } from '@almadar/runtime/OrbitalServerRuntime';
44
50
  import { InMemoryPersistence, StateMachineManager, createContextFromBindings, interpolateValue, collectDeclaredConfigDefaults, createServerEffectHandlers, EffectExecutor } from '@almadar/runtime';
@@ -3173,8 +3179,8 @@ var init_Typography = __esm({
3173
3179
  children
3174
3180
  }) => {
3175
3181
  const variant = variantProp ?? (level ? `h${level}` : "body1");
3176
- const Component = as || defaultElements[variant];
3177
- const Comp = Component;
3182
+ const Component2 = as || defaultElements[variant];
3183
+ const Comp = Component2;
3178
3184
  return /* @__PURE__ */ jsx(
3179
3185
  Comp,
3180
3186
  {
@@ -3483,7 +3489,7 @@ var init_Box = __esm({
3483
3489
  position,
3484
3490
  className,
3485
3491
  children,
3486
- as: Component = "div",
3492
+ as: Component2 = "div",
3487
3493
  action,
3488
3494
  actionPayload,
3489
3495
  hoverEvent,
@@ -3515,7 +3521,7 @@ var init_Box = __esm({
3515
3521
  }, [hoverEvent, eventBus, onMouseLeave]);
3516
3522
  const isClickable = action || onClick;
3517
3523
  return React91__default.createElement(
3518
- Component,
3524
+ Component2,
3519
3525
  {
3520
3526
  ref,
3521
3527
  className: cn(
@@ -3591,7 +3597,7 @@ var init_Stack = __esm({
3591
3597
  className,
3592
3598
  style,
3593
3599
  children,
3594
- as: Component = "div",
3600
+ as: Component2 = "div",
3595
3601
  onClick,
3596
3602
  onKeyDown,
3597
3603
  role,
@@ -3609,7 +3615,7 @@ var init_Stack = __esm({
3609
3615
  };
3610
3616
  const isHorizontal = direction === "horizontal";
3611
3617
  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";
3612
- const Comp = Component;
3618
+ const Comp = Component2;
3613
3619
  return /* @__PURE__ */ jsx(
3614
3620
  Comp,
3615
3621
  {
@@ -4523,9 +4529,9 @@ function resolvePhosphor(name, weight, family) {
4523
4529
  (lib) => {
4524
4530
  const PhosphorComp = lib[target];
4525
4531
  if (!PhosphorComp || typeof PhosphorComp !== "object") return null;
4526
- const Component = PhosphorComp;
4532
+ const Component2 = PhosphorComp;
4527
4533
  const Adapter = (props) => /* @__PURE__ */ jsx(
4528
- Component,
4534
+ Component2,
4529
4535
  {
4530
4536
  weight,
4531
4537
  className: props.className,
@@ -4549,9 +4555,9 @@ function resolveTabler(name, family) {
4549
4555
  (lib) => {
4550
4556
  const TablerComp = lib[target];
4551
4557
  if (!TablerComp || typeof TablerComp !== "object") return null;
4552
- const Component = TablerComp;
4558
+ const Component2 = TablerComp;
4553
4559
  const Adapter = (props) => /* @__PURE__ */ jsx(
4554
- Component,
4560
+ Component2,
4555
4561
  {
4556
4562
  stroke: props.strokeWidth ?? 1.5,
4557
4563
  className: props.className,
@@ -4575,9 +4581,9 @@ function resolveFa(name, family) {
4575
4581
  (lib) => {
4576
4582
  const FaComp = lib[target];
4577
4583
  if (!FaComp || typeof FaComp !== "function") return null;
4578
- const Component = FaComp;
4584
+ const Component2 = FaComp;
4579
4585
  const Adapter = (props) => /* @__PURE__ */ jsx(
4580
- Component,
4586
+ Component2,
4581
4587
  {
4582
4588
  className: props.className,
4583
4589
  style: props.style,
@@ -7969,10 +7975,10 @@ var init_Center = __esm({
7969
7975
  className,
7970
7976
  style,
7971
7977
  children,
7972
- as: Component = "div"
7978
+ as: Component2 = "div"
7973
7979
  }) => {
7974
7980
  const mergedStyle = minHeight ? { minHeight, ...style } : style;
7975
- const Comp = Component;
7981
+ const Comp = Component2;
7976
7982
  return /* @__PURE__ */ jsx(
7977
7983
  Comp,
7978
7984
  {
@@ -10766,6 +10772,7 @@ var init_HealthBar = __esm({
10766
10772
  }
10767
10773
  });
10768
10774
  function ScoreDisplay({
10775
+ assetUrl = DEFAULT_ASSET_URL,
10769
10776
  value,
10770
10777
  label,
10771
10778
  icon,
@@ -10812,14 +10819,24 @@ function ScoreDisplay({
10812
10819
  className
10813
10820
  ),
10814
10821
  children: [
10815
- icon && /* @__PURE__ */ jsx("span", { className: "flex-shrink-0", children: typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon }) : /* @__PURE__ */ jsx(Icon, { icon }) }),
10822
+ assetUrl ? /* @__PURE__ */ jsx(
10823
+ "img",
10824
+ {
10825
+ src: assetUrl,
10826
+ alt: "",
10827
+ width: 20,
10828
+ height: 20,
10829
+ style: { imageRendering: "pixelated", objectFit: "contain" },
10830
+ className: "flex-shrink-0"
10831
+ }
10832
+ ) : icon ? /* @__PURE__ */ jsx("span", { className: "flex-shrink-0", children: typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon }) : /* @__PURE__ */ jsx(Icon, { icon }) }) : null,
10816
10833
  label && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: label }),
10817
10834
  /* @__PURE__ */ jsx("span", { className: "tabular-nums", children: formattedValue })
10818
10835
  ]
10819
10836
  }
10820
10837
  );
10821
10838
  }
10822
- var sizeMap2;
10839
+ var sizeMap2, DEFAULT_ASSET_URL;
10823
10840
  var init_ScoreDisplay = __esm({
10824
10841
  "components/game/atoms/ScoreDisplay.tsx"() {
10825
10842
  "use client";
@@ -10831,10 +10848,12 @@ var init_ScoreDisplay = __esm({
10831
10848
  lg: "text-2xl",
10832
10849
  xl: "text-4xl"
10833
10850
  };
10851
+ DEFAULT_ASSET_URL = "https://almadar-kflow-assets.web.app/shared/effects/particles/star_01.png";
10834
10852
  ScoreDisplay.displayName = "ScoreDisplay";
10835
10853
  }
10836
10854
  });
10837
10855
  function ControlButton({
10856
+ assetUrl = DEFAULT_ASSET_URL2,
10838
10857
  label,
10839
10858
  icon,
10840
10859
  size = "md",
@@ -10881,7 +10900,7 @@ function ControlButton({
10881
10900
  },
10882
10901
  [isPressed, releaseEvent, eventBus, onRelease]
10883
10902
  );
10884
- return /* @__PURE__ */ jsxs(
10903
+ return /* @__PURE__ */ jsx(
10885
10904
  "button",
10886
10905
  {
10887
10906
  type: "button",
@@ -10902,20 +10921,27 @@ function ControlButton({
10902
10921
  disabled && "opacity-50 cursor-not-allowed",
10903
10922
  className
10904
10923
  ),
10905
- children: [
10906
- icon && /* @__PURE__ */ jsx("span", { className: "text-2xl", children: typeof icon === "string" ? /^[a-zA-Z0-9-]+$/.test(icon) ? (() => {
10907
- const I = resolveIcon(icon);
10908
- return I ? /* @__PURE__ */ jsx(I, { className: "w-6 h-6" }) : null;
10909
- })() : icon : /* @__PURE__ */ (() => {
10910
- const I = icon;
10911
- return /* @__PURE__ */ jsx(I, { className: "w-6 h-6" });
10912
- })() }),
10913
- label && !icon && /* @__PURE__ */ jsx("span", { children: label })
10914
- ]
10924
+ children: assetUrl ? /* @__PURE__ */ jsx(
10925
+ "img",
10926
+ {
10927
+ src: assetUrl,
10928
+ alt: "",
10929
+ width: 24,
10930
+ height: 24,
10931
+ style: { imageRendering: "pixelated", objectFit: "contain" },
10932
+ className: "flex-shrink-0"
10933
+ }
10934
+ ) : icon ? /* @__PURE__ */ jsx("span", { className: "text-2xl", children: typeof icon === "string" ? /^[a-zA-Z0-9-]+$/.test(icon) ? (() => {
10935
+ const I = resolveIcon(icon);
10936
+ return I ? /* @__PURE__ */ jsx(I, { className: "w-6 h-6" }) : null;
10937
+ })() : icon : /* @__PURE__ */ (() => {
10938
+ const I = icon;
10939
+ return /* @__PURE__ */ jsx(I, { className: "w-6 h-6" });
10940
+ })() }) : label ? /* @__PURE__ */ jsx("span", { children: label }) : null
10915
10941
  }
10916
10942
  );
10917
10943
  }
10918
- var sizeMap3, shapeMap, variantMap;
10944
+ var sizeMap3, shapeMap, variantMap, DEFAULT_ASSET_URL2;
10919
10945
  var init_ControlButton = __esm({
10920
10946
  "components/game/atoms/ControlButton.tsx"() {
10921
10947
  "use client";
@@ -10938,6 +10964,7 @@ var init_ControlButton = __esm({
10938
10964
  secondary: "bg-secondary text-secondary-foreground border-border hover:bg-secondary-hover",
10939
10965
  ghost: "bg-transparent text-foreground border-border hover:bg-muted"
10940
10966
  };
10967
+ DEFAULT_ASSET_URL2 = "https://almadar-kflow-assets.web.app/shared/effects/particles/circle_01.png";
10941
10968
  ControlButton.displayName = "ControlButton";
10942
10969
  }
10943
10970
  });
@@ -11017,6 +11044,7 @@ var init_Sprite = __esm({
11017
11044
  }
11018
11045
  });
11019
11046
  function StateIndicator({
11047
+ assetUrl = DEFAULT_ASSET_URL3,
11020
11048
  state = "idle",
11021
11049
  label,
11022
11050
  size = "md",
@@ -11039,18 +11067,29 @@ function StateIndicator({
11039
11067
  className
11040
11068
  ),
11041
11069
  children: [
11042
- /* @__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 }) }),
11070
+ /* @__PURE__ */ jsx(Box, { as: "span", children: assetUrl ? /* @__PURE__ */ jsx(
11071
+ "img",
11072
+ {
11073
+ src: assetUrl,
11074
+ alt: displayLabel,
11075
+ width: 16,
11076
+ height: 16,
11077
+ style: { imageRendering: "pixelated", objectFit: "contain" },
11078
+ className: "flex-shrink-0"
11079
+ }
11080
+ ) : 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 }) }),
11043
11081
  /* @__PURE__ */ jsx(Box, { as: "span", children: displayLabel })
11044
11082
  ]
11045
11083
  }
11046
11084
  );
11047
11085
  }
11048
- var DEFAULT_STATE_STYLES, DEFAULT_STYLE, SIZE_CLASSES;
11086
+ var DEFAULT_ASSET_URL3, DEFAULT_STATE_STYLES, DEFAULT_STYLE, SIZE_CLASSES;
11049
11087
  var init_StateIndicator = __esm({
11050
11088
  "components/game/atoms/StateIndicator.tsx"() {
11051
11089
  init_Box();
11052
11090
  init_Icon();
11053
11091
  init_cn();
11092
+ DEFAULT_ASSET_URL3 = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
11054
11093
  DEFAULT_STATE_STYLES = {
11055
11094
  idle: { icon: "\u23F8", bgClass: "bg-muted" },
11056
11095
  active: { icon: "\u25B6", bgClass: "bg-success" },
@@ -11125,6 +11164,7 @@ var init_TimerDisplay = __esm({
11125
11164
  }
11126
11165
  });
11127
11166
  function ResourceCounter({
11167
+ assetUrl = DEFAULT_ASSET_URL4,
11128
11168
  icon,
11129
11169
  label = "Gold",
11130
11170
  value = 250,
@@ -11144,7 +11184,17 @@ function ResourceCounter({
11144
11184
  className
11145
11185
  ),
11146
11186
  children: [
11147
- icon && /* @__PURE__ */ jsx("span", { className: cn("flex-shrink-0", sizes.icon), children: typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon }) : /* @__PURE__ */ jsx(Icon, { icon }) }),
11187
+ assetUrl ? /* @__PURE__ */ jsx(
11188
+ "img",
11189
+ {
11190
+ src: assetUrl,
11191
+ alt: label,
11192
+ width: sizes.img,
11193
+ height: sizes.img,
11194
+ style: { imageRendering: "pixelated", objectFit: "contain" },
11195
+ className: "flex-shrink-0"
11196
+ }
11197
+ ) : 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,
11148
11198
  /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: label }),
11149
11199
  /* @__PURE__ */ jsxs("span", { className: cn("font-bold tabular-nums", color && (color in colorTokenClasses2 ? colorTokenClasses2[color] : color)), children: [
11150
11200
  value,
@@ -11157,7 +11207,7 @@ function ResourceCounter({
11157
11207
  }
11158
11208
  );
11159
11209
  }
11160
- var colorTokenClasses2, sizeMap5;
11210
+ var colorTokenClasses2, DEFAULT_ASSET_URL4, sizeMap5;
11161
11211
  var init_ResourceCounter = __esm({
11162
11212
  "components/game/atoms/ResourceCounter.tsx"() {
11163
11213
  init_cn();
@@ -11170,16 +11220,17 @@ var init_ResourceCounter = __esm({
11170
11220
  error: "text-error",
11171
11221
  muted: "text-muted-foreground"
11172
11222
  };
11223
+ DEFAULT_ASSET_URL4 = "https://almadar-kflow-assets.web.app/shared/world-map/gold_mine.png";
11173
11224
  sizeMap5 = {
11174
- sm: { wrapper: "text-xs gap-1 px-1.5 py-0.5", icon: "text-sm" },
11175
- md: { wrapper: "text-sm gap-1.5 px-2 py-1", icon: "text-base" },
11176
- lg: { wrapper: "text-base gap-2 px-3 py-1.5", icon: "text-lg" }
11225
+ sm: { wrapper: "text-xs gap-1 px-1.5 py-0.5", icon: "text-sm", img: 16 },
11226
+ md: { wrapper: "text-sm gap-1.5 px-2 py-1", icon: "text-base", img: 20 },
11227
+ lg: { wrapper: "text-base gap-2 px-3 py-1.5", icon: "text-lg", img: 28 }
11177
11228
  };
11178
11229
  ResourceCounter.displayName = "ResourceCounter";
11179
11230
  }
11180
11231
  });
11181
11232
  function ItemSlot({
11182
- assetUrl = DEFAULT_ASSET_URL,
11233
+ assetUrl = DEFAULT_ASSET_URL5,
11183
11234
  icon = "sword",
11184
11235
  label = "Iron Sword",
11185
11236
  quantity,
@@ -11238,7 +11289,7 @@ function ItemSlot({
11238
11289
  }
11239
11290
  );
11240
11291
  }
11241
- var sizeMap6, rarityBorderMap, rarityGlowMap, DEFAULT_ASSET_URL, assetSizeMap;
11292
+ var sizeMap6, rarityBorderMap, rarityGlowMap, DEFAULT_ASSET_URL5, assetSizeMap;
11242
11293
  var init_ItemSlot = __esm({
11243
11294
  "components/game/atoms/ItemSlot.tsx"() {
11244
11295
  "use client";
@@ -11263,7 +11314,7 @@ var init_ItemSlot = __esm({
11263
11314
  epic: "shadow-lg",
11264
11315
  legendary: "shadow-lg"
11265
11316
  };
11266
- DEFAULT_ASSET_URL = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
11317
+ DEFAULT_ASSET_URL5 = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
11267
11318
  assetSizeMap = {
11268
11319
  sm: 28,
11269
11320
  md: 40,
@@ -11273,6 +11324,7 @@ var init_ItemSlot = __esm({
11273
11324
  }
11274
11325
  });
11275
11326
  function TurnIndicator({
11327
+ assetUrl = DEFAULT_ASSET_URL6,
11276
11328
  currentTurn = 1,
11277
11329
  maxTurns,
11278
11330
  activeTeam,
@@ -11305,14 +11357,24 @@ function TurnIndicator({
11305
11357
  ] }),
11306
11358
  activeTeam && /* @__PURE__ */ jsxs(Fragment, { children: [
11307
11359
  /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "|" }),
11308
- /* @__PURE__ */ jsx("span", { className: cn("rounded-full bg-success/20", sizes.dot) }),
11360
+ assetUrl ? /* @__PURE__ */ jsx(
11361
+ "img",
11362
+ {
11363
+ src: assetUrl,
11364
+ alt: "",
11365
+ width: 12,
11366
+ height: 12,
11367
+ style: { imageRendering: "pixelated", objectFit: "contain" },
11368
+ className: "flex-shrink-0"
11369
+ }
11370
+ ) : /* @__PURE__ */ jsx("span", { className: cn("rounded-full bg-success/20", sizes.dot) }),
11309
11371
  /* @__PURE__ */ jsx("span", { className: "text-success", children: activeTeam })
11310
11372
  ] })
11311
11373
  ]
11312
11374
  }
11313
11375
  );
11314
11376
  }
11315
- var sizeMap7;
11377
+ var sizeMap7, DEFAULT_ASSET_URL6;
11316
11378
  var init_TurnIndicator = __esm({
11317
11379
  "components/game/atoms/TurnIndicator.tsx"() {
11318
11380
  init_cn();
@@ -11321,6 +11383,7 @@ var init_TurnIndicator = __esm({
11321
11383
  md: { wrapper: "text-sm gap-2 px-3 py-1", dot: "w-2 h-2" },
11322
11384
  lg: { wrapper: "text-base gap-2.5 px-4 py-1.5", dot: "w-2.5 h-2.5" }
11323
11385
  };
11386
+ DEFAULT_ASSET_URL6 = "https://almadar-kflow-assets.web.app/shared/effects/particles/symbol_01.png";
11324
11387
  TurnIndicator.displayName = "TurnIndicator";
11325
11388
  }
11326
11389
  });
@@ -11336,6 +11399,7 @@ function getComboScale(combo) {
11336
11399
  return "";
11337
11400
  }
11338
11401
  function ComboCounter({
11402
+ assetUrl = DEFAULT_ASSET_URL7,
11339
11403
  combo = 5,
11340
11404
  multiplier,
11341
11405
  streak,
@@ -11355,6 +11419,17 @@ function ComboCounter({
11355
11419
  className
11356
11420
  ),
11357
11421
  children: [
11422
+ assetUrl && /* @__PURE__ */ jsx(
11423
+ "img",
11424
+ {
11425
+ src: assetUrl,
11426
+ alt: "combo",
11427
+ width: 24,
11428
+ height: 24,
11429
+ style: { imageRendering: "pixelated", objectFit: "contain" },
11430
+ className: "flex-shrink-0 mb-0.5"
11431
+ }
11432
+ ),
11358
11433
  /* @__PURE__ */ jsx("span", { className: cn("font-black tabular-nums leading-none", sizes.combo, getComboIntensity(combo)), children: combo }),
11359
11434
  /* @__PURE__ */ jsx("span", { className: cn("font-bold uppercase tracking-wider text-muted-foreground", sizes.label), children: "combo" }),
11360
11435
  multiplier != null && multiplier > 1 && /* @__PURE__ */ jsxs("span", { className: cn("font-bold text-warning tabular-nums", sizes.multiplier), children: [
@@ -11369,10 +11444,11 @@ function ComboCounter({
11369
11444
  }
11370
11445
  );
11371
11446
  }
11372
- var sizeMap8;
11447
+ var DEFAULT_ASSET_URL7, sizeMap8;
11373
11448
  var init_ComboCounter = __esm({
11374
11449
  "components/game/atoms/ComboCounter.tsx"() {
11375
11450
  init_cn();
11451
+ DEFAULT_ASSET_URL7 = "https://almadar-kflow-assets.web.app/shared/effects/flash/flash00.png";
11376
11452
  sizeMap8 = {
11377
11453
  sm: { combo: "text-lg", label: "text-xs", multiplier: "text-xs" },
11378
11454
  md: { combo: "text-2xl", label: "text-xs", multiplier: "text-sm" },
@@ -11466,6 +11542,7 @@ var init_XPBar = __esm({
11466
11542
  }
11467
11543
  });
11468
11544
  function WaypointMarker({
11545
+ assetUrl = DEFAULT_ASSET_URL8,
11469
11546
  label,
11470
11547
  icon,
11471
11548
  active = true,
@@ -11504,7 +11581,17 @@ function WaypointMarker({
11504
11581
  active && !completed && "bg-info text-foreground",
11505
11582
  !active && !completed && "bg-muted"
11506
11583
  ),
11507
- children: completed ? checkIcon : icon && (typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon }) : /* @__PURE__ */ jsx(Icon, { icon }))
11584
+ children: completed ? checkIcon : assetUrl ? /* @__PURE__ */ jsx(
11585
+ "img",
11586
+ {
11587
+ src: assetUrl,
11588
+ alt: label,
11589
+ width: sizes.img,
11590
+ height: sizes.img,
11591
+ style: { imageRendering: "pixelated", objectFit: "contain" },
11592
+ className: "flex-shrink-0"
11593
+ }
11594
+ ) : icon ? typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon }) : /* @__PURE__ */ jsx(Icon, { icon }) : null
11508
11595
  }
11509
11596
  )
11510
11597
  ] }),
@@ -11521,15 +11608,16 @@ function WaypointMarker({
11521
11608
  )
11522
11609
  ] });
11523
11610
  }
11524
- var sizeMap10, checkIcon;
11611
+ var DEFAULT_ASSET_URL8, sizeMap10, checkIcon;
11525
11612
  var init_WaypointMarker = __esm({
11526
11613
  "components/game/atoms/WaypointMarker.tsx"() {
11527
11614
  init_cn();
11528
11615
  init_Icon();
11616
+ DEFAULT_ASSET_URL8 = "https://almadar-kflow-assets.web.app/shared/world-map/battle_marker.png";
11529
11617
  sizeMap10 = {
11530
- sm: { dot: "w-4 h-4", ring: "w-6 h-6", label: "text-xs mt-1" },
11531
- md: { dot: "w-6 h-6", ring: "w-8 h-8", label: "text-sm mt-1.5" },
11532
- lg: { dot: "w-8 h-8", ring: "w-10 h-10", label: "text-base mt-2" }
11618
+ sm: { dot: "w-4 h-4", ring: "w-6 h-6", label: "text-xs mt-1", img: 12 },
11619
+ md: { dot: "w-6 h-6", ring: "w-8 h-8", label: "text-sm mt-1.5", img: 18 },
11620
+ lg: { dot: "w-8 h-8", ring: "w-10 h-10", label: "text-base mt-2", img: 24 }
11533
11621
  };
11534
11622
  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" }) });
11535
11623
  WaypointMarker.displayName = "WaypointMarker";
@@ -11542,6 +11630,7 @@ function formatDuration(seconds) {
11542
11630
  return `${Math.round(seconds)}s`;
11543
11631
  }
11544
11632
  function StatusEffect({
11633
+ assetUrl = DEFAULT_ASSET_URL9,
11545
11634
  icon = "shield",
11546
11635
  label = "Shield",
11547
11636
  duration = 30,
@@ -11562,7 +11651,17 @@ function StatusEffect({
11562
11651
  ),
11563
11652
  title: label,
11564
11653
  children: [
11565
- /* @__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" }) }),
11654
+ /* @__PURE__ */ jsx("span", { className: cn("flex items-center justify-center", sizes.icon), children: assetUrl ? /* @__PURE__ */ jsx(
11655
+ "img",
11656
+ {
11657
+ src: assetUrl,
11658
+ alt: label,
11659
+ width: sizes.img,
11660
+ height: sizes.img,
11661
+ style: { imageRendering: "pixelated", objectFit: "contain" },
11662
+ className: "flex-shrink-0"
11663
+ }
11664
+ ) : typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon, size: "sm" }) : /* @__PURE__ */ jsx(Icon, { icon, size: "sm" }) }),
11566
11665
  duration !== void 0 && /* @__PURE__ */ jsx(
11567
11666
  "span",
11568
11667
  {
@@ -11589,15 +11688,16 @@ function StatusEffect({
11589
11688
  label && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground mt-0.5 text-center whitespace-nowrap", children: label })
11590
11689
  ] });
11591
11690
  }
11592
- var sizeMap11, variantStyles7;
11691
+ var DEFAULT_ASSET_URL9, sizeMap11, variantStyles7;
11593
11692
  var init_StatusEffect = __esm({
11594
11693
  "components/game/atoms/StatusEffect.tsx"() {
11595
11694
  init_cn();
11596
11695
  init_Icon();
11696
+ DEFAULT_ASSET_URL9 = "https://almadar-kflow-assets.web.app/shared/effects/particles/flame_01.png";
11597
11697
  sizeMap11 = {
11598
- sm: { container: "w-8 h-8", icon: "text-sm", badge: "text-xs -top-1 -right-1 w-4 h-4", timer: "text-[9px]" },
11599
- md: { container: "w-10 h-10", icon: "text-base", badge: "text-xs -top-1 -right-1 w-5 h-5", timer: "text-xs" },
11600
- 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" }
11698
+ 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 },
11699
+ 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 },
11700
+ 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 }
11601
11701
  };
11602
11702
  variantStyles7 = {
11603
11703
  buff: "border-success bg-success/20",
@@ -11608,6 +11708,7 @@ var init_StatusEffect = __esm({
11608
11708
  }
11609
11709
  });
11610
11710
  function DamageNumber({
11711
+ assetUrl = DEFAULT_ASSET_URL10,
11611
11712
  value = 42,
11612
11713
  type = "damage",
11613
11714
  size = "md",
@@ -11616,22 +11717,35 @@ function DamageNumber({
11616
11717
  const displayText = type === "miss" ? "MISS" : type === "heal" ? `+${value}` : `${value}`;
11617
11718
  return /* @__PURE__ */ jsxs(Fragment, { children: [
11618
11719
  /* @__PURE__ */ jsx("style", { children: floatKeyframes }),
11619
- /* @__PURE__ */ jsx(
11720
+ /* @__PURE__ */ jsxs(
11620
11721
  "span",
11621
11722
  {
11622
11723
  className: cn(
11623
- "inline-block font-mono select-none pointer-events-none",
11724
+ "inline-flex items-center gap-0.5 font-mono select-none pointer-events-none",
11624
11725
  sizeMap12[size],
11625
11726
  typeStyles[type],
11626
11727
  className
11627
11728
  ),
11628
11729
  style: { animation: "damageFloat 1s ease-out forwards" },
11629
- children: displayText
11730
+ children: [
11731
+ assetUrl && /* @__PURE__ */ jsx(
11732
+ "img",
11733
+ {
11734
+ src: assetUrl,
11735
+ alt: "",
11736
+ width: 14,
11737
+ height: 14,
11738
+ style: { imageRendering: "pixelated", objectFit: "contain" },
11739
+ className: "flex-shrink-0"
11740
+ }
11741
+ ),
11742
+ displayText
11743
+ ]
11630
11744
  }
11631
11745
  )
11632
11746
  ] });
11633
11747
  }
11634
- var sizeMap12, typeStyles, floatKeyframes;
11748
+ var sizeMap12, typeStyles, floatKeyframes, DEFAULT_ASSET_URL10;
11635
11749
  var init_DamageNumber = __esm({
11636
11750
  "components/game/atoms/DamageNumber.tsx"() {
11637
11751
  init_cn();
@@ -11653,6 +11767,7 @@ var init_DamageNumber = __esm({
11653
11767
  100% { opacity: 0; transform: translateY(-32px) scale(0.8); }
11654
11768
  }
11655
11769
  `;
11770
+ DEFAULT_ASSET_URL10 = "https://almadar-kflow-assets.web.app/shared/effects/particles/spark_01.png";
11656
11771
  DamageNumber.displayName = "DamageNumber";
11657
11772
  }
11658
11773
  });
@@ -11741,6 +11856,7 @@ var init_ChoiceButton = __esm({
11741
11856
  }
11742
11857
  });
11743
11858
  function ActionButton({
11859
+ assetUrl = DEFAULT_ASSET_URL11,
11744
11860
  label = "Attack",
11745
11861
  icon,
11746
11862
  cooldown = 0,
@@ -11780,13 +11896,23 @@ function ActionButton({
11780
11896
  }
11781
11897
  }
11782
11898
  ),
11783
- icon && /* @__PURE__ */ jsx("span", { className: cn("flex-shrink-0", sizes.icon), children: typeof icon === "string" ? (() => {
11899
+ assetUrl ? /* @__PURE__ */ jsx(
11900
+ "img",
11901
+ {
11902
+ src: assetUrl,
11903
+ alt: "",
11904
+ width: 16,
11905
+ height: 16,
11906
+ style: { imageRendering: "pixelated", objectFit: "contain" },
11907
+ className: cn("flex-shrink-0", sizes.icon)
11908
+ }
11909
+ ) : icon ? /* @__PURE__ */ jsx("span", { className: cn("flex-shrink-0", sizes.icon), children: typeof icon === "string" ? (() => {
11784
11910
  const I = resolveIcon(icon);
11785
11911
  return I ? /* @__PURE__ */ jsx(I, { className: "w-4 h-4" }) : null;
11786
11912
  })() : /* @__PURE__ */ (() => {
11787
11913
  const I = icon;
11788
11914
  return /* @__PURE__ */ jsx(I, { className: "w-4 h-4" });
11789
- })() }),
11915
+ })() }) : null,
11790
11916
  /* @__PURE__ */ jsx("span", { className: "relative z-10", children: label }),
11791
11917
  hotkey && /* @__PURE__ */ jsx(
11792
11918
  "span",
@@ -11802,7 +11928,7 @@ function ActionButton({
11802
11928
  }
11803
11929
  );
11804
11930
  }
11805
- var sizeMap13, variantStyles8;
11931
+ var sizeMap13, variantStyles8, DEFAULT_ASSET_URL11;
11806
11932
  var init_ActionButton = __esm({
11807
11933
  "components/game/atoms/ActionButton.tsx"() {
11808
11934
  init_cn();
@@ -11817,6 +11943,7 @@ var init_ActionButton = __esm({
11817
11943
  secondary: "bg-secondary text-secondary-foreground hover:bg-secondary-hover border-border",
11818
11944
  danger: "bg-error text-error-foreground hover:bg-error/90 border-error"
11819
11945
  };
11946
+ DEFAULT_ASSET_URL11 = "https://almadar-kflow-assets.web.app/shared/effects/particles/slash_01.png";
11820
11947
  ActionButton.displayName = "ActionButton";
11821
11948
  }
11822
11949
  });
@@ -12329,10 +12456,10 @@ var init_Container = __esm({
12329
12456
  center = true,
12330
12457
  className,
12331
12458
  children,
12332
- as: Component = "div"
12459
+ as: Component2 = "div"
12333
12460
  }) => {
12334
12461
  const resolvedSize = maxWidth ?? size ?? "lg";
12335
- const Comp = Component;
12462
+ const Comp = Component2;
12336
12463
  return /* @__PURE__ */ jsx(
12337
12464
  Comp,
12338
12465
  {
@@ -15166,15 +15293,15 @@ function BattleBoard({
15166
15293
  const board = boardEntity(entity) ?? {};
15167
15294
  const tiles = propTiles ?? (Array.isArray(board.tiles) ? board.tiles : []);
15168
15295
  const features = propFeatures ?? (Array.isArray(board.features) ? board.features : []);
15169
- const boardWidth = num(board.boardWidth, 8);
15170
- const boardHeight = num(board.boardHeight, 6);
15296
+ const boardWidth = num(board.gridWidth ?? board.boardWidth, 8);
15297
+ const boardHeight = num(board.gridHeight ?? board.boardHeight, 6);
15171
15298
  const assetManifest = propAssetManifest ?? board.assetManifest;
15172
15299
  const backgroundImage = board.backgroundImage;
15173
15300
  const units = rows(board.units);
15174
15301
  const selectedUnitId = board.selectedUnitId ?? null;
15175
15302
  const currentPhase = str(board.phase) || "observation";
15176
15303
  const currentTurn = num(board.turn, 1);
15177
- const gameResult = board.gameResult ?? null;
15304
+ const gameResult = board.result ?? null;
15178
15305
  const eventBus = useEventBus();
15179
15306
  const { t } = useTranslate();
15180
15307
  const [hoveredTile, setHoveredTile] = useState(null);
@@ -15195,7 +15322,7 @@ function BattleBoard({
15195
15322
  const validMoves = useMemo(() => {
15196
15323
  if (!selectedUnit || currentPhase !== "movement") return [];
15197
15324
  const moves = [];
15198
- const range = num(selectedUnit.movement);
15325
+ const range = num(board.movementRange, 2);
15199
15326
  const origin = unitPosition(selectedUnit);
15200
15327
  for (let dy = -range; dy <= range; dy++) {
15201
15328
  for (let dx = -range; dx <= range; dx++) {
@@ -22651,15 +22778,24 @@ function CastleBoard({
22651
22778
  featureClickEvent,
22652
22779
  unitClickEvent,
22653
22780
  tileClickEvent,
22781
+ playAgainEvent,
22654
22782
  className
22655
22783
  }) {
22656
22784
  const eventBus = useEventBus();
22785
+ const { t } = useTranslate();
22657
22786
  const resolved = boardEntity(entity);
22658
22787
  const tiles = propTiles ?? (Array.isArray(resolved?.tiles) ? resolved.tiles : []);
22659
22788
  const features = propFeatures ?? (Array.isArray(resolved?.features) ? resolved.features : []);
22660
22789
  const units = propUnits ?? (Array.isArray(resolved?.units) ? resolved.units : []);
22661
22790
  const assetManifest = propAssetManifest ?? resolved?.assetManifest;
22662
22791
  const backgroundImage = resolved?.backgroundImage;
22792
+ const gold = num(resolved?.gold);
22793
+ const health = num(resolved?.health);
22794
+ const maxHealth = num(resolved?.maxHealth);
22795
+ const wave = num(resolved?.wave);
22796
+ const tickCount = num(resolved?.tickCount);
22797
+ const buildings = rows(resolved?.buildings);
22798
+ const result = str(resolved?.result) || "none";
22663
22799
  const [hoveredTile, setHoveredTile] = useState(null);
22664
22800
  const [selectedFeature, setSelectedFeature] = useState(null);
22665
22801
  const hoveredFeature = useMemo(() => {
@@ -22672,7 +22808,7 @@ function CastleBoard({
22672
22808
  (u) => u.position?.x === hoveredTile.x && u.position?.y === hoveredTile.y
22673
22809
  ) ?? null;
22674
22810
  }, [hoveredTile, units]);
22675
- const maxY = Math.max(...tiles.map((t) => t.y), 0);
22811
+ const maxY = Math.max(...tiles.map((t2) => t2.y), 0);
22676
22812
  const baseOffsetX = (maxY + 1) * (TILE_WIDTH * scale / 2);
22677
22813
  const tileToScreen = useCallback(
22678
22814
  (tx, ty) => isoToScreen(tx, ty, scale, baseOffsetX),
@@ -22707,6 +22843,11 @@ function CastleBoard({
22707
22843
  }
22708
22844
  }, [units, onUnitClick, unitClickEvent, eventBus]);
22709
22845
  const clearSelection = useCallback(() => setSelectedFeature(null), []);
22846
+ const handlePlayAgain = useCallback(() => {
22847
+ if (playAgainEvent) {
22848
+ eventBus.emit(`UI:${playAgainEvent}`, {});
22849
+ }
22850
+ }, [playAgainEvent, eventBus]);
22710
22851
  const ctx = useMemo(
22711
22852
  () => ({
22712
22853
  hoveredTile,
@@ -22715,11 +22856,18 @@ function CastleBoard({
22715
22856
  selectedFeature,
22716
22857
  clearSelection,
22717
22858
  tileToScreen,
22718
- scale
22859
+ scale,
22860
+ gold,
22861
+ health,
22862
+ maxHealth,
22863
+ wave,
22864
+ tickCount,
22865
+ buildings,
22866
+ result
22719
22867
  }),
22720
- [hoveredTile, hoveredFeature, hoveredUnit, selectedFeature, clearSelection, tileToScreen, scale]
22868
+ [hoveredTile, hoveredFeature, hoveredUnit, selectedFeature, clearSelection, tileToScreen, scale, gold, health, maxHealth, wave, tickCount, buildings, result]
22721
22869
  );
22722
- return /* @__PURE__ */ jsxs("div", { className: cn("castle-board min-h-screen flex flex-col bg-background", className), children: [
22870
+ return /* @__PURE__ */ jsxs("div", { className: cn("castle-board relative min-h-screen flex flex-col bg-background", className), children: [
22723
22871
  header && header(ctx),
22724
22872
  /* @__PURE__ */ jsxs("div", { className: "flex flex-1 overflow-hidden", children: [
22725
22873
  /* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-auto p-4 relative", children: [
@@ -22744,7 +22892,29 @@ function CastleBoard({
22744
22892
  ] }),
22745
22893
  sidePanel && /* @__PURE__ */ jsx("div", { className: "w-96 shrink-0 border-l border-border bg-surface overflow-y-auto", children: sidePanel(ctx) })
22746
22894
  ] }),
22747
- footer && footer(ctx)
22895
+ footer && footer(ctx),
22896
+ 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: [
22897
+ /* @__PURE__ */ jsx(
22898
+ Typography,
22899
+ {
22900
+ variant: "h2",
22901
+ className: cn(
22902
+ "text-4xl font-black tracking-widest uppercase",
22903
+ result === "victory" ? "text-warning" : "text-error"
22904
+ ),
22905
+ children: result === "victory" ? t("battle.victory") : t("battle.defeat")
22906
+ }
22907
+ ),
22908
+ /* @__PURE__ */ jsx(
22909
+ Button,
22910
+ {
22911
+ variant: "primary",
22912
+ className: "px-8 py-3 font-semibold",
22913
+ onClick: handlePlayAgain,
22914
+ children: t("battle.playAgain")
22915
+ }
22916
+ )
22917
+ ] }) })
22748
22918
  ] });
22749
22919
  }
22750
22920
  var init_CastleBoard = __esm({
@@ -22752,6 +22922,10 @@ var init_CastleBoard = __esm({
22752
22922
  "use client";
22753
22923
  init_cn();
22754
22924
  init_useEventBus();
22925
+ init_Box();
22926
+ init_Button();
22927
+ init_Typography();
22928
+ init_Stack();
22755
22929
  init_IsometricCanvas();
22756
22930
  init_boardEntity();
22757
22931
  init_isometric();
@@ -23628,22 +23802,16 @@ function ClassifierBoard({
23628
23802
  const { emit } = useEventBus();
23629
23803
  const { t } = useTranslate();
23630
23804
  const resolved = boardEntity(entity);
23631
- const [localAssignments, setLocalAssignments] = useState({});
23632
23805
  const [headerError, setHeaderError] = useState(false);
23633
- const [localSubmitted, setLocalSubmitted] = useState(false);
23634
- const [localAttempts, setLocalAttempts] = useState(0);
23635
- const [showHint, setShowHint] = useState(false);
23636
23806
  const items = Array.isArray(resolved?.items) ? resolved.items : [];
23637
23807
  const categories = Array.isArray(resolved?.categories) ? resolved.categories : [];
23638
- const entityResult = str(resolved?.result);
23639
- const entityDrivesResult = entityResult.length > 0;
23640
- const entityHasAssignments = items.some((item) => item.assignedCategory != null);
23641
- const assignments = entityHasAssignments ? items.reduce((acc, item) => {
23642
- if (item.assignedCategory != null) acc[item.id] = item.assignedCategory;
23808
+ const result = str(resolved?.result);
23809
+ const submitted = result === "win";
23810
+ const attempts = num(resolved?.attempts);
23811
+ const assignments = items.reduce((acc, item) => {
23812
+ if (item.assignedCategory != null && item.assignedCategory !== "") acc[item.id] = item.assignedCategory;
23643
23813
  return acc;
23644
- }, {}) : localAssignments;
23645
- const attempts = entityDrivesResult ? num(resolved?.attempts) : localAttempts;
23646
- const submitted = entityDrivesResult || localSubmitted;
23814
+ }, {});
23647
23815
  const unassignedItems = items.filter((item) => !assignments[item.id]);
23648
23816
  const allAssigned = items.length > 0 && Object.keys(assignments).length === items.length;
23649
23817
  const results = submitted ? items.map((item) => ({
@@ -23651,54 +23819,25 @@ function ClassifierBoard({
23651
23819
  assigned: assignments[item.id],
23652
23820
  correct: assignments[item.id] === item.correctCategory
23653
23821
  })) : [];
23654
- const allCorrect = entityDrivesResult ? entityResult === "success" : results.length > 0 && results.every((r2) => r2.correct);
23822
+ const allCorrect = result === "win";
23655
23823
  const correctCount = results.filter((r2) => r2.correct).length;
23824
+ const showHint = attempts >= 2 && Boolean(str(resolved?.hint));
23656
23825
  const handleAssign = (itemId, categoryId) => {
23657
23826
  if (submitted) return;
23658
- if (assignEvent) {
23659
- emit(`UI:${assignEvent}`, { itemId, categoryId });
23660
- }
23661
- if (!entityHasAssignments) {
23662
- setLocalAssignments((prev) => ({ ...prev, [itemId]: categoryId }));
23663
- }
23827
+ if (assignEvent) emit(`UI:${assignEvent}`, { itemId, categoryId });
23664
23828
  };
23665
23829
  const handleUnassign = (itemId) => {
23666
23830
  if (submitted) return;
23667
- if (!entityHasAssignments) {
23668
- setLocalAssignments((prev) => {
23669
- const next = { ...prev };
23670
- delete next[itemId];
23671
- return next;
23672
- });
23673
- }
23831
+ if (assignEvent) emit(`UI:${assignEvent}`, { itemId, categoryId: "" });
23674
23832
  };
23675
- const handleSubmit = useCallback(() => {
23676
- if (checkEvent) {
23677
- emit(`UI:${checkEvent}`, {});
23678
- }
23679
- if (!entityDrivesResult) {
23680
- setLocalSubmitted(true);
23681
- setLocalAttempts((a) => a + 1);
23682
- const correct = items.every((item) => assignments[item.id] === item.correctCategory);
23683
- if (correct) {
23684
- emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
23685
- }
23686
- }
23687
- }, [checkEvent, entityDrivesResult, items, assignments, attempts, completeEvent, emit]);
23688
- const handleReset = () => {
23689
- if (!entityDrivesResult) setLocalSubmitted(false);
23690
- if (attempts >= 2 && str(resolved?.hint)) {
23691
- setShowHint(true);
23833
+ const handleSubmit = () => {
23834
+ if (checkEvent) emit(`UI:${checkEvent}`, {});
23835
+ if (allAssigned && items.every((item) => assignments[item.id] === item.correctCategory)) {
23836
+ emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
23692
23837
  }
23693
23838
  };
23694
23839
  const handleFullReset = () => {
23695
- if (playAgainEvent) {
23696
- emit(`UI:${playAgainEvent}`, {});
23697
- }
23698
- setLocalAssignments({});
23699
- setLocalSubmitted(false);
23700
- setLocalAttempts(0);
23701
- setShowHint(false);
23840
+ if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
23702
23841
  };
23703
23842
  if (!resolved) return null;
23704
23843
  const theme = resolved.theme ?? void 0;
@@ -23737,17 +23876,17 @@ function ClassifierBoard({
23737
23876
  /* @__PURE__ */ jsx(Badge, { size: "sm", children: catItems.length })
23738
23877
  ] }),
23739
23878
  /* @__PURE__ */ jsx(HStack, { gap: "xs", className: "flex-wrap min-h-[2rem]", children: catItems.map((item) => {
23740
- const result = results.find((r2) => r2.item.id === item.id);
23879
+ const result2 = results.find((r2) => r2.item.id === item.id);
23741
23880
  return /* @__PURE__ */ jsxs(
23742
23881
  Badge,
23743
23882
  {
23744
23883
  size: "sm",
23745
- className: `cursor-pointer ${result ? result.correct ? "border-success bg-success/10" : "border-error bg-error/10" : ""}`,
23884
+ className: `cursor-pointer ${result2 ? result2.correct ? "border-success bg-success/10" : "border-error bg-error/10" : ""}`,
23746
23885
  onClick: () => handleUnassign(item.id),
23747
23886
  children: [
23748
23887
  item.iconUrl && /* @__PURE__ */ jsx("img", { src: item.iconUrl, alt: "", className: "w-3 h-3 object-contain inline-block" }),
23749
23888
  item.label,
23750
- result && /* @__PURE__ */ jsx(Icon, { icon: result.correct ? CheckCircle : XCircle, size: "xs", className: result.correct ? "text-success" : "text-error" })
23889
+ result2 && /* @__PURE__ */ jsx(Icon, { icon: result2.correct ? CheckCircle : XCircle, size: "xs", className: result2.correct ? "text-success" : "text-error" })
23751
23890
  ]
23752
23891
  },
23753
23892
  item.id
@@ -23776,10 +23915,10 @@ function ClassifierBoard({
23776
23915
  ] }) }),
23777
23916
  showHint && hint && /* @__PURE__ */ jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsx(Typography, { variant: "body", children: hint }) }),
23778
23917
  /* @__PURE__ */ jsxs(HStack, { gap: "sm", justify: "center", children: [
23779
- !submitted ? /* @__PURE__ */ jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: !allAssigned, children: [
23918
+ !submitted && /* @__PURE__ */ jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: !allAssigned, children: [
23780
23919
  /* @__PURE__ */ jsx(Icon, { icon: Send, size: "sm" }),
23781
23920
  t("classifier.check")
23782
- ] }) : !allCorrect ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleReset, children: t("classifier.tryAgain") }) : null,
23921
+ ] }),
23783
23922
  /* @__PURE__ */ jsxs(Button, { variant: "secondary", onClick: handleFullReset, children: [
23784
23923
  /* @__PURE__ */ jsx(Icon, { icon: RotateCcw, size: "sm" }),
23785
23924
  t("classifier.reset")
@@ -27497,7 +27636,7 @@ var init_Flex = __esm({
27497
27636
  basis,
27498
27637
  className,
27499
27638
  children,
27500
- as: Component = "div"
27639
+ as: Component2 = "div"
27501
27640
  }) => {
27502
27641
  const flexStyle = {};
27503
27642
  if (grow !== void 0 || shrink !== void 0 || basis !== void 0) {
@@ -27509,7 +27648,7 @@ var init_Flex = __esm({
27509
27648
  flexStyle.flexBasis = typeof basis === "number" ? `${basis}px` : basis;
27510
27649
  }
27511
27650
  }
27512
- const Comp = Component;
27651
+ const Comp = Component2;
27513
27652
  return /* @__PURE__ */ jsx(
27514
27653
  Comp,
27515
27654
  {
@@ -27629,11 +27768,11 @@ var init_Grid = __esm({
27629
27768
  className,
27630
27769
  style,
27631
27770
  children,
27632
- as: Component = "div"
27771
+ as: Component2 = "div"
27633
27772
  }) => {
27634
27773
  const mergedStyle = rows2 ? { gridTemplateRows: `repeat(${rows2}, minmax(0, 1fr))`, ...style } : style;
27635
27774
  return React91__default.createElement(
27636
- Component,
27775
+ Component2,
27637
27776
  {
27638
27777
  className: cn(
27639
27778
  "grid",
@@ -29459,6 +29598,7 @@ var init_ProgressDots = __esm({
29459
29598
  }
29460
29599
  });
29461
29600
  function StatBadge({
29601
+ assetUrl = DEFAULT_ASSET_URL12,
29462
29602
  label,
29463
29603
  value = 0,
29464
29604
  max,
@@ -29482,7 +29622,17 @@ function StatBadge({
29482
29622
  className
29483
29623
  ),
29484
29624
  children: [
29485
- 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" }) }),
29625
+ assetUrl ? /* @__PURE__ */ jsx(
29626
+ "img",
29627
+ {
29628
+ src: assetUrl,
29629
+ alt: "",
29630
+ width: 16,
29631
+ height: 16,
29632
+ style: { imageRendering: "pixelated", objectFit: "contain" },
29633
+ className: "flex-shrink-0"
29634
+ }
29635
+ ) : 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,
29486
29636
  /* @__PURE__ */ jsx("span", { className: "text-muted-foreground font-medium", children: label }),
29487
29637
  format === "hearts" && max && /* @__PURE__ */ jsx(
29488
29638
  HealthBar,
@@ -29515,7 +29665,7 @@ function StatBadge({
29515
29665
  }
29516
29666
  );
29517
29667
  }
29518
- var sizeMap17, variantMap2;
29668
+ var sizeMap17, variantMap2, DEFAULT_ASSET_URL12;
29519
29669
  var init_StatBadge = __esm({
29520
29670
  "components/game/molecules/StatBadge.tsx"() {
29521
29671
  init_cn();
@@ -29534,6 +29684,7 @@ var init_StatBadge = __esm({
29534
29684
  warning: "bg-warning/15 border-warning/40 text-foreground",
29535
29685
  danger: "bg-error/15 border-error/40 text-foreground"
29536
29686
  };
29687
+ DEFAULT_ASSET_URL12 = "https://almadar-kflow-assets.web.app/shared/effects/particles/light_01.png";
29537
29688
  StatBadge.displayName = "StatBadge";
29538
29689
  }
29539
29690
  });
@@ -30190,6 +30341,7 @@ var init_TurnPanel = __esm({
30190
30341
  }
30191
30342
  });
30192
30343
  function EnemyPlate({
30344
+ assetUrl = DEFAULT_ASSET_URL13,
30193
30345
  name = "Shadow Guard",
30194
30346
  health = 80,
30195
30347
  maxHealth = 100,
@@ -30207,15 +30359,28 @@ function EnemyPlate({
30207
30359
  ),
30208
30360
  children: [
30209
30361
  /* @__PURE__ */ jsxs(Box, { className: "flex items-center justify-between gap-2", children: [
30210
- /* @__PURE__ */ jsx(
30211
- Typography,
30212
- {
30213
- variant: "caption",
30214
- weight: "bold",
30215
- className: "text-[var(--color-foreground)] truncate",
30216
- children: name
30217
- }
30218
- ),
30362
+ /* @__PURE__ */ jsxs(Box, { className: "flex items-center gap-1.5 min-w-0", children: [
30363
+ assetUrl && /* @__PURE__ */ jsx(
30364
+ "img",
30365
+ {
30366
+ src: assetUrl,
30367
+ alt: name,
30368
+ width: 24,
30369
+ height: 24,
30370
+ style: { objectFit: "cover", borderRadius: "50%" },
30371
+ className: "flex-shrink-0"
30372
+ }
30373
+ ),
30374
+ /* @__PURE__ */ jsx(
30375
+ Typography,
30376
+ {
30377
+ variant: "caption",
30378
+ weight: "bold",
30379
+ className: "text-[var(--color-foreground)] truncate",
30380
+ children: name
30381
+ }
30382
+ )
30383
+ ] }),
30219
30384
  level != null && /* @__PURE__ */ jsxs(Badge, { variant: "neutral", size: "sm", children: [
30220
30385
  "Lv.",
30221
30386
  level
@@ -30260,7 +30425,7 @@ function EnemyPlate({
30260
30425
  }
30261
30426
  );
30262
30427
  }
30263
- var effectVariantMap2, DEFAULT_ENEMY_EFFECTS;
30428
+ var effectVariantMap2, DEFAULT_ENEMY_EFFECTS, DEFAULT_ASSET_URL13;
30264
30429
  var init_EnemyPlate = __esm({
30265
30430
  "components/game/molecules/EnemyPlate.tsx"() {
30266
30431
  "use client";
@@ -30277,6 +30442,7 @@ var init_EnemyPlate = __esm({
30277
30442
  DEFAULT_ENEMY_EFFECTS = [
30278
30443
  { icon: "flame", label: "Burn", variant: "debuff" }
30279
30444
  ];
30445
+ DEFAULT_ASSET_URL13 = "https://almadar-kflow-assets.web.app/shared/portraits/shadow-legion.png";
30280
30446
  EnemyPlate.displayName = "EnemyPlate";
30281
30447
  }
30282
30448
  });
@@ -31808,13 +31974,13 @@ var init_MapView = __esm({
31808
31974
  shadowSize: [41, 41]
31809
31975
  });
31810
31976
  L.Marker.prototype.options.icon = defaultIcon;
31811
- const { useEffect: useEffect78, useRef: useRef70, useCallback: useCallback118, useState: useState115 } = React91__default;
31977
+ const { useEffect: useEffect84, useRef: useRef76, useCallback: useCallback124, useState: useState120 } = React91__default;
31812
31978
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
31813
- const { useEventBus: useEventBus3 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
31979
+ const { useEventBus: useEventBus4 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
31814
31980
  function MapUpdater({ centerLat, centerLng, zoom }) {
31815
31981
  const map = useMap();
31816
- const prevRef = useRef70({ centerLat, centerLng, zoom });
31817
- useEffect78(() => {
31982
+ const prevRef = useRef76({ centerLat, centerLng, zoom });
31983
+ useEffect84(() => {
31818
31984
  const prev = prevRef.current;
31819
31985
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
31820
31986
  map.setView([centerLat, centerLng], zoom);
@@ -31825,7 +31991,7 @@ var init_MapView = __esm({
31825
31991
  }
31826
31992
  function MapClickHandler({ onMapClick }) {
31827
31993
  const map = useMap();
31828
- useEffect78(() => {
31994
+ useEffect84(() => {
31829
31995
  if (!onMapClick) return;
31830
31996
  const handler = (e) => {
31831
31997
  onMapClick(e.latlng.lat, e.latlng.lng);
@@ -31852,9 +32018,9 @@ var init_MapView = __esm({
31852
32018
  className,
31853
32019
  showAttribution = true
31854
32020
  }) {
31855
- const eventBus = useEventBus3();
31856
- const [clickedPosition, setClickedPosition] = useState115(null);
31857
- const handleMapClick = useCallback118((lat, lng) => {
32021
+ const eventBus = useEventBus4();
32022
+ const [clickedPosition, setClickedPosition] = useState120(null);
32023
+ const handleMapClick = useCallback124((lat, lng) => {
31858
32024
  if (showClickedPin) {
31859
32025
  setClickedPosition({ lat, lng });
31860
32026
  }
@@ -31863,7 +32029,7 @@ var init_MapView = __esm({
31863
32029
  eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
31864
32030
  }
31865
32031
  }, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
31866
- const handleMarkerClick = useCallback118((marker) => {
32032
+ const handleMarkerClick = useCallback124((marker) => {
31867
32033
  onMarkerClick?.(marker);
31868
32034
  if (markerClickEvent) {
31869
32035
  eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
@@ -38581,10 +38747,10 @@ var init_Section = __esm({
38581
38747
  children,
38582
38748
  headerClassName,
38583
38749
  contentClassName,
38584
- as: Component = "section"
38750
+ as: Component2 = "section"
38585
38751
  }) => {
38586
38752
  const hasHeader = title || description || action;
38587
- const Comp = Component;
38753
+ const Comp = Component2;
38588
38754
  return /* @__PURE__ */ jsxs(
38589
38755
  Comp,
38590
38756
  {
@@ -40685,44 +40851,27 @@ function DebuggerBoard({
40685
40851
  const { t } = useTranslate();
40686
40852
  const resolved = boardEntity(entity);
40687
40853
  const [headerError, setHeaderError] = useState(false);
40688
- const [showHint, setShowHint] = useState(false);
40689
40854
  const lines = Array.isArray(resolved?.lines) ? resolved.lines : [];
40690
- const result = resolved?.result ?? null;
40855
+ const result = str(resolved?.result) || "none";
40691
40856
  const attempts = num(resolved?.attempts);
40692
- const submitted = result != null;
40857
+ const submitted = result === "win";
40858
+ const showHint = !submitted && attempts >= 2 && Boolean(str(resolved?.hint));
40693
40859
  const bugLines = lines.filter((l) => l.isBug);
40694
40860
  const flaggedLines = lines.filter((l) => l.isFlagged);
40695
40861
  const correctFlags = lines.filter((l) => l.isBug && l.isFlagged);
40696
40862
  const falseFlags = lines.filter((l) => !l.isBug && l.isFlagged);
40697
- const allCorrect = result === "win";
40863
+ const allCorrect = submitted;
40698
40864
  const toggleLine = (lineId) => {
40699
40865
  if (submitted) return;
40700
- if (toggleFlagEvent) {
40701
- emit(`UI:${toggleFlagEvent}`, { lineId });
40702
- }
40866
+ if (toggleFlagEvent) emit(`UI:${toggleFlagEvent}`, { lineId });
40703
40867
  };
40704
40868
  const handleSubmit = useCallback(() => {
40705
- if (checkEvent) {
40706
- emit(`UI:${checkEvent}`, {});
40707
- }
40869
+ if (checkEvent) emit(`UI:${checkEvent}`, {});
40708
40870
  const correct = correctFlags.length === bugLines.length && falseFlags.length === 0;
40709
- if (correct) {
40710
- emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
40711
- }
40871
+ if (correct) emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
40712
40872
  }, [checkEvent, correctFlags.length, bugLines.length, falseFlags.length, attempts, completeEvent, emit]);
40713
40873
  const handleReset = () => {
40714
- if (playAgainEvent) {
40715
- emit(`UI:${playAgainEvent}`, {});
40716
- }
40717
- if (attempts >= 2 && str(resolved?.hint)) {
40718
- setShowHint(true);
40719
- }
40720
- };
40721
- const handleFullReset = () => {
40722
- if (playAgainEvent) {
40723
- emit(`UI:${playAgainEvent}`, {});
40724
- }
40725
- setShowHint(false);
40874
+ if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
40726
40875
  };
40727
40876
  if (!resolved) return null;
40728
40877
  const theme = resolved.theme ?? void 0;
@@ -40746,7 +40895,7 @@ function DebuggerBoard({
40746
40895
  /* @__PURE__ */ jsx(Typography, { variant: "h4", weight: "bold", children: str(resolved.title) })
40747
40896
  ] }),
40748
40897
  /* @__PURE__ */ jsx(Typography, { variant: "body", children: str(resolved.description) }),
40749
- /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-muted-foreground", children: t("debugger.findBugs", { count: String(num(resolved.bugCount)) }) })
40898
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-muted-foreground", children: t("debugger.findBugs", { count: String(lines.filter((l) => l.isBug).length) }) })
40750
40899
  ] }) }),
40751
40900
  /* @__PURE__ */ jsx(Card, { className: "p-0 overflow-hidden", children: /* @__PURE__ */ jsx(VStack, { gap: "none", children: lines.map((line, i) => {
40752
40901
  const isFlagged = !!line.isFlagged;
@@ -40796,11 +40945,11 @@ function DebuggerBoard({
40796
40945
  ] }) }),
40797
40946
  showHint && hint && /* @__PURE__ */ jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsx(Typography, { variant: "body", children: hint }) }),
40798
40947
  /* @__PURE__ */ jsxs(HStack, { gap: "sm", justify: "center", children: [
40799
- !submitted ? /* @__PURE__ */ jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: flaggedLines.length === 0, children: [
40948
+ !submitted && /* @__PURE__ */ jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: flaggedLines.length === 0, children: [
40800
40949
  /* @__PURE__ */ jsx(Icon, { icon: Send, size: "sm" }),
40801
40950
  t("debugger.submit")
40802
- ] }) : !allCorrect ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleReset, children: t("debugger.tryAgain") }) : null,
40803
- /* @__PURE__ */ jsxs(Button, { variant: "secondary", onClick: handleFullReset, children: [
40951
+ ] }),
40952
+ /* @__PURE__ */ jsxs(Button, { variant: "secondary", onClick: handleReset, children: [
40804
40953
  /* @__PURE__ */ jsx(Icon, { icon: RotateCcw, size: "sm" }),
40805
40954
  t("debugger.reset")
40806
40955
  ] })
@@ -41801,39 +41950,40 @@ function EventHandlerBoard({
41801
41950
  stepDurationMs = 800,
41802
41951
  playEvent,
41803
41952
  completeEvent,
41953
+ editRuleEvent,
41954
+ playAgainEvent,
41804
41955
  className
41805
41956
  }) {
41806
41957
  const { emit } = useEventBus();
41807
41958
  const { t } = useTranslate();
41808
41959
  const resolved = boardEntity(entity);
41809
- const entityObjects = rows(resolved?.objects);
41810
- const [objects, setObjects] = useState(() => [...entityObjects]);
41960
+ const objects = rows(resolved?.objects);
41961
+ const entityResult = str(resolved?.result) || "none";
41962
+ const isSuccess = entityResult === "win";
41963
+ const attempts = typeof resolved?.attempts === "number" ? resolved.attempts : 0;
41811
41964
  const [selectedObjectId, setSelectedObjectId] = useState(
41812
- entityObjects[0] ? objId(entityObjects[0]) : null
41965
+ objects[0] ? objId(objects[0]) : null
41813
41966
  );
41814
41967
  const [headerError, setHeaderError] = useState(false);
41815
- const [playState, setPlayState] = useState("editing");
41968
+ const [isPlaying, setIsPlaying] = useState(false);
41816
41969
  const [eventLog, setEventLog] = useState([]);
41817
- const [attempts, setAttempts] = useState(0);
41818
41970
  const timerRef = useRef(null);
41819
41971
  const logIdCounter = useRef(0);
41820
41972
  useEffect(() => () => {
41821
41973
  if (timerRef.current) clearTimeout(timerRef.current);
41822
41974
  }, []);
41823
- const selectedObject = objects.find((o) => objId(o) === selectedObjectId) || null;
41975
+ const selectedObject = objects.find((o) => objId(o) === selectedObjectId) ?? null;
41824
41976
  const handleRulesChange = useCallback((objectId, rules) => {
41825
- setObjects((prev) => prev.map(
41826
- (o) => objId(o) === objectId ? { ...o, rules } : o
41827
- ));
41828
- }, []);
41977
+ if (editRuleEvent) emit(`UI:${editRuleEvent}`, { objectId, rules });
41978
+ }, [editRuleEvent, emit]);
41829
41979
  const addLogEntry = useCallback((icon, message, status = "done") => {
41830
41980
  const id = `log-${logIdCounter.current++}`;
41831
41981
  setEventLog((prev) => [...prev, { id, timestamp: Date.now(), icon, message, status }]);
41832
41982
  }, []);
41833
41983
  const handlePlay = useCallback(() => {
41834
- if (playState !== "editing") return;
41984
+ if (isPlaying || isSuccess) return;
41835
41985
  if (playEvent) emit(`UI:${playEvent}`, {});
41836
- setPlayState("playing");
41986
+ setIsPlaying(true);
41837
41987
  setEventLog([]);
41838
41988
  const allRules = [];
41839
41989
  objects.forEach((obj) => {
@@ -41849,15 +41999,8 @@ function EventHandlerBoard({
41849
41999
  let goalReached = false;
41850
42000
  const processNext = () => {
41851
42001
  if (eventQueue.length === 0 || stepIdx > 20) {
41852
- if (goalReached) {
41853
- setPlayState("success");
41854
- if (completeEvent) {
41855
- emit(`UI:${completeEvent}`, { success: true });
41856
- }
41857
- } else {
41858
- setAttempts((prev) => prev + 1);
41859
- setPlayState("fail");
41860
- }
42002
+ setIsPlaying(false);
42003
+ if (goalReached && completeEvent) emit(`UI:${completeEvent}`, { success: true });
41861
42004
  return;
41862
42005
  }
41863
42006
  const currentEvent = eventQueue.shift();
@@ -41888,21 +42031,19 @@ function EventHandlerBoard({
41888
42031
  addLogEntry("\u{1F3AC}", t("eventHandler.simulationStarted", { events: triggers.join(", ") }), "active");
41889
42032
  }
41890
42033
  timerRef.current = setTimeout(processNext, stepDurationMs);
41891
- }, [playState, objects, resolved, stepDurationMs, playEvent, completeEvent, emit, addLogEntry, t]);
42034
+ }, [isPlaying, isSuccess, objects, resolved, stepDurationMs, playEvent, completeEvent, emit, addLogEntry, t]);
41892
42035
  const handleTryAgain = useCallback(() => {
41893
42036
  if (timerRef.current) clearTimeout(timerRef.current);
41894
- setPlayState("editing");
42037
+ setIsPlaying(false);
41895
42038
  setEventLog([]);
41896
42039
  }, []);
41897
42040
  const handleReset = useCallback(() => {
41898
42041
  if (timerRef.current) clearTimeout(timerRef.current);
41899
- const resetObjects = rows(resolved?.objects);
41900
- setObjects([...resetObjects]);
41901
- setPlayState("editing");
42042
+ setIsPlaying(false);
41902
42043
  setEventLog([]);
41903
- setSelectedObjectId(resetObjects[0] ? objId(resetObjects[0]) : null);
41904
- setAttempts(0);
41905
- }, [resolved?.objects]);
42044
+ setSelectedObjectId(objects[0] ? objId(objects[0]) : null);
42045
+ if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
42046
+ }, [objects, playAgainEvent, emit]);
41906
42047
  if (!resolved) return null;
41907
42048
  const objectViewers = objects.map((obj) => {
41908
42049
  const states = objStates(obj);
@@ -41971,12 +42112,12 @@ function EventHandlerBoard({
41971
42112
  {
41972
42113
  object: selectedObject,
41973
42114
  onRulesChange: handleRulesChange,
41974
- disabled: playState !== "editing"
42115
+ disabled: isPlaying
41975
42116
  }
41976
42117
  ),
41977
42118
  eventLog.length > 0 && /* @__PURE__ */ jsx(EventLog, { entries: eventLog }),
41978
- 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") }) }),
41979
- playState === "fail" && /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
42119
+ 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") }) }),
42120
+ !isPlaying && !isSuccess && attempts > 0 && /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
41980
42121
  /* @__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) }) }),
41981
42122
  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: [
41982
42123
  /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-accent font-bold shrink-0", children: "\u{1F4A1} " + t("game.hint") + ":" }),
@@ -41984,12 +42125,12 @@ function EventHandlerBoard({
41984
42125
  ] }) })
41985
42126
  ] }),
41986
42127
  /* @__PURE__ */ jsxs(HStack, { gap: "sm", children: [
41987
- playState === "fail" ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleTryAgain, children: "\u{1F504} " + t("puzzle.tryAgainButton") }) : /* @__PURE__ */ jsx(
42128
+ !isPlaying && !isSuccess && attempts > 0 ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleTryAgain, children: "\u{1F504} " + t("puzzle.tryAgainButton") }) : /* @__PURE__ */ jsx(
41988
42129
  Button,
41989
42130
  {
41990
42131
  variant: "primary",
41991
42132
  onClick: handlePlay,
41992
- disabled: playState !== "editing",
42133
+ disabled: isPlaying || isSuccess,
41993
42134
  children: "\u25B6 " + t("game.play")
41994
42135
  }
41995
42136
  ),
@@ -43174,6 +43315,1568 @@ var init_GameAudioToggle = __esm({
43174
43315
  GameAudioToggle.displayName = "GameAudioToggle";
43175
43316
  }
43176
43317
  });
43318
+ function detectAssetRoot(modelUrl) {
43319
+ const idx = modelUrl.indexOf("/3d/");
43320
+ if (idx !== -1) {
43321
+ return modelUrl.substring(0, idx + 4);
43322
+ }
43323
+ return modelUrl.substring(0, modelUrl.lastIndexOf("/") + 1);
43324
+ }
43325
+ function createGLTFLoaderForUrl(url) {
43326
+ const loader = new GLTFLoader();
43327
+ loader.setResourcePath(detectAssetRoot(url));
43328
+ return loader;
43329
+ }
43330
+ var AssetLoader;
43331
+ var init_AssetLoader = __esm({
43332
+ "components/game/molecules/three/loaders/AssetLoader.ts"() {
43333
+ AssetLoader = class {
43334
+ constructor() {
43335
+ __publicField(this, "objLoader");
43336
+ __publicField(this, "textureLoader");
43337
+ __publicField(this, "modelCache");
43338
+ __publicField(this, "textureCache");
43339
+ __publicField(this, "loadingPromises");
43340
+ this.objLoader = new OBJLoader();
43341
+ this.textureLoader = new THREE.TextureLoader();
43342
+ this.modelCache = /* @__PURE__ */ new Map();
43343
+ this.textureCache = /* @__PURE__ */ new Map();
43344
+ this.loadingPromises = /* @__PURE__ */ new Map();
43345
+ }
43346
+ /**
43347
+ * Load a GLB/GLTF model
43348
+ * @param url - URL to the .glb or .gltf file
43349
+ * @returns Promise with loaded model scene and animations
43350
+ */
43351
+ async loadModel(url) {
43352
+ if (this.modelCache.has(url)) {
43353
+ return this.modelCache.get(url);
43354
+ }
43355
+ if (this.loadingPromises.has(url)) {
43356
+ return this.loadingPromises.get(url);
43357
+ }
43358
+ const loader = createGLTFLoaderForUrl(url);
43359
+ const loadPromise = loader.loadAsync(url).then((gltf) => {
43360
+ const result = {
43361
+ scene: gltf.scene,
43362
+ animations: gltf.animations || []
43363
+ };
43364
+ this.modelCache.set(url, result);
43365
+ this.loadingPromises.delete(url);
43366
+ return result;
43367
+ }).catch((error) => {
43368
+ this.loadingPromises.delete(url);
43369
+ throw new Error(`Failed to load model ${url}: ${error.message}`);
43370
+ });
43371
+ this.loadingPromises.set(url, loadPromise);
43372
+ return loadPromise;
43373
+ }
43374
+ /**
43375
+ * Load an OBJ model (fallback for non-GLB assets)
43376
+ * @param url - URL to the .obj file
43377
+ * @returns Promise with loaded object group
43378
+ */
43379
+ async loadOBJ(url) {
43380
+ if (this.modelCache.has(url)) {
43381
+ return this.modelCache.get(url).scene;
43382
+ }
43383
+ if (this.loadingPromises.has(url)) {
43384
+ const result = await this.loadingPromises.get(url);
43385
+ return result.scene;
43386
+ }
43387
+ const loadPromise = this.objLoader.loadAsync(url).then((group) => {
43388
+ const result = {
43389
+ scene: group,
43390
+ animations: []
43391
+ };
43392
+ this.modelCache.set(url, result);
43393
+ this.loadingPromises.delete(url);
43394
+ return result;
43395
+ }).catch((error) => {
43396
+ this.loadingPromises.delete(url);
43397
+ throw new Error(`Failed to load OBJ ${url}: ${error.message}`);
43398
+ });
43399
+ this.loadingPromises.set(url, loadPromise);
43400
+ return (await loadPromise).scene;
43401
+ }
43402
+ /**
43403
+ * Load a texture
43404
+ * @param url - URL to the texture image
43405
+ * @returns Promise with loaded texture
43406
+ */
43407
+ async loadTexture(url) {
43408
+ if (this.textureCache.has(url)) {
43409
+ return this.textureCache.get(url);
43410
+ }
43411
+ if (this.loadingPromises.has(`texture:${url}`)) {
43412
+ return this.loadingPromises.get(`texture:${url}`);
43413
+ }
43414
+ const loadPromise = this.textureLoader.loadAsync(url).then((texture) => {
43415
+ texture.colorSpace = THREE.SRGBColorSpace;
43416
+ this.textureCache.set(url, texture);
43417
+ this.loadingPromises.delete(`texture:${url}`);
43418
+ return texture;
43419
+ }).catch((error) => {
43420
+ this.loadingPromises.delete(`texture:${url}`);
43421
+ throw new Error(`Failed to load texture ${url}: ${error.message}`);
43422
+ });
43423
+ this.loadingPromises.set(`texture:${url}`, loadPromise);
43424
+ return loadPromise;
43425
+ }
43426
+ /**
43427
+ * Preload multiple assets
43428
+ * @param urls - Array of asset URLs to preload
43429
+ * @returns Promise that resolves when all assets are loaded
43430
+ */
43431
+ async preload(urls) {
43432
+ const promises = urls.map((url) => {
43433
+ if (url.endsWith(".glb") || url.endsWith(".gltf")) {
43434
+ return this.loadModel(url).catch(() => null);
43435
+ } else if (url.endsWith(".obj")) {
43436
+ return this.loadOBJ(url).catch(() => null);
43437
+ } else if (/\.(png|jpg|jpeg|webp)$/i.test(url)) {
43438
+ return this.loadTexture(url).catch(() => null);
43439
+ }
43440
+ return Promise.resolve(null);
43441
+ });
43442
+ await Promise.all(promises);
43443
+ }
43444
+ /**
43445
+ * Check if a model is cached
43446
+ * @param url - Model URL
43447
+ */
43448
+ hasModel(url) {
43449
+ return this.modelCache.has(url);
43450
+ }
43451
+ /**
43452
+ * Check if a texture is cached
43453
+ * @param url - Texture URL
43454
+ */
43455
+ hasTexture(url) {
43456
+ return this.textureCache.has(url);
43457
+ }
43458
+ /**
43459
+ * Get cached model (throws if not cached)
43460
+ * @param url - Model URL
43461
+ */
43462
+ getModel(url) {
43463
+ const model = this.modelCache.get(url);
43464
+ if (!model) {
43465
+ throw new Error(`Model ${url} not in cache`);
43466
+ }
43467
+ return model;
43468
+ }
43469
+ /**
43470
+ * Get cached texture (throws if not cached)
43471
+ * @param url - Texture URL
43472
+ */
43473
+ getTexture(url) {
43474
+ const texture = this.textureCache.get(url);
43475
+ if (!texture) {
43476
+ throw new Error(`Texture ${url} not in cache`);
43477
+ }
43478
+ return texture;
43479
+ }
43480
+ /**
43481
+ * Clear all caches
43482
+ */
43483
+ clearCache() {
43484
+ this.textureCache.forEach((texture) => {
43485
+ texture.dispose();
43486
+ });
43487
+ this.modelCache.forEach((model) => {
43488
+ model.scene.traverse((child) => {
43489
+ if (child instanceof THREE.Mesh) {
43490
+ child.geometry.dispose();
43491
+ if (Array.isArray(child.material)) {
43492
+ child.material.forEach((m) => m.dispose());
43493
+ } else {
43494
+ child.material.dispose();
43495
+ }
43496
+ }
43497
+ });
43498
+ });
43499
+ this.modelCache.clear();
43500
+ this.textureCache.clear();
43501
+ this.loadingPromises.clear();
43502
+ }
43503
+ /**
43504
+ * Get cache statistics
43505
+ */
43506
+ getStats() {
43507
+ return {
43508
+ models: this.modelCache.size,
43509
+ textures: this.textureCache.size,
43510
+ loading: this.loadingPromises.size
43511
+ };
43512
+ }
43513
+ };
43514
+ new AssetLoader();
43515
+ }
43516
+ });
43517
+ function useAssetLoader(options = {}) {
43518
+ const { preloadUrls = [], loader: customLoader } = options;
43519
+ const loaderRef = useRef(customLoader || new AssetLoader());
43520
+ const [state, setState] = useState({
43521
+ isLoading: false,
43522
+ progress: 0,
43523
+ loaded: 0,
43524
+ total: 0,
43525
+ errors: []
43526
+ });
43527
+ useEffect(() => {
43528
+ if (preloadUrls.length > 0) {
43529
+ preload(preloadUrls);
43530
+ }
43531
+ }, []);
43532
+ const updateProgress = useCallback((loaded, total) => {
43533
+ setState((prev) => ({
43534
+ ...prev,
43535
+ loaded,
43536
+ total,
43537
+ progress: total > 0 ? Math.round(loaded / total * 100) : 0
43538
+ }));
43539
+ }, []);
43540
+ const loadModel = useCallback(
43541
+ async (url) => {
43542
+ setState((prev) => ({ ...prev, isLoading: true }));
43543
+ try {
43544
+ const model = await loaderRef.current.loadModel(url);
43545
+ setState((prev) => ({
43546
+ ...prev,
43547
+ isLoading: false,
43548
+ loaded: prev.loaded + 1
43549
+ }));
43550
+ return model;
43551
+ } catch (error) {
43552
+ const errorMsg = error instanceof Error ? error.message : String(error);
43553
+ setState((prev) => ({
43554
+ ...prev,
43555
+ isLoading: false,
43556
+ errors: [...prev.errors, errorMsg]
43557
+ }));
43558
+ throw error;
43559
+ }
43560
+ },
43561
+ []
43562
+ );
43563
+ const loadOBJ = useCallback(
43564
+ async (url) => {
43565
+ setState((prev) => ({ ...prev, isLoading: true }));
43566
+ try {
43567
+ const model = await loaderRef.current.loadOBJ(url);
43568
+ setState((prev) => ({
43569
+ ...prev,
43570
+ isLoading: false,
43571
+ loaded: prev.loaded + 1
43572
+ }));
43573
+ return model;
43574
+ } catch (error) {
43575
+ const errorMsg = error instanceof Error ? error.message : String(error);
43576
+ setState((prev) => ({
43577
+ ...prev,
43578
+ isLoading: false,
43579
+ errors: [...prev.errors, errorMsg]
43580
+ }));
43581
+ throw error;
43582
+ }
43583
+ },
43584
+ []
43585
+ );
43586
+ const loadTexture = useCallback(
43587
+ async (url) => {
43588
+ setState((prev) => ({ ...prev, isLoading: true }));
43589
+ try {
43590
+ const texture = await loaderRef.current.loadTexture(url);
43591
+ setState((prev) => ({
43592
+ ...prev,
43593
+ isLoading: false,
43594
+ loaded: prev.loaded + 1
43595
+ }));
43596
+ return texture;
43597
+ } catch (error) {
43598
+ const errorMsg = error instanceof Error ? error.message : String(error);
43599
+ setState((prev) => ({
43600
+ ...prev,
43601
+ isLoading: false,
43602
+ errors: [...prev.errors, errorMsg]
43603
+ }));
43604
+ throw error;
43605
+ }
43606
+ },
43607
+ []
43608
+ );
43609
+ const preload = useCallback(
43610
+ async (urls) => {
43611
+ setState((prev) => ({
43612
+ ...prev,
43613
+ isLoading: true,
43614
+ total: urls.length,
43615
+ loaded: 0,
43616
+ errors: []
43617
+ }));
43618
+ let completed = 0;
43619
+ const errors = [];
43620
+ await Promise.all(
43621
+ urls.map(async (url) => {
43622
+ try {
43623
+ if (url.endsWith(".glb") || url.endsWith(".gltf")) {
43624
+ await loaderRef.current.loadModel(url);
43625
+ } else if (url.endsWith(".obj")) {
43626
+ await loaderRef.current.loadOBJ(url);
43627
+ } else if (/\.(png|jpg|jpeg|webp)$/i.test(url)) {
43628
+ await loaderRef.current.loadTexture(url);
43629
+ }
43630
+ completed++;
43631
+ updateProgress(completed, urls.length);
43632
+ } catch (error) {
43633
+ const errorMsg = error instanceof Error ? error.message : String(error);
43634
+ errors.push(`${url}: ${errorMsg}`);
43635
+ completed++;
43636
+ updateProgress(completed, urls.length);
43637
+ }
43638
+ })
43639
+ );
43640
+ setState((prev) => ({
43641
+ ...prev,
43642
+ isLoading: false,
43643
+ errors
43644
+ }));
43645
+ },
43646
+ [updateProgress]
43647
+ );
43648
+ const hasModel = useCallback((url) => {
43649
+ return loaderRef.current.hasModel(url);
43650
+ }, []);
43651
+ const hasTexture = useCallback((url) => {
43652
+ return loaderRef.current.hasTexture(url);
43653
+ }, []);
43654
+ const getModel = useCallback((url) => {
43655
+ try {
43656
+ return loaderRef.current.getModel(url);
43657
+ } catch {
43658
+ return void 0;
43659
+ }
43660
+ }, []);
43661
+ const getTexture = useCallback((url) => {
43662
+ try {
43663
+ return loaderRef.current.getTexture(url);
43664
+ } catch {
43665
+ return void 0;
43666
+ }
43667
+ }, []);
43668
+ const clearCache = useCallback(() => {
43669
+ loaderRef.current.clearCache();
43670
+ setState({
43671
+ isLoading: false,
43672
+ progress: 0,
43673
+ loaded: 0,
43674
+ total: 0,
43675
+ errors: []
43676
+ });
43677
+ }, []);
43678
+ return {
43679
+ ...state,
43680
+ loadModel,
43681
+ loadOBJ,
43682
+ loadTexture,
43683
+ preload,
43684
+ hasModel,
43685
+ hasTexture,
43686
+ getModel,
43687
+ getTexture,
43688
+ clearCache
43689
+ };
43690
+ }
43691
+ var init_useAssetLoader = __esm({
43692
+ "components/game/molecules/three/hooks/useAssetLoader.ts"() {
43693
+ "use client";
43694
+ init_AssetLoader();
43695
+ }
43696
+ });
43697
+ function useGameCanvas3DEvents(options) {
43698
+ const {
43699
+ tileClickEvent,
43700
+ unitClickEvent,
43701
+ featureClickEvent,
43702
+ canvasClickEvent,
43703
+ tileHoverEvent,
43704
+ tileLeaveEvent,
43705
+ unitAnimationEvent,
43706
+ cameraChangeEvent,
43707
+ onTileClick,
43708
+ onUnitClick,
43709
+ onFeatureClick,
43710
+ onCanvasClick,
43711
+ onTileHover,
43712
+ onUnitAnimation
43713
+ } = options;
43714
+ const emit = useEmitEvent();
43715
+ const optionsRef = useRef(options);
43716
+ optionsRef.current = options;
43717
+ const handleTileClick = useCallback(
43718
+ (tile, event) => {
43719
+ if (tileClickEvent) {
43720
+ emit(tileClickEvent, {
43721
+ tileId: tile.id,
43722
+ x: tile.x,
43723
+ z: tile.z ?? tile.y ?? 0,
43724
+ type: tile.type,
43725
+ terrain: tile.terrain,
43726
+ elevation: tile.elevation
43727
+ });
43728
+ }
43729
+ optionsRef.current.onTileClick?.(tile, event);
43730
+ },
43731
+ [tileClickEvent, emit]
43732
+ );
43733
+ const handleUnitClick = useCallback(
43734
+ (unit, event) => {
43735
+ if (unitClickEvent) {
43736
+ emit(unitClickEvent, {
43737
+ unitId: unit.id,
43738
+ x: unit.x,
43739
+ z: unit.z ?? unit.y ?? 0,
43740
+ unitType: unit.unitType,
43741
+ name: unit.name,
43742
+ team: unit.team,
43743
+ faction: unit.faction,
43744
+ health: unit.health,
43745
+ maxHealth: unit.maxHealth
43746
+ });
43747
+ }
43748
+ optionsRef.current.onUnitClick?.(unit, event);
43749
+ },
43750
+ [unitClickEvent, emit]
43751
+ );
43752
+ const handleFeatureClick = useCallback(
43753
+ (feature, event) => {
43754
+ if (featureClickEvent) {
43755
+ emit(featureClickEvent, {
43756
+ featureId: feature.id,
43757
+ x: feature.x,
43758
+ z: feature.z ?? feature.y ?? 0,
43759
+ type: feature.type,
43760
+ elevation: feature.elevation
43761
+ });
43762
+ }
43763
+ optionsRef.current.onFeatureClick?.(feature, event);
43764
+ },
43765
+ [featureClickEvent, emit]
43766
+ );
43767
+ const handleCanvasClick = useCallback(
43768
+ (event) => {
43769
+ if (canvasClickEvent) {
43770
+ emit(canvasClickEvent, {
43771
+ clientX: event.clientX,
43772
+ clientY: event.clientY,
43773
+ button: event.button
43774
+ });
43775
+ }
43776
+ optionsRef.current.onCanvasClick?.(event);
43777
+ },
43778
+ [canvasClickEvent, emit]
43779
+ );
43780
+ const handleTileHover = useCallback(
43781
+ (tile, event) => {
43782
+ if (tile) {
43783
+ if (tileHoverEvent) {
43784
+ emit(tileHoverEvent, {
43785
+ tileId: tile.id,
43786
+ x: tile.x,
43787
+ z: tile.z ?? tile.y ?? 0,
43788
+ type: tile.type
43789
+ });
43790
+ }
43791
+ } else {
43792
+ if (tileLeaveEvent) {
43793
+ emit(tileLeaveEvent, {});
43794
+ }
43795
+ }
43796
+ optionsRef.current.onTileHover?.(tile, event);
43797
+ },
43798
+ [tileHoverEvent, tileLeaveEvent, emit]
43799
+ );
43800
+ const handleUnitAnimation = useCallback(
43801
+ (unitId, state) => {
43802
+ if (unitAnimationEvent) {
43803
+ emit(unitAnimationEvent, {
43804
+ unitId,
43805
+ state,
43806
+ timestamp: Date.now()
43807
+ });
43808
+ }
43809
+ optionsRef.current.onUnitAnimation?.(unitId, state);
43810
+ },
43811
+ [unitAnimationEvent, emit]
43812
+ );
43813
+ const handleCameraChange = useCallback(
43814
+ (position) => {
43815
+ if (cameraChangeEvent) {
43816
+ emit(cameraChangeEvent, {
43817
+ position,
43818
+ timestamp: Date.now()
43819
+ });
43820
+ }
43821
+ },
43822
+ [cameraChangeEvent, emit]
43823
+ );
43824
+ return {
43825
+ handleTileClick,
43826
+ handleUnitClick,
43827
+ handleFeatureClick,
43828
+ handleCanvasClick,
43829
+ handleTileHover,
43830
+ handleUnitAnimation,
43831
+ handleCameraChange
43832
+ };
43833
+ }
43834
+ var init_useGameCanvas3DEvents = __esm({
43835
+ "components/game/molecules/three/hooks/useGameCanvas3DEvents.ts"() {
43836
+ "use client";
43837
+ init_useEventBus();
43838
+ }
43839
+ });
43840
+
43841
+ // components/game/molecules/three/components/Canvas3DLoadingState.css
43842
+ var init_Canvas3DLoadingState = __esm({
43843
+ "components/game/molecules/three/components/Canvas3DLoadingState.css"() {
43844
+ }
43845
+ });
43846
+ function Canvas3DLoadingState({
43847
+ progress = 0,
43848
+ loaded = 0,
43849
+ total = 0,
43850
+ message = "Loading 3D Scene...",
43851
+ details,
43852
+ showSpinner = true,
43853
+ className
43854
+ }) {
43855
+ const clampedProgress = Math.max(0, Math.min(100, progress));
43856
+ const hasProgress = total > 0;
43857
+ return /* @__PURE__ */ jsxs("div", { className: `canvas-3d-loading ${className || ""}`, children: [
43858
+ /* @__PURE__ */ jsxs("div", { className: "canvas-3d-loading__content", children: [
43859
+ showSpinner && /* @__PURE__ */ jsxs("div", { className: "canvas-3d-loading__spinner", children: [
43860
+ /* @__PURE__ */ jsx("div", { className: "spinner__ring" }),
43861
+ /* @__PURE__ */ jsx("div", { className: "spinner__ring spinner__ring--secondary" })
43862
+ ] }),
43863
+ /* @__PURE__ */ jsx("div", { className: "canvas-3d-loading__message", children: message }),
43864
+ details && /* @__PURE__ */ jsx("div", { className: "canvas-3d-loading__details", children: details }),
43865
+ hasProgress && /* @__PURE__ */ jsxs("div", { className: "canvas-3d-loading__progress", children: [
43866
+ /* @__PURE__ */ jsx("div", { className: "progress__bar", children: /* @__PURE__ */ jsx(
43867
+ "div",
43868
+ {
43869
+ className: "progress__fill",
43870
+ style: { width: `${clampedProgress}%` }
43871
+ }
43872
+ ) }),
43873
+ /* @__PURE__ */ jsxs("div", { className: "progress__text", children: [
43874
+ /* @__PURE__ */ jsxs("span", { className: "progress__percentage", children: [
43875
+ clampedProgress,
43876
+ "%"
43877
+ ] }),
43878
+ /* @__PURE__ */ jsxs("span", { className: "progress__count", children: [
43879
+ "(",
43880
+ loaded,
43881
+ "/",
43882
+ total,
43883
+ ")"
43884
+ ] })
43885
+ ] })
43886
+ ] })
43887
+ ] }),
43888
+ /* @__PURE__ */ jsx("div", { className: "canvas-3d-loading__background", children: /* @__PURE__ */ jsx("div", { className: "bg__grid" }) })
43889
+ ] });
43890
+ }
43891
+ var init_Canvas3DLoadingState2 = __esm({
43892
+ "components/game/molecules/three/components/Canvas3DLoadingState.tsx"() {
43893
+ init_Canvas3DLoadingState();
43894
+ }
43895
+ });
43896
+
43897
+ // components/game/molecules/three/components/Canvas3DErrorBoundary.css
43898
+ var init_Canvas3DErrorBoundary = __esm({
43899
+ "components/game/molecules/three/components/Canvas3DErrorBoundary.css"() {
43900
+ }
43901
+ });
43902
+ var log9, Canvas3DErrorBoundary;
43903
+ var init_Canvas3DErrorBoundary2 = __esm({
43904
+ "components/game/molecules/three/components/Canvas3DErrorBoundary.tsx"() {
43905
+ init_Canvas3DErrorBoundary();
43906
+ log9 = createLogger("almadar:ui:game:canvas3d:error-boundary");
43907
+ Canvas3DErrorBoundary = class extends Component {
43908
+ constructor(props) {
43909
+ super(props);
43910
+ __publicField(this, "handleReset", () => {
43911
+ this.setState({
43912
+ hasError: false,
43913
+ error: null,
43914
+ errorInfo: null
43915
+ });
43916
+ this.props.onReset?.();
43917
+ });
43918
+ this.state = {
43919
+ hasError: false,
43920
+ error: null,
43921
+ errorInfo: null
43922
+ };
43923
+ }
43924
+ static getDerivedStateFromError(error) {
43925
+ return {
43926
+ hasError: true,
43927
+ error,
43928
+ errorInfo: null
43929
+ };
43930
+ }
43931
+ componentDidCatch(error, errorInfo) {
43932
+ this.setState({ errorInfo });
43933
+ this.props.onError?.(error, errorInfo);
43934
+ log9.error("Error caught", { error });
43935
+ log9.error("Component stack", { componentStack: errorInfo.componentStack ?? "<none>" });
43936
+ }
43937
+ render() {
43938
+ if (this.state.hasError) {
43939
+ if (this.props.fallback) {
43940
+ return this.props.fallback;
43941
+ }
43942
+ return /* @__PURE__ */ jsx("div", { className: "canvas-3d-error", children: /* @__PURE__ */ jsxs("div", { className: "canvas-3d-error__content", children: [
43943
+ /* @__PURE__ */ jsx("div", { className: "canvas-3d-error__icon", children: "\u26A0\uFE0F" }),
43944
+ /* @__PURE__ */ jsx("h2", { className: "canvas-3d-error__title", children: "3D Scene Error" }),
43945
+ /* @__PURE__ */ jsx("p", { className: "canvas-3d-error__message", children: "Something went wrong while rendering the 3D scene." }),
43946
+ this.state.error && /* @__PURE__ */ jsxs("details", { className: "canvas-3d-error__details", children: [
43947
+ /* @__PURE__ */ jsx("summary", { children: "Error Details" }),
43948
+ /* @__PURE__ */ jsxs("pre", { className: "error__stack", children: [
43949
+ this.state.error.message,
43950
+ "\n",
43951
+ this.state.error.stack
43952
+ ] }),
43953
+ this.state.errorInfo && /* @__PURE__ */ jsx("pre", { className: "error__component-stack", children: this.state.errorInfo.componentStack })
43954
+ ] }),
43955
+ /* @__PURE__ */ jsxs("div", { className: "canvas-3d-error__actions", children: [
43956
+ /* @__PURE__ */ jsx(
43957
+ "button",
43958
+ {
43959
+ className: "error__button error__button--primary",
43960
+ onClick: this.handleReset,
43961
+ children: "Try Again"
43962
+ }
43963
+ ),
43964
+ /* @__PURE__ */ jsx(
43965
+ "button",
43966
+ {
43967
+ className: "error__button error__button--secondary",
43968
+ onClick: () => window.location.reload(),
43969
+ children: "Reload Page"
43970
+ }
43971
+ )
43972
+ ] })
43973
+ ] }) });
43974
+ }
43975
+ return this.props.children;
43976
+ }
43977
+ };
43978
+ }
43979
+ });
43980
+ function detectAssetRoot2(modelUrl) {
43981
+ const idx = modelUrl.indexOf("/3d/");
43982
+ if (idx !== -1) {
43983
+ return modelUrl.substring(0, idx + 4);
43984
+ }
43985
+ return modelUrl.substring(0, modelUrl.lastIndexOf("/") + 1);
43986
+ }
43987
+ function useGLTFModel(url, resourceBasePath) {
43988
+ const [state, setState] = useState({
43989
+ model: null,
43990
+ isLoading: false,
43991
+ error: null
43992
+ });
43993
+ useEffect(() => {
43994
+ if (!url) {
43995
+ setState({ model: null, isLoading: false, error: null });
43996
+ return;
43997
+ }
43998
+ log10.debug("Loading", { url });
43999
+ setState((prev) => ({ ...prev, isLoading: true, error: null }));
44000
+ const assetRoot = resourceBasePath || detectAssetRoot2(url);
44001
+ const loader = new GLTFLoader$1();
44002
+ loader.setResourcePath(assetRoot);
44003
+ loader.load(
44004
+ url,
44005
+ (gltf) => {
44006
+ log10.debug("Loaded", { url });
44007
+ setState({
44008
+ model: gltf.scene,
44009
+ isLoading: false,
44010
+ error: null
44011
+ });
44012
+ },
44013
+ void 0,
44014
+ (err) => {
44015
+ log10.warn("Failed", { url, error: err instanceof Error ? err : String(err) });
44016
+ setState({
44017
+ model: null,
44018
+ isLoading: false,
44019
+ error: err instanceof Error ? err : new Error(String(err))
44020
+ });
44021
+ }
44022
+ );
44023
+ }, [url, resourceBasePath]);
44024
+ return state;
44025
+ }
44026
+ function ModelLoader({
44027
+ url,
44028
+ position = [0, 0, 0],
44029
+ scale = 1,
44030
+ rotation = [0, 0, 0],
44031
+ isSelected = false,
44032
+ isHovered = false,
44033
+ onClick,
44034
+ onHover,
44035
+ fallbackGeometry = "box",
44036
+ castShadow = true,
44037
+ receiveShadow = true,
44038
+ resourceBasePath
44039
+ }) {
44040
+ const { model: loadedModel, isLoading, error } = useGLTFModel(url, resourceBasePath);
44041
+ const model = useMemo(() => {
44042
+ if (!loadedModel) return null;
44043
+ const cloned = loadedModel.clone();
44044
+ cloned.traverse((child) => {
44045
+ if (child instanceof THREE.Mesh) {
44046
+ child.castShadow = castShadow;
44047
+ child.receiveShadow = receiveShadow;
44048
+ }
44049
+ });
44050
+ return cloned;
44051
+ }, [loadedModel, castShadow, receiveShadow]);
44052
+ const scaleArray = useMemo(() => {
44053
+ if (typeof scale === "number") {
44054
+ return [scale, scale, scale];
44055
+ }
44056
+ return scale;
44057
+ }, [scale]);
44058
+ const rotationRad = useMemo(() => {
44059
+ return [
44060
+ rotation[0] * Math.PI / 180,
44061
+ rotation[1] * Math.PI / 180,
44062
+ rotation[2] * Math.PI / 180
44063
+ ];
44064
+ }, [rotation]);
44065
+ if (isLoading) {
44066
+ return /* @__PURE__ */ jsx("group", { position, children: /* @__PURE__ */ jsxs("mesh", { rotation: [Math.PI / 2, 0, 0], children: [
44067
+ /* @__PURE__ */ jsx("ringGeometry", { args: [0.3, 0.35, 16] }),
44068
+ /* @__PURE__ */ jsx("meshBasicMaterial", { color: "#4a90d9", transparent: true, opacity: 0.8 })
44069
+ ] }) });
44070
+ }
44071
+ if (error || !model) {
44072
+ if (fallbackGeometry === "none") {
44073
+ return /* @__PURE__ */ jsx("group", { position });
44074
+ }
44075
+ const fallbackProps = {
44076
+ onClick,
44077
+ onPointerOver: () => onHover?.(true),
44078
+ onPointerOut: () => onHover?.(false)
44079
+ };
44080
+ return /* @__PURE__ */ jsxs("group", { position, children: [
44081
+ (isSelected || isHovered) && /* @__PURE__ */ jsxs("mesh", { position: [0, 0.02, 0], rotation: [-Math.PI / 2, 0, 0], children: [
44082
+ /* @__PURE__ */ jsx("ringGeometry", { args: [0.6, 0.7, 32] }),
44083
+ /* @__PURE__ */ jsx(
44084
+ "meshBasicMaterial",
44085
+ {
44086
+ color: isSelected ? 16755200 : 16777215,
44087
+ transparent: true,
44088
+ opacity: 0.5
44089
+ }
44090
+ )
44091
+ ] }),
44092
+ fallbackGeometry === "box" && /* @__PURE__ */ jsxs("mesh", { ...fallbackProps, position: [0, 0.5, 0], children: [
44093
+ /* @__PURE__ */ jsx("boxGeometry", { args: [0.8, 0.8, 0.8] }),
44094
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color: error ? 16729156 : 8947848 })
44095
+ ] }),
44096
+ fallbackGeometry === "sphere" && /* @__PURE__ */ jsxs("mesh", { ...fallbackProps, position: [0, 0.5, 0], children: [
44097
+ /* @__PURE__ */ jsx("sphereGeometry", { args: [0.4, 16, 16] }),
44098
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color: error ? 16729156 : 8947848 })
44099
+ ] }),
44100
+ fallbackGeometry === "cylinder" && /* @__PURE__ */ jsxs("mesh", { ...fallbackProps, position: [0, 0.5, 0], children: [
44101
+ /* @__PURE__ */ jsx("cylinderGeometry", { args: [0.3, 0.3, 0.8, 16] }),
44102
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color: error ? 16729156 : 8947848 })
44103
+ ] })
44104
+ ] });
44105
+ }
44106
+ return /* @__PURE__ */ jsxs(
44107
+ "group",
44108
+ {
44109
+ position,
44110
+ rotation: rotationRad,
44111
+ onClick,
44112
+ onPointerOver: () => onHover?.(true),
44113
+ onPointerOut: () => onHover?.(false),
44114
+ children: [
44115
+ (isSelected || isHovered) && /* @__PURE__ */ jsxs("mesh", { position: [0, 0.02, 0], rotation: [-Math.PI / 2, 0, 0], children: [
44116
+ /* @__PURE__ */ jsx("ringGeometry", { args: [0.6, 0.7, 32] }),
44117
+ /* @__PURE__ */ jsx(
44118
+ "meshBasicMaterial",
44119
+ {
44120
+ color: isSelected ? 16755200 : 16777215,
44121
+ transparent: true,
44122
+ opacity: 0.5
44123
+ }
44124
+ )
44125
+ ] }),
44126
+ /* @__PURE__ */ jsx("primitive", { object: model, scale: scaleArray })
44127
+ ]
44128
+ }
44129
+ );
44130
+ }
44131
+ var log10;
44132
+ var init_ModelLoader = __esm({
44133
+ "components/game/molecules/three/components/ModelLoader.tsx"() {
44134
+ "use client";
44135
+ log10 = createLogger("almadar:ui:game:model-loader");
44136
+ }
44137
+ });
44138
+
44139
+ // components/game/molecules/GameCanvas3D.css
44140
+ var init_GameCanvas3D = __esm({
44141
+ "components/game/molecules/GameCanvas3D.css"() {
44142
+ }
44143
+ });
44144
+ function CameraController({
44145
+ onCameraChange
44146
+ }) {
44147
+ const { camera } = useThree();
44148
+ useEffect(() => {
44149
+ if (onCameraChange) {
44150
+ onCameraChange({
44151
+ x: camera.position.x,
44152
+ y: camera.position.y,
44153
+ z: camera.position.z
44154
+ });
44155
+ }
44156
+ }, [camera.position, onCameraChange]);
44157
+ return null;
44158
+ }
44159
+ var DEFAULT_GRID_CONFIG, GameCanvas3D;
44160
+ var init_GameCanvas3D2 = __esm({
44161
+ "components/game/molecules/GameCanvas3D.tsx"() {
44162
+ "use client";
44163
+ init_AssetLoader();
44164
+ init_useAssetLoader();
44165
+ init_useGameCanvas3DEvents();
44166
+ init_Canvas3DLoadingState2();
44167
+ init_Canvas3DErrorBoundary2();
44168
+ init_ModelLoader();
44169
+ init_cn();
44170
+ init_GameCanvas3D();
44171
+ DEFAULT_GRID_CONFIG = {
44172
+ cellSize: 1,
44173
+ offsetX: 0,
44174
+ offsetZ: 0
44175
+ };
44176
+ GameCanvas3D = forwardRef(
44177
+ ({
44178
+ tiles = [],
44179
+ units = [],
44180
+ features = [],
44181
+ events: events2 = [],
44182
+ orientation = "standard",
44183
+ cameraMode = "isometric",
44184
+ showGrid = true,
44185
+ showCoordinates = false,
44186
+ showTileInfo = false,
44187
+ overlay = "default",
44188
+ shadows = true,
44189
+ backgroundColor = "#1a1a2e",
44190
+ onTileClick,
44191
+ onUnitClick,
44192
+ onFeatureClick,
44193
+ onCanvasClick,
44194
+ onTileHover,
44195
+ onUnitAnimation,
44196
+ assetLoader: customAssetLoader,
44197
+ tileRenderer: CustomTileRenderer,
44198
+ unitRenderer: CustomUnitRenderer,
44199
+ featureRenderer: CustomFeatureRenderer,
44200
+ className,
44201
+ isLoading: externalLoading,
44202
+ error: externalError,
44203
+ entity,
44204
+ preloadAssets = [],
44205
+ tileClickEvent,
44206
+ unitClickEvent,
44207
+ featureClickEvent,
44208
+ canvasClickEvent,
44209
+ tileHoverEvent,
44210
+ tileLeaveEvent,
44211
+ unitAnimationEvent,
44212
+ cameraChangeEvent,
44213
+ loadingMessage = "Loading 3D Scene...",
44214
+ useInstancing = true,
44215
+ validMoves = [],
44216
+ attackTargets = [],
44217
+ selectedTileIds = [],
44218
+ selectedUnitId = null,
44219
+ children
44220
+ }, ref) => {
44221
+ const containerRef = useRef(null);
44222
+ const controlsRef = useRef(null);
44223
+ const [hoveredTile, setHoveredTile] = useState(null);
44224
+ const [internalError, setInternalError] = useState(null);
44225
+ const { isLoading: assetsLoading, progress, loaded, total } = useAssetLoader({
44226
+ preloadUrls: preloadAssets,
44227
+ loader: customAssetLoader
44228
+ });
44229
+ const eventHandlers = useGameCanvas3DEvents({
44230
+ tileClickEvent,
44231
+ unitClickEvent,
44232
+ featureClickEvent,
44233
+ canvasClickEvent,
44234
+ tileHoverEvent,
44235
+ tileLeaveEvent,
44236
+ unitAnimationEvent,
44237
+ cameraChangeEvent,
44238
+ onTileClick,
44239
+ onUnitClick,
44240
+ onFeatureClick,
44241
+ onCanvasClick,
44242
+ onTileHover,
44243
+ onUnitAnimation
44244
+ });
44245
+ const gridBounds = useMemo(() => {
44246
+ if (tiles.length === 0) {
44247
+ return { minX: 0, maxX: 10, minZ: 0, maxZ: 10 };
44248
+ }
44249
+ const xs = tiles.map((t) => t.x);
44250
+ const zs = tiles.map((t) => t.z || t.y || 0);
44251
+ return {
44252
+ minX: Math.min(...xs),
44253
+ maxX: Math.max(...xs),
44254
+ minZ: Math.min(...zs),
44255
+ maxZ: Math.max(...zs)
44256
+ };
44257
+ }, [tiles]);
44258
+ const cameraTarget = useMemo(() => {
44259
+ return [
44260
+ (gridBounds.minX + gridBounds.maxX) / 2,
44261
+ 0,
44262
+ (gridBounds.minZ + gridBounds.maxZ) / 2
44263
+ ];
44264
+ }, [gridBounds]);
44265
+ const gridConfig = useMemo(
44266
+ () => ({
44267
+ ...DEFAULT_GRID_CONFIG,
44268
+ offsetX: -(gridBounds.maxX - gridBounds.minX) / 2,
44269
+ offsetZ: -(gridBounds.maxZ - gridBounds.minZ) / 2
44270
+ }),
44271
+ [gridBounds]
44272
+ );
44273
+ const gridToWorld = useCallback(
44274
+ (x, z, y = 0) => {
44275
+ const worldX = (x - gridBounds.minX) * gridConfig.cellSize;
44276
+ const worldZ = (z - gridBounds.minZ) * gridConfig.cellSize;
44277
+ return [worldX, y * gridConfig.cellSize, worldZ];
44278
+ },
44279
+ [gridBounds, gridConfig]
44280
+ );
44281
+ useImperativeHandle(ref, () => ({
44282
+ getCameraPosition: () => {
44283
+ if (controlsRef.current) {
44284
+ const pos = controlsRef.current.object.position;
44285
+ return new THREE.Vector3(pos.x, pos.y, pos.z);
44286
+ }
44287
+ return null;
44288
+ },
44289
+ setCameraPosition: (x, y, z) => {
44290
+ if (controlsRef.current) {
44291
+ controlsRef.current.object.position.set(x, y, z);
44292
+ controlsRef.current.update();
44293
+ }
44294
+ },
44295
+ lookAt: (x, y, z) => {
44296
+ if (controlsRef.current) {
44297
+ controlsRef.current.target.set(x, y, z);
44298
+ controlsRef.current.update();
44299
+ }
44300
+ },
44301
+ resetCamera: () => {
44302
+ if (controlsRef.current) {
44303
+ controlsRef.current.reset();
44304
+ }
44305
+ },
44306
+ screenshot: () => {
44307
+ const canvas = containerRef.current?.querySelector("canvas");
44308
+ if (canvas) {
44309
+ return canvas.toDataURL("image/png");
44310
+ }
44311
+ return null;
44312
+ },
44313
+ export: () => ({
44314
+ tiles,
44315
+ units,
44316
+ features
44317
+ })
44318
+ }));
44319
+ const handleTileClick = useCallback(
44320
+ (tile, event) => {
44321
+ eventHandlers.handleTileClick(tile, event);
44322
+ },
44323
+ [eventHandlers]
44324
+ );
44325
+ const handleUnitClick = useCallback(
44326
+ (unit, event) => {
44327
+ eventHandlers.handleUnitClick(unit, event);
44328
+ },
44329
+ [eventHandlers]
44330
+ );
44331
+ const handleFeatureClick = useCallback(
44332
+ (feature, event) => {
44333
+ if (event) {
44334
+ eventHandlers.handleFeatureClick(feature, event);
44335
+ }
44336
+ },
44337
+ [eventHandlers]
44338
+ );
44339
+ const handleTileHover = useCallback(
44340
+ (tile, event) => {
44341
+ setHoveredTile(tile);
44342
+ if (event) {
44343
+ eventHandlers.handleTileHover(tile, event);
44344
+ }
44345
+ },
44346
+ [eventHandlers]
44347
+ );
44348
+ const cameraConfig = useMemo(() => {
44349
+ const size = Math.max(
44350
+ gridBounds.maxX - gridBounds.minX,
44351
+ gridBounds.maxZ - gridBounds.minZ
44352
+ );
44353
+ const distance = size * 1.5;
44354
+ switch (cameraMode) {
44355
+ case "isometric":
44356
+ return {
44357
+ position: [distance, distance * 0.8, distance],
44358
+ fov: 45
44359
+ };
44360
+ case "top-down":
44361
+ return {
44362
+ position: [0, distance * 2, 0],
44363
+ fov: 45
44364
+ };
44365
+ case "perspective":
44366
+ default:
44367
+ return {
44368
+ position: [distance, distance, distance],
44369
+ fov: 45
44370
+ };
44371
+ }
44372
+ }, [cameraMode, gridBounds]);
44373
+ const DefaultTileRenderer = useCallback(
44374
+ ({ tile, position }) => {
44375
+ const isSelected = tile.id ? selectedTileIds.includes(tile.id) : false;
44376
+ const isHovered = hoveredTile?.id === tile.id;
44377
+ const isValidMove = validMoves.some(
44378
+ (m) => m.x === tile.x && m.z === (tile.z ?? tile.y ?? 0)
44379
+ );
44380
+ const isAttackTarget = attackTargets.some(
44381
+ (m) => m.x === tile.x && m.z === (tile.z ?? tile.y ?? 0)
44382
+ );
44383
+ let color = 8421504;
44384
+ if (tile.type === "water") color = 4491468;
44385
+ else if (tile.type === "grass") color = 4500036;
44386
+ else if (tile.type === "sand") color = 14535816;
44387
+ else if (tile.type === "rock") color = 8947848;
44388
+ else if (tile.type === "snow") color = 15658734;
44389
+ let emissive = 0;
44390
+ if (isSelected) emissive = 4473924;
44391
+ else if (isAttackTarget) emissive = 4456448;
44392
+ else if (isValidMove) emissive = 17408;
44393
+ else if (isHovered) emissive = 2236962;
44394
+ if (tile.modelUrl) {
44395
+ return /* @__PURE__ */ jsx(
44396
+ "group",
44397
+ {
44398
+ position,
44399
+ onClick: (e) => handleTileClick(tile, e),
44400
+ onPointerEnter: (e) => handleTileHover(tile, e),
44401
+ onPointerLeave: (e) => handleTileHover(null, e),
44402
+ userData: { type: "tile", tileId: tile.id, gridX: tile.x, gridZ: tile.z ?? tile.y },
44403
+ children: /* @__PURE__ */ jsx(
44404
+ ModelLoader,
44405
+ {
44406
+ url: tile.modelUrl,
44407
+ scale: 0.95,
44408
+ fallbackGeometry: "box",
44409
+ castShadow: true,
44410
+ receiveShadow: true
44411
+ }
44412
+ )
44413
+ }
44414
+ );
44415
+ }
44416
+ return /* @__PURE__ */ jsxs(
44417
+ "mesh",
44418
+ {
44419
+ position,
44420
+ onClick: (e) => handleTileClick(tile, e),
44421
+ onPointerEnter: (e) => handleTileHover(tile, e),
44422
+ onPointerLeave: (e) => handleTileHover(null, e),
44423
+ userData: { type: "tile", tileId: tile.id, gridX: tile.x, gridZ: tile.z ?? tile.y },
44424
+ children: [
44425
+ /* @__PURE__ */ jsx("boxGeometry", { args: [0.95, 0.2, 0.95] }),
44426
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color, emissive })
44427
+ ]
44428
+ }
44429
+ );
44430
+ },
44431
+ [selectedTileIds, hoveredTile, validMoves, attackTargets, handleTileClick, handleTileHover]
44432
+ );
44433
+ const DefaultUnitRenderer = useCallback(
44434
+ ({ unit, position }) => {
44435
+ const isSelected = selectedUnitId === unit.id;
44436
+ const color = unit.faction === "player" ? 4491519 : unit.faction === "enemy" ? 16729156 : 16777028;
44437
+ return /* @__PURE__ */ jsxs(
44438
+ "group",
44439
+ {
44440
+ position,
44441
+ onClick: (e) => handleUnitClick(unit, e),
44442
+ userData: { type: "unit", unitId: unit.id },
44443
+ children: [
44444
+ isSelected && /* @__PURE__ */ jsxs("mesh", { position: [0, 0.05, 0], rotation: [-Math.PI / 2, 0, 0], children: [
44445
+ /* @__PURE__ */ jsx("ringGeometry", { args: [0.4, 0.5, 32] }),
44446
+ /* @__PURE__ */ jsx("meshBasicMaterial", { color: "#ffff00", transparent: true, opacity: 0.8 })
44447
+ ] }),
44448
+ unit.modelUrl ? (
44449
+ /* GLB unit model (box fallback while loading / on error) */
44450
+ /* @__PURE__ */ jsx(
44451
+ ModelLoader,
44452
+ {
44453
+ url: unit.modelUrl,
44454
+ scale: 0.5,
44455
+ fallbackGeometry: "box",
44456
+ castShadow: true
44457
+ }
44458
+ )
44459
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
44460
+ /* @__PURE__ */ jsxs("mesh", { position: [0, 0.3, 0], children: [
44461
+ /* @__PURE__ */ jsx("cylinderGeometry", { args: [0.3, 0.3, 0.1, 8] }),
44462
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color })
44463
+ ] }),
44464
+ /* @__PURE__ */ jsxs("mesh", { position: [0, 0.6, 0], children: [
44465
+ /* @__PURE__ */ jsx("capsuleGeometry", { args: [0.2, 0.4, 4, 8] }),
44466
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color })
44467
+ ] }),
44468
+ /* @__PURE__ */ jsxs("mesh", { position: [0, 0.9, 0], children: [
44469
+ /* @__PURE__ */ jsx("sphereGeometry", { args: [0.12, 8, 8] }),
44470
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color })
44471
+ ] })
44472
+ ] }),
44473
+ unit.health !== void 0 && unit.maxHealth !== void 0 && /* @__PURE__ */ jsxs("group", { position: [0, 1.2, 0], children: [
44474
+ /* @__PURE__ */ jsxs("mesh", { position: [-0.25, 0, 0], children: [
44475
+ /* @__PURE__ */ jsx("planeGeometry", { args: [0.5, 0.05] }),
44476
+ /* @__PURE__ */ jsx("meshBasicMaterial", { color: 3355443 })
44477
+ ] }),
44478
+ /* @__PURE__ */ jsxs(
44479
+ "mesh",
44480
+ {
44481
+ position: [
44482
+ -0.25 + 0.5 * (unit.health / unit.maxHealth) / 2,
44483
+ 0,
44484
+ 0.01
44485
+ ],
44486
+ children: [
44487
+ /* @__PURE__ */ jsx("planeGeometry", { args: [0.5 * (unit.health / unit.maxHealth), 0.05] }),
44488
+ /* @__PURE__ */ jsx(
44489
+ "meshBasicMaterial",
44490
+ {
44491
+ color: unit.health / unit.maxHealth > 0.5 ? 4500036 : unit.health / unit.maxHealth > 0.25 ? 11184708 : 16729156
44492
+ }
44493
+ )
44494
+ ]
44495
+ }
44496
+ )
44497
+ ] })
44498
+ ]
44499
+ }
44500
+ );
44501
+ },
44502
+ [selectedUnitId, handleUnitClick]
44503
+ );
44504
+ const DefaultFeatureRenderer = useCallback(
44505
+ ({
44506
+ feature,
44507
+ position
44508
+ }) => {
44509
+ if (feature.assetUrl) {
44510
+ return /* @__PURE__ */ jsx(
44511
+ ModelLoader,
44512
+ {
44513
+ url: feature.assetUrl,
44514
+ position,
44515
+ scale: 0.5,
44516
+ rotation: [0, feature.rotation ?? 0, 0],
44517
+ onClick: () => handleFeatureClick(feature, null),
44518
+ fallbackGeometry: "box"
44519
+ },
44520
+ feature.id
44521
+ );
44522
+ }
44523
+ if (feature.type === "tree") {
44524
+ return /* @__PURE__ */ jsxs(
44525
+ "group",
44526
+ {
44527
+ position,
44528
+ onClick: (e) => handleFeatureClick(feature, e),
44529
+ userData: { type: "feature", featureId: feature.id },
44530
+ children: [
44531
+ /* @__PURE__ */ jsxs("mesh", { position: [0, 0.4, 0], children: [
44532
+ /* @__PURE__ */ jsx("cylinderGeometry", { args: [0.1, 0.15, 0.8, 6] }),
44533
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color: 9127187 })
44534
+ ] }),
44535
+ /* @__PURE__ */ jsxs("mesh", { position: [0, 0.9, 0], children: [
44536
+ /* @__PURE__ */ jsx("coneGeometry", { args: [0.5, 0.8, 8] }),
44537
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color: 2263842 })
44538
+ ] })
44539
+ ]
44540
+ }
44541
+ );
44542
+ }
44543
+ if (feature.type === "rock") {
44544
+ return /* @__PURE__ */ jsxs(
44545
+ "mesh",
44546
+ {
44547
+ position: [position[0], position[1] + 0.3, position[2]],
44548
+ onClick: (e) => handleFeatureClick(feature, e),
44549
+ userData: { type: "feature", featureId: feature.id },
44550
+ children: [
44551
+ /* @__PURE__ */ jsx("dodecahedronGeometry", { args: [0.3, 0] }),
44552
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color: 8421504 })
44553
+ ]
44554
+ }
44555
+ );
44556
+ }
44557
+ return null;
44558
+ },
44559
+ [handleFeatureClick]
44560
+ );
44561
+ if (externalLoading || assetsLoading && preloadAssets.length > 0) {
44562
+ return /* @__PURE__ */ jsx(
44563
+ Canvas3DLoadingState,
44564
+ {
44565
+ progress,
44566
+ loaded,
44567
+ total,
44568
+ message: loadingMessage,
44569
+ className
44570
+ }
44571
+ );
44572
+ }
44573
+ const displayError = externalError || internalError;
44574
+ if (displayError) {
44575
+ 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: [
44576
+ "Error: ",
44577
+ displayError
44578
+ ] }) }) });
44579
+ }
44580
+ return /* @__PURE__ */ jsx(
44581
+ Canvas3DErrorBoundary,
44582
+ {
44583
+ onError: (err) => setInternalError(err.message),
44584
+ onReset: () => setInternalError(null),
44585
+ children: /* @__PURE__ */ jsxs(
44586
+ "div",
44587
+ {
44588
+ ref: containerRef,
44589
+ className: cn("game-canvas-3d relative w-full h-full min-h-[85vh] overflow-hidden", className),
44590
+ "data-orientation": orientation,
44591
+ "data-camera-mode": cameraMode,
44592
+ "data-overlay": overlay,
44593
+ children: [
44594
+ /* @__PURE__ */ jsxs(
44595
+ Canvas,
44596
+ {
44597
+ shadows,
44598
+ camera: {
44599
+ position: cameraConfig.position,
44600
+ fov: cameraConfig.fov,
44601
+ near: 0.1,
44602
+ far: 1e3
44603
+ },
44604
+ style: { background: backgroundColor },
44605
+ onClick: (e) => {
44606
+ if (e.target === e.currentTarget) {
44607
+ eventHandlers.handleCanvasClick(e);
44608
+ }
44609
+ },
44610
+ children: [
44611
+ /* @__PURE__ */ jsx(CameraController, { onCameraChange: eventHandlers.handleCameraChange }),
44612
+ /* @__PURE__ */ jsx("ambientLight", { intensity: 0.6 }),
44613
+ /* @__PURE__ */ jsx(
44614
+ "directionalLight",
44615
+ {
44616
+ position: [10, 20, 10],
44617
+ intensity: 0.8,
44618
+ castShadow: shadows,
44619
+ "shadow-mapSize": [2048, 2048]
44620
+ }
44621
+ ),
44622
+ /* @__PURE__ */ jsx("hemisphereLight", { intensity: 0.3, color: "#87ceeb", groundColor: "#362d1d" }),
44623
+ showGrid && /* @__PURE__ */ jsx(
44624
+ Grid$1,
44625
+ {
44626
+ args: [
44627
+ Math.max(gridBounds.maxX - gridBounds.minX + 2, 10),
44628
+ Math.max(gridBounds.maxZ - gridBounds.minZ + 2, 10)
44629
+ ],
44630
+ position: [
44631
+ (gridBounds.maxX - gridBounds.minX) / 2 - 0.5,
44632
+ 0,
44633
+ (gridBounds.maxZ - gridBounds.minZ) / 2 - 0.5
44634
+ ],
44635
+ cellSize: 1,
44636
+ cellThickness: 1,
44637
+ cellColor: "#444444",
44638
+ sectionSize: 5,
44639
+ sectionThickness: 1.5,
44640
+ sectionColor: "#666666",
44641
+ fadeDistance: 50,
44642
+ fadeStrength: 1
44643
+ }
44644
+ ),
44645
+ tiles.map((tile, index) => {
44646
+ const position = gridToWorld(
44647
+ tile.x,
44648
+ tile.z ?? tile.y ?? 0,
44649
+ tile.elevation ?? 0
44650
+ );
44651
+ const Renderer = CustomTileRenderer || DefaultTileRenderer;
44652
+ return /* @__PURE__ */ jsx(Renderer, { tile, position }, tile.id ?? `tile-${index}`);
44653
+ }),
44654
+ features.map((feature, index) => {
44655
+ const position = gridToWorld(
44656
+ feature.x,
44657
+ feature.z ?? feature.y ?? 0,
44658
+ (feature.elevation ?? 0) + 0.5
44659
+ );
44660
+ const Renderer = CustomFeatureRenderer || DefaultFeatureRenderer;
44661
+ return /* @__PURE__ */ jsx(Renderer, { feature, position }, feature.id ?? `feature-${index}`);
44662
+ }),
44663
+ units.map((unit) => {
44664
+ const position = gridToWorld(
44665
+ unit.x ?? 0,
44666
+ unit.z ?? unit.y ?? 0,
44667
+ (unit.elevation ?? 0) + 0.5
44668
+ );
44669
+ const Renderer = CustomUnitRenderer || DefaultUnitRenderer;
44670
+ return /* @__PURE__ */ jsx(Renderer, { unit, position }, unit.id);
44671
+ }),
44672
+ children,
44673
+ /* @__PURE__ */ jsx(
44674
+ OrbitControls,
44675
+ {
44676
+ ref: controlsRef,
44677
+ target: cameraTarget,
44678
+ enableDamping: true,
44679
+ dampingFactor: 0.05,
44680
+ minDistance: 2,
44681
+ maxDistance: 100,
44682
+ maxPolarAngle: Math.PI / 2 - 0.1
44683
+ }
44684
+ )
44685
+ ]
44686
+ }
44687
+ ),
44688
+ showCoordinates && hoveredTile && /* @__PURE__ */ jsxs("div", { className: "game-canvas-3d__coordinates", children: [
44689
+ "X: ",
44690
+ hoveredTile.x,
44691
+ ", Z: ",
44692
+ hoveredTile.z ?? hoveredTile.y ?? 0
44693
+ ] }),
44694
+ showTileInfo && hoveredTile && /* @__PURE__ */ jsxs("div", { className: "game-canvas-3d__tile-info", children: [
44695
+ /* @__PURE__ */ jsx("div", { className: "tile-info__type", children: hoveredTile.type }),
44696
+ hoveredTile.terrain && /* @__PURE__ */ jsx("div", { className: "tile-info__terrain", children: hoveredTile.terrain })
44697
+ ] })
44698
+ ]
44699
+ }
44700
+ )
44701
+ }
44702
+ );
44703
+ }
44704
+ );
44705
+ GameCanvas3D.displayName = "GameCanvas3D";
44706
+ }
44707
+ });
44708
+ function GameBoard3D({
44709
+ entity,
44710
+ tiles = [],
44711
+ features = [],
44712
+ cameraMode = "perspective",
44713
+ backgroundColor = "#2a1a1a",
44714
+ tileClickEvent,
44715
+ unitClickEvent,
44716
+ attackEvent,
44717
+ endTurnEvent,
44718
+ cancelEvent,
44719
+ playAgainEvent,
44720
+ gameEndEvent,
44721
+ className
44722
+ }) {
44723
+ const row = boardEntity(entity);
44724
+ const eventBus = useEventBus();
44725
+ const entityUnits = row ? rows(row.units) : [];
44726
+ const units = entityUnits;
44727
+ const selectedUnitId = row ? str(row.selectedUnitId) || null : null;
44728
+ const phase = row ? str(row.phase) : "observation";
44729
+ const result = row ? str(row.result) : "none";
44730
+ const validMoves = row && Array.isArray(row.validMoves) ? row.validMoves : [];
44731
+ const attackTargets = row && Array.isArray(row.attackTargets) ? row.attackTargets : [];
44732
+ const turn = row ? num(row.turn) : 0;
44733
+ const currentTeam = row ? str(row.currentTeam) : "player";
44734
+ const isGameOver = result !== "none";
44735
+ const gameEndEmittedRef = useRef(false);
44736
+ useEffect(() => {
44737
+ if ((result === "victory" || result === "defeat") && gameEndEvent) {
44738
+ if (!gameEndEmittedRef.current) {
44739
+ gameEndEmittedRef.current = true;
44740
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
44741
+ }
44742
+ } else {
44743
+ gameEndEmittedRef.current = false;
44744
+ }
44745
+ }, [result, gameEndEvent, eventBus]);
44746
+ const checkGameEnd = useCallback(() => {
44747
+ const alivePlayer = entityUnits.filter((u) => unitTeam(u) === "player" && unitHealth(u) > 0);
44748
+ const aliveEnemy = entityUnits.filter((u) => unitTeam(u) === "enemy" && unitHealth(u) > 0);
44749
+ if (alivePlayer.length === 0 && gameEndEvent) {
44750
+ eventBus.emit(`UI:${gameEndEvent}`, { result: "defeat" });
44751
+ } else if (aliveEnemy.length === 0 && gameEndEvent) {
44752
+ eventBus.emit(`UI:${gameEndEvent}`, { result: "victory" });
44753
+ }
44754
+ }, [entityUnits, gameEndEvent, eventBus]);
44755
+ const handleUnitClickCallback = useCallback((isoUnit) => {
44756
+ if (phase !== "action" || !selectedUnitId || !attackEvent) return;
44757
+ const attackerRow = entityUnits.find((u) => str(u.id) === selectedUnitId);
44758
+ const targetRow = entityUnits.find((u) => str(u.id) === isoUnit.id);
44759
+ if (!attackerRow || !targetRow) return;
44760
+ if (unitTeam(targetRow) !== "enemy" || unitHealth(targetRow) <= 0) return;
44761
+ const ap = unitPosition(attackerRow);
44762
+ const tp = unitPosition(targetRow);
44763
+ const dx = Math.abs(ap.x - tp.x);
44764
+ const dy = Math.abs(ap.y - tp.y);
44765
+ if (dx <= 1 && dy <= 1 && dx + dy > 0) {
44766
+ const damage = Math.max(1, num(attackerRow.attack) - num(targetRow.defense));
44767
+ eventBus.emit(`UI:${attackEvent}`, {
44768
+ attackerId: str(attackerRow.id),
44769
+ targetId: str(targetRow.id),
44770
+ damage
44771
+ });
44772
+ setTimeout(checkGameEnd, 100);
44773
+ }
44774
+ }, [phase, selectedUnitId, entityUnits, attackEvent, eventBus, checkGameEnd]);
44775
+ const handleEndTurn = useCallback(() => {
44776
+ if (endTurnEvent) eventBus.emit(`UI:${endTurnEvent}`, {});
44777
+ }, [endTurnEvent, eventBus]);
44778
+ const handleCancel = useCallback(() => {
44779
+ if (cancelEvent) eventBus.emit(`UI:${cancelEvent}`, {});
44780
+ }, [cancelEvent, eventBus]);
44781
+ const handlePlayAgain = useCallback(() => {
44782
+ if (playAgainEvent) eventBus.emit(`UI:${playAgainEvent}`, {});
44783
+ }, [playAgainEvent, eventBus]);
44784
+ return /* @__PURE__ */ jsxs(VStack, { className: cn("game-board-3d block w-full min-h-[85vh] relative", className), children: [
44785
+ /* @__PURE__ */ jsxs("div", { className: "game-board-3d__status absolute top-3 left-3 z-10 flex gap-2 items-center", children: [
44786
+ /* @__PURE__ */ jsx(Typography, { variant: "small", className: "status__phase capitalize", children: phase.replace("_", " ") }),
44787
+ currentTeam && !isGameOver && /* @__PURE__ */ jsxs(Typography, { variant: "small", color: "muted", className: "status__team", children: [
44788
+ "\u2014 ",
44789
+ currentTeam === "player" ? "Your Turn" : "Enemy's Turn"
44790
+ ] }),
44791
+ /* @__PURE__ */ jsxs(Typography, { variant: "small", color: "muted", className: "status__turn", children: [
44792
+ "Turn ",
44793
+ turn
44794
+ ] })
44795
+ ] }),
44796
+ /* @__PURE__ */ jsx(
44797
+ GameCanvas3D,
44798
+ {
44799
+ tiles,
44800
+ units,
44801
+ features,
44802
+ cameraMode,
44803
+ showGrid: true,
44804
+ showCoordinates: false,
44805
+ showTileInfo: false,
44806
+ shadows: true,
44807
+ backgroundColor,
44808
+ tileClickEvent,
44809
+ unitClickEvent,
44810
+ onUnitClick: handleUnitClickCallback,
44811
+ selectedUnitId,
44812
+ validMoves,
44813
+ attackTargets,
44814
+ className: "game-board-3d__canvas w-full min-h-[85vh]"
44815
+ }
44816
+ ),
44817
+ !isGameOver && /* @__PURE__ */ jsxs(HStack, { className: "fixed bottom-6 right-6 z-50", gap: "sm", children: [
44818
+ (phase === "selection" || phase === "movement" || phase === "action") && /* @__PURE__ */ jsx(
44819
+ Button,
44820
+ {
44821
+ variant: "secondary",
44822
+ className: "shadow-xl",
44823
+ onClick: handleCancel,
44824
+ children: "Cancel"
44825
+ }
44826
+ ),
44827
+ phase !== "enemy_turn" && /* @__PURE__ */ jsx(
44828
+ Button,
44829
+ {
44830
+ variant: "primary",
44831
+ className: "shadow-xl",
44832
+ onClick: handleEndTurn,
44833
+ children: "End Turn"
44834
+ }
44835
+ )
44836
+ ] }),
44837
+ 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: [
44838
+ /* @__PURE__ */ jsx(
44839
+ Typography,
44840
+ {
44841
+ variant: "h2",
44842
+ className: cn(
44843
+ "overlay__result",
44844
+ result === "victory" ? "text-yellow-400" : "text-red-500"
44845
+ ),
44846
+ children: result === "victory" ? "Victory!" : "Defeat"
44847
+ }
44848
+ ),
44849
+ /* @__PURE__ */ jsxs(Typography, { variant: "body", color: "muted", className: "overlay__turn-count", children: [
44850
+ "Completed in ",
44851
+ turn,
44852
+ " turn",
44853
+ turn !== 1 ? "s" : ""
44854
+ ] }),
44855
+ /* @__PURE__ */ jsx(
44856
+ Button,
44857
+ {
44858
+ variant: "primary",
44859
+ className: "px-8 py-3 font-semibold",
44860
+ onClick: handlePlayAgain,
44861
+ children: "Play Again"
44862
+ }
44863
+ )
44864
+ ] }) })
44865
+ ] });
44866
+ }
44867
+ var init_GameBoard3D = __esm({
44868
+ "components/game/organisms/GameBoard3D.tsx"() {
44869
+ "use client";
44870
+ init_cn();
44871
+ init_Button();
44872
+ init_Typography();
44873
+ init_Stack();
44874
+ init_useEventBus();
44875
+ init_GameCanvas3D2();
44876
+ init_boardEntity();
44877
+ GameBoard3D.displayName = "GameBoard3D";
44878
+ }
44879
+ });
43177
44880
  var GameShell;
43178
44881
  var init_GameShell = __esm({
43179
44882
  "components/game/templates/GameShell.tsx"() {
@@ -44542,21 +46245,6 @@ var init_ModalSlot = __esm({
44542
46245
  ModalSlot.displayName = "ModalSlot";
44543
46246
  }
44544
46247
  });
44545
- function getOpponentAction(strategy, actions, history) {
44546
- const actionIds = actions.map((a) => a.id);
44547
- switch (strategy) {
44548
- case "always-cooperate":
44549
- return actionIds[0];
44550
- case "always-defect":
44551
- return actionIds[actionIds.length - 1];
44552
- case "tit-for-tat":
44553
- if (history.length === 0) return actionIds[0];
44554
- return history[history.length - 1].playerAction;
44555
- case "random":
44556
- default:
44557
- return actionIds[Math.floor(Math.random() * actionIds.length)];
44558
- }
44559
- }
44560
46248
  function NegotiatorBoard({
44561
46249
  entity,
44562
46250
  completeEvent = "PUZZLE_COMPLETE",
@@ -44578,28 +46266,35 @@ function NegotiatorBoard({
44578
46266
  const playerTotal = num(resolved?.score);
44579
46267
  const isComplete = result !== "none" || totalRounds > 0 && currentRound >= totalRounds;
44580
46268
  const won = result === "win";
44581
- const opponentTotal = history.reduce((s, r2) => s + r2.opponentPayoff, 0);
46269
+ const lastPlayerAction = str(resolved?.lastPlayerAction);
46270
+ const lastOpponentAction = str(resolved?.lastOpponentAction);
46271
+ const lastPayoff = num(resolved?.lastPayoff);
44582
46272
  const actions = Array.isArray(resolved?.actions) ? resolved.actions : [];
44583
46273
  const payoffMatrix = Array.isArray(resolved?.payoffMatrix) ? resolved.payoffMatrix : [];
46274
+ const prevRoundRef = useRef(currentRound);
46275
+ useEffect(() => {
46276
+ if (currentRound > prevRoundRef.current && lastPlayerAction) {
46277
+ const opponentPayoffEntry = payoffMatrix.find(
46278
+ (p2) => p2.playerAction === lastPlayerAction && p2.opponentAction === lastOpponentAction
46279
+ );
46280
+ setHistory((prev) => [
46281
+ ...prev,
46282
+ {
46283
+ round: currentRound,
46284
+ playerAction: lastPlayerAction,
46285
+ opponentAction: lastOpponentAction,
46286
+ playerPayoff: lastPayoff,
46287
+ opponentPayoff: opponentPayoffEntry?.opponentPayoff ?? 0
46288
+ }
46289
+ ]);
46290
+ }
46291
+ prevRoundRef.current = currentRound;
46292
+ }, [currentRound, lastPlayerAction, lastOpponentAction, lastPayoff, payoffMatrix]);
46293
+ const opponentTotal = useMemo(() => history.reduce((s, r2) => s + r2.opponentPayoff, 0), [history]);
44584
46294
  const handleAction = useCallback((actionId) => {
44585
46295
  if (isComplete) return;
44586
- const opponentAction = getOpponentAction(str(resolved?.opponentStrategy) || "random", actions, history);
44587
- const payoff = payoffMatrix.find(
44588
- (p2) => p2.playerAction === actionId && p2.opponentAction === opponentAction
44589
- );
44590
- const playerPayoff = payoff?.playerPayoff ?? 0;
44591
- setHistory((prev) => [
44592
- ...prev,
44593
- {
44594
- round: prev.length + 1,
44595
- playerAction: actionId,
44596
- opponentAction,
44597
- playerPayoff,
44598
- opponentPayoff: payoff?.opponentPayoff ?? 0
44599
- }
44600
- ]);
44601
46296
  if (playRoundEvent) {
44602
- emit(`UI:${playRoundEvent}`, { playerAction: actionId, payoff: playerPayoff });
46297
+ emit(`UI:${playRoundEvent}`, { playerAction: actionId, payoff: 0 });
44603
46298
  }
44604
46299
  if (totalRounds > 0 && currentRound + 1 >= totalRounds) {
44605
46300
  if (finishEvent) {
@@ -44609,10 +46304,11 @@ function NegotiatorBoard({
44609
46304
  setShowHint(true);
44610
46305
  }
44611
46306
  }
44612
- }, [isComplete, resolved, totalRounds, currentRound, actions, payoffMatrix, history, playRoundEvent, finishEvent, emit]);
46307
+ }, [isComplete, resolved, totalRounds, currentRound, playRoundEvent, finishEvent, emit]);
44613
46308
  const handleReset = useCallback(() => {
44614
46309
  setHistory([]);
44615
46310
  setShowHint(false);
46311
+ prevRoundRef.current = 0;
44616
46312
  if (playAgainEvent) {
44617
46313
  emit(`UI:${playAgainEvent}`, {});
44618
46314
  }
@@ -44723,6 +46419,236 @@ var init_NegotiatorBoard = __esm({
44723
46419
  NegotiatorBoard.displayName = "NegotiatorBoard";
44724
46420
  }
44725
46421
  });
46422
+ function resolveEntity(entity) {
46423
+ if (!entity) return {};
46424
+ if (Array.isArray(entity)) return entity[0] ?? {};
46425
+ return entity;
46426
+ }
46427
+ function numField(row, key, fallback) {
46428
+ const v = row[key];
46429
+ return typeof v === "number" ? v : fallback;
46430
+ }
46431
+ function strField(row, key, fallback) {
46432
+ const v = row[key];
46433
+ return typeof v === "string" ? v : fallback;
46434
+ }
46435
+ function PlatformerBoard({
46436
+ entity,
46437
+ player: propPlayer,
46438
+ platforms: propPlatforms,
46439
+ result: propResult,
46440
+ lives: propLives,
46441
+ score: propScore,
46442
+ level: propLevel,
46443
+ worldWidth = 800,
46444
+ worldHeight = 400,
46445
+ canvasWidth = 800,
46446
+ canvasHeight = 400,
46447
+ playerSprite,
46448
+ tileSprites,
46449
+ bgColor,
46450
+ leftEvent = "LEFT",
46451
+ rightEvent = "RIGHT",
46452
+ jumpEvent = "JUMP",
46453
+ stopEvent = "STOP",
46454
+ playAgainEvent = "PLAY_AGAIN",
46455
+ gameEndEvent = "GAME_END",
46456
+ className
46457
+ }) {
46458
+ const row = resolveEntity(entity);
46459
+ const eventBus = useEventBus();
46460
+ const { t } = useTranslate();
46461
+ const result = propResult ?? strField(row, "result", "none");
46462
+ propLives ?? numField(row, "lives", 3);
46463
+ const score = propScore ?? numField(row, "score", 0);
46464
+ const level = propLevel ?? numField(row, "level", 1);
46465
+ const entityPlayer = row["player"];
46466
+ const player = propPlayer ?? entityPlayer ?? {
46467
+ x: 80,
46468
+ y: 320,
46469
+ width: 32,
46470
+ height: 48,
46471
+ vx: 0,
46472
+ vy: 0,
46473
+ grounded: false,
46474
+ facingRight: true
46475
+ };
46476
+ const entityPlatforms = Array.isArray(row["platforms"]) ? row["platforms"] : void 0;
46477
+ const platforms = propPlatforms ?? entityPlatforms ?? [
46478
+ { x: 0, y: 368, width: 800, height: 32, type: "ground" },
46479
+ { x: 150, y: 280, width: 160, height: 16, type: "platform" },
46480
+ { x: 420, y: 220, width: 160, height: 16, type: "platform" },
46481
+ { x: 580, y: 300, width: 80, height: 16, type: "hazard" },
46482
+ { x: 700, y: 340, width: 64, height: 28, type: "goal" }
46483
+ ];
46484
+ const handleRestart = useCallback(() => {
46485
+ if (playAgainEvent) {
46486
+ eventBus.emit(`UI:${playAgainEvent}`, {});
46487
+ }
46488
+ }, [playAgainEvent, eventBus]);
46489
+ const isGameOver = result === "won" || result === "lost";
46490
+ const gameEndEmittedRef = useRef(false);
46491
+ useEffect(() => {
46492
+ if ((result === "won" || result === "lost") && gameEndEvent) {
46493
+ if (!gameEndEmittedRef.current) {
46494
+ gameEndEmittedRef.current = true;
46495
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
46496
+ }
46497
+ } else {
46498
+ gameEndEmittedRef.current = false;
46499
+ }
46500
+ }, [result, gameEndEvent, eventBus]);
46501
+ return /* @__PURE__ */ jsx(
46502
+ VStack,
46503
+ {
46504
+ className: cn("platformer-board relative bg-background", className),
46505
+ gap: "none",
46506
+ children: /* @__PURE__ */ jsxs(Box, { className: "relative", children: [
46507
+ /* @__PURE__ */ jsx(
46508
+ PlatformerCanvas,
46509
+ {
46510
+ player,
46511
+ platforms,
46512
+ worldWidth,
46513
+ worldHeight,
46514
+ canvasWidth,
46515
+ canvasHeight,
46516
+ followCamera: true,
46517
+ bgColor,
46518
+ playerSprite,
46519
+ tileSprites,
46520
+ leftEvent,
46521
+ rightEvent,
46522
+ jumpEvent,
46523
+ stopEvent
46524
+ }
46525
+ ),
46526
+ 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: [
46527
+ /* @__PURE__ */ jsx(
46528
+ Typography,
46529
+ {
46530
+ variant: "h2",
46531
+ className: cn(
46532
+ "text-4xl font-black tracking-widest uppercase",
46533
+ result === "won" ? "text-warning" : "text-error"
46534
+ ),
46535
+ children: result === "won" ? t("platformer.won") : t("platformer.lost")
46536
+ }
46537
+ ),
46538
+ result === "won" && /* @__PURE__ */ jsxs(Typography, { variant: "body1", className: "text-muted-foreground", children: [
46539
+ t("platformer.level"),
46540
+ " ",
46541
+ level,
46542
+ " \u2014 ",
46543
+ t("platformer.score"),
46544
+ ": ",
46545
+ score
46546
+ ] }),
46547
+ result === "lost" && /* @__PURE__ */ jsx(Typography, { variant: "body1", className: "text-muted-foreground", children: t("platformer.noLives") }),
46548
+ /* @__PURE__ */ jsx(
46549
+ Button,
46550
+ {
46551
+ variant: "primary",
46552
+ className: "px-8 py-3 font-semibold",
46553
+ onClick: handleRestart,
46554
+ children: result === "won" ? t("platformer.nextLevel") : t("platformer.playAgain")
46555
+ }
46556
+ )
46557
+ ] }) })
46558
+ ] })
46559
+ }
46560
+ );
46561
+ }
46562
+ var init_PlatformerBoard = __esm({
46563
+ "components/game/organisms/PlatformerBoard.tsx"() {
46564
+ "use client";
46565
+ init_cn();
46566
+ init_useEventBus();
46567
+ init_Box();
46568
+ init_Button();
46569
+ init_Typography();
46570
+ init_Stack();
46571
+ init_PlatformerCanvas();
46572
+ PlatformerBoard.displayName = "PlatformerBoard";
46573
+ }
46574
+ });
46575
+ function PlatformerTemplate({
46576
+ entity,
46577
+ platforms = DEFAULT_PLATFORMS,
46578
+ worldWidth = 800,
46579
+ worldHeight = 400,
46580
+ canvasWidth = 800,
46581
+ canvasHeight = 400,
46582
+ playerSprite = `${CDN3}/characters/platformChar_idle.png`,
46583
+ tileSprites = DEFAULT_TILE_SPRITES,
46584
+ bgColor = "#5c94fc",
46585
+ gameEndEvent = "GAME_END",
46586
+ className
46587
+ }) {
46588
+ const row = entity && !Array.isArray(entity) ? entity : void 0;
46589
+ const lives = typeof row?.["lives"] === "number" ? row["lives"] : 3;
46590
+ const score = typeof row?.["score"] === "number" ? row["score"] : 0;
46591
+ const level = typeof row?.["level"] === "number" ? row["level"] : 1;
46592
+ return /* @__PURE__ */ jsxs(VStack, { className: cn("platformer-template w-full", className), gap: "none", children: [
46593
+ /* @__PURE__ */ jsx(Box, { className: "relative", children: /* @__PURE__ */ jsx(
46594
+ GameHud,
46595
+ {
46596
+ position: "top",
46597
+ stats: [
46598
+ { label: "Lives", value: lives, format: "number", variant: "danger" },
46599
+ { label: "Level", value: level, format: "number", variant: "default" }
46600
+ ]
46601
+ }
46602
+ ) }),
46603
+ /* @__PURE__ */ jsx(
46604
+ PlatformerBoard,
46605
+ {
46606
+ entity,
46607
+ platforms,
46608
+ worldWidth,
46609
+ worldHeight,
46610
+ canvasWidth,
46611
+ canvasHeight,
46612
+ playerSprite,
46613
+ tileSprites,
46614
+ bgColor,
46615
+ leftEvent: "LEFT",
46616
+ rightEvent: "RIGHT",
46617
+ jumpEvent: "JUMP",
46618
+ stopEvent: "STOP",
46619
+ playAgainEvent: "PLAY_AGAIN",
46620
+ gameEndEvent
46621
+ }
46622
+ ),
46623
+ /* @__PURE__ */ jsx(Box, { className: "px-4 py-2", children: /* @__PURE__ */ jsx(ScoreBoard, { score, level }) })
46624
+ ] });
46625
+ }
46626
+ var CDN3, DEFAULT_TILE_SPRITES, DEFAULT_PLATFORMS;
46627
+ var init_PlatformerTemplate = __esm({
46628
+ "components/game/templates/PlatformerTemplate.tsx"() {
46629
+ init_cn();
46630
+ init_Box();
46631
+ init_Stack();
46632
+ init_PlatformerBoard();
46633
+ init_GameHud();
46634
+ init_ScoreBoard();
46635
+ CDN3 = "https://almadar-kflow-assets.web.app/shared/platformer";
46636
+ DEFAULT_TILE_SPRITES = {
46637
+ ground: `${CDN3}/tiles/platformPack_tile001.png`,
46638
+ platform: `${CDN3}/tiles/platformPack_tile004.png`,
46639
+ hazard: `${CDN3}/tiles/platformPack_tile017.png`,
46640
+ goal: `${CDN3}/tiles/platformPack_tile007.png`
46641
+ };
46642
+ DEFAULT_PLATFORMS = [
46643
+ { x: 0, y: 368, width: 800, height: 32, type: "ground" },
46644
+ { x: 150, y: 280, width: 160, height: 16, type: "platform" },
46645
+ { x: 420, y: 220, width: 160, height: 16, type: "platform" },
46646
+ { x: 580, y: 300, width: 80, height: 16, type: "hazard" },
46647
+ { x: 700, y: 340, width: 64, height: 28, type: "goal" }
46648
+ ];
46649
+ PlatformerTemplate.displayName = "PlatformerTemplate";
46650
+ }
46651
+ });
44726
46652
  var PricingOrganism;
44727
46653
  var init_PricingOrganism = __esm({
44728
46654
  "components/marketing/organisms/PricingOrganism.tsx"() {
@@ -44845,6 +46771,356 @@ var init_PricingPageTemplate = __esm({
44845
46771
  PricingPageTemplate.displayName = "PricingPageTemplate";
44846
46772
  }
44847
46773
  });
46774
+ function RoguelikeBoard({
46775
+ entity,
46776
+ tiles: propTiles,
46777
+ enemies: propEnemies,
46778
+ items: propItems,
46779
+ player: propPlayer,
46780
+ playerHp: propPlayerHp,
46781
+ playerMaxHp: propPlayerMaxHp,
46782
+ playerAttack: propPlayerAttack,
46783
+ depth: propDepth,
46784
+ result: propResult,
46785
+ phase: propPhase,
46786
+ stairsX: propStairsX,
46787
+ stairsY: propStairsY,
46788
+ assetManifest: propAssetManifest,
46789
+ scale = 0.45,
46790
+ unitScale = 1,
46791
+ moveEvent,
46792
+ playAgainEvent,
46793
+ gameEndEvent,
46794
+ className
46795
+ }) {
46796
+ const board = boardEntity(entity) ?? {};
46797
+ const tiles = propTiles ?? (Array.isArray(board.tiles) ? board.tiles : DEFAULT_TILES2);
46798
+ const enemies = propEnemies ?? (Array.isArray(board.enemies) ? board.enemies : DEFAULT_ENEMIES);
46799
+ const items = propItems ?? (Array.isArray(board.items) ? board.items : DEFAULT_ITEMS);
46800
+ const player = propPlayer ?? board.player ?? { x: 1, y: 1 };
46801
+ const playerHp = propPlayerHp ?? num(board.playerHp, 10);
46802
+ const playerMaxHp = propPlayerMaxHp ?? num(board.playerMaxHp, 10);
46803
+ const playerAttack = propPlayerAttack ?? num(board.playerAttack, 3);
46804
+ const depth = propDepth ?? num(board.depth, 1);
46805
+ const result = propResult ?? (str(board.result) || "none");
46806
+ const phase = propPhase ?? (str(board.phase) || "player_turn");
46807
+ propStairsX ?? num(board.stairsX, 3);
46808
+ propStairsY ?? num(board.stairsY, 3);
46809
+ const eventBus = useEventBus();
46810
+ const { t } = useTranslate();
46811
+ const [hoveredTile, setHoveredTile] = useState(null);
46812
+ const emitMove = useCallback((dx, dy) => {
46813
+ if (!moveEvent) return;
46814
+ eventBus.emit(`UI:${moveEvent}`, { dx, dy });
46815
+ }, [moveEvent, eventBus]);
46816
+ useEffect(() => {
46817
+ const handler = (e) => {
46818
+ if (result !== "none" || phase !== "player_turn") return;
46819
+ if (e.key === "ArrowUp" || e.key === "w") emitMove(0, -1);
46820
+ if (e.key === "ArrowDown" || e.key === "s") emitMove(0, 1);
46821
+ if (e.key === "ArrowLeft" || e.key === "a") emitMove(-1, 0);
46822
+ if (e.key === "ArrowRight" || e.key === "d") emitMove(1, 0);
46823
+ };
46824
+ window.addEventListener("keydown", handler);
46825
+ return () => window.removeEventListener("keydown", handler);
46826
+ }, [result, phase, emitMove]);
46827
+ useEffect(() => {
46828
+ if ((result === "won" || result === "lost") && gameEndEvent) {
46829
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
46830
+ }
46831
+ }, [result, gameEndEvent, eventBus]);
46832
+ const handleTileClick = useCallback((x, y) => {
46833
+ if (result !== "none" || phase !== "player_turn") return;
46834
+ const dx = x - player.x;
46835
+ const dy = y - player.y;
46836
+ if (Math.abs(dx) + Math.abs(dy) === 1) {
46837
+ emitMove(dx, dy);
46838
+ }
46839
+ }, [result, phase, player, emitMove]);
46840
+ const handleReset = useCallback(() => {
46841
+ if (!playAgainEvent) return;
46842
+ eventBus.emit(`UI:${playAgainEvent}`, {});
46843
+ }, [playAgainEvent, eventBus]);
46844
+ const isoUnits = useMemo(() => {
46845
+ const liveEnemies = enemies.filter((e) => e.hp > 0);
46846
+ return [
46847
+ {
46848
+ id: "player",
46849
+ position: player,
46850
+ name: t("roguelike.player") || "Hero",
46851
+ team: "player",
46852
+ health: playerHp,
46853
+ maxHealth: playerMaxHp,
46854
+ unitType: "player",
46855
+ sprite: `${CDN4}/isometric-dungeon/Characters/Male/Male_0_Idle0.png`
46856
+ },
46857
+ ...liveEnemies.map((e) => ({
46858
+ id: e.id,
46859
+ position: { x: e.x, y: e.y },
46860
+ name: t("roguelike.enemy") || "Enemy",
46861
+ team: "enemy",
46862
+ health: e.hp,
46863
+ maxHealth: 6,
46864
+ unitType: "enemy",
46865
+ sprite: `${CDN4}/sprite-sheets/shadow-legion-sprite-sheet-sw.png`
46866
+ }))
46867
+ ];
46868
+ }, [player, enemies, playerHp, playerMaxHp, t]);
46869
+ const isoFeatures = useMemo(
46870
+ () => items.map((it) => ({
46871
+ id: it.id,
46872
+ x: it.x,
46873
+ y: it.y,
46874
+ type: it.kind,
46875
+ sprite: FEATURE_SPRITE[it.kind] ?? FEATURE_SPRITE.health_potion
46876
+ })),
46877
+ [items]
46878
+ );
46879
+ const validMoves = useMemo(() => {
46880
+ if (result !== "none" || phase !== "player_turn") return [];
46881
+ const dirs = [{ dx: 0, dy: -1 }, { dx: 0, dy: 1 }, { dx: -1, dy: 0 }, { dx: 1, dy: 0 }];
46882
+ return dirs.map(({ dx, dy }) => ({ x: player.x + dx, y: player.y + dy })).filter(({ x, y }) => {
46883
+ const tile = tiles.find((t2) => t2.x === x && t2.y === y);
46884
+ return tile?.passable ?? false;
46885
+ });
46886
+ }, [result, phase, player, tiles]);
46887
+ const isGameOver = result === "won" || result === "lost";
46888
+ return /* @__PURE__ */ jsxs(VStack, { className: cn("roguelike-board relative min-h-[600px] bg-background", className), gap: "none", children: [
46889
+ /* @__PURE__ */ jsxs(HStack, { className: "items-center justify-between px-4 py-2 border-b border-border", gap: "md", children: [
46890
+ /* @__PURE__ */ jsxs(HStack, { gap: "sm", className: "items-center", children: [
46891
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: t("roguelike.depth") || "Depth" }),
46892
+ /* @__PURE__ */ jsx(Typography, { variant: "body1", className: "font-bold tabular-nums", children: depth })
46893
+ ] }),
46894
+ /* @__PURE__ */ jsxs(HStack, { gap: "sm", className: "items-center", children: [
46895
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: t("roguelike.hp") || "HP" }),
46896
+ /* @__PURE__ */ jsxs(
46897
+ Typography,
46898
+ {
46899
+ variant: "body1",
46900
+ className: cn(
46901
+ "font-bold tabular-nums",
46902
+ playerHp <= Math.floor(playerMaxHp * 0.25) ? "text-error" : "text-success"
46903
+ ),
46904
+ children: [
46905
+ playerHp,
46906
+ " / ",
46907
+ playerMaxHp
46908
+ ]
46909
+ }
46910
+ )
46911
+ ] }),
46912
+ /* @__PURE__ */ jsxs(HStack, { gap: "sm", className: "items-center", children: [
46913
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: t("roguelike.atk") || "ATK" }),
46914
+ /* @__PURE__ */ jsx(Typography, { variant: "body1", className: "font-bold tabular-nums text-foreground", children: playerAttack })
46915
+ ] }),
46916
+ /* @__PURE__ */ jsxs(HStack, { gap: "sm", className: "items-center", children: [
46917
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: t("roguelike.phase") || "Phase" }),
46918
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "capitalize text-foreground", children: phase.replace("_", " ") })
46919
+ ] })
46920
+ ] }),
46921
+ /* @__PURE__ */ jsx(Box, { className: "relative flex-1", children: /* @__PURE__ */ jsx(
46922
+ IsometricCanvas_default,
46923
+ {
46924
+ tiles,
46925
+ units: isoUnits,
46926
+ features: isoFeatures,
46927
+ selectedUnitId: null,
46928
+ validMoves,
46929
+ attackTargets: [],
46930
+ hoveredTile,
46931
+ onTileClick: handleTileClick,
46932
+ onUnitClick: () => void 0,
46933
+ onTileHover: (x, y) => setHoveredTile({ x, y }),
46934
+ onTileLeave: () => setHoveredTile(null),
46935
+ scale,
46936
+ assetBaseUrl: propAssetManifest?.baseUrl ?? CDN4,
46937
+ assetManifest: propAssetManifest,
46938
+ unitScale
46939
+ }
46940
+ ) }),
46941
+ !isGameOver && /* @__PURE__ */ jsx(HStack, { className: "justify-center gap-2 p-3", gap: "none", children: [
46942
+ { label: "\u2191", dx: 0, dy: -1 },
46943
+ { label: "\u2193", dx: 0, dy: 1 },
46944
+ { label: "\u2190", dx: -1, dy: 0 },
46945
+ { label: "\u2192", dx: 1, dy: 0 }
46946
+ ].map(({ label, dx, dy }) => /* @__PURE__ */ jsx(
46947
+ Button,
46948
+ {
46949
+ variant: "secondary",
46950
+ className: "w-9 h-9 p-0 text-base font-mono",
46951
+ onClick: () => emitMove(dx, dy),
46952
+ disabled: phase !== "player_turn",
46953
+ children: label
46954
+ },
46955
+ label
46956
+ )) }),
46957
+ 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: [
46958
+ /* @__PURE__ */ jsx(
46959
+ Typography,
46960
+ {
46961
+ variant: "h2",
46962
+ className: cn(
46963
+ "text-4xl font-black tracking-widest uppercase",
46964
+ result === "won" ? "text-warning" : "text-error"
46965
+ ),
46966
+ children: result === "won" ? t("roguelike.victory") || "Victory!" : t("roguelike.defeat") || "Defeated!"
46967
+ }
46968
+ ),
46969
+ result === "won" && /* @__PURE__ */ jsx(Typography, { variant: "body1", className: "text-muted-foreground", children: t("roguelike.cleared") || `Dungeon cleared! Depth reached: ${depth}` }),
46970
+ /* @__PURE__ */ jsx(
46971
+ Button,
46972
+ {
46973
+ variant: "primary",
46974
+ className: "px-8 py-3 font-semibold",
46975
+ onClick: handleReset,
46976
+ children: t("roguelike.playAgain") || "Play Again"
46977
+ }
46978
+ )
46979
+ ] }) })
46980
+ ] });
46981
+ }
46982
+ var CDN4, DEFAULT_TILES2, DEFAULT_ENEMIES, DEFAULT_ITEMS, FEATURE_SPRITE;
46983
+ var init_RoguelikeBoard = __esm({
46984
+ "components/game/organisms/RoguelikeBoard.tsx"() {
46985
+ "use client";
46986
+ init_cn();
46987
+ init_useEventBus();
46988
+ init_Box();
46989
+ init_Button();
46990
+ init_Typography();
46991
+ init_Stack();
46992
+ init_IsometricCanvas();
46993
+ init_boardEntity();
46994
+ CDN4 = "https://almadar-kflow-assets.web.app/shared";
46995
+ DEFAULT_TILES2 = [
46996
+ { x: 0, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
46997
+ { x: 1, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
46998
+ { x: 2, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
46999
+ { x: 3, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
47000
+ { x: 4, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
47001
+ { x: 0, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
47002
+ { x: 1, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
47003
+ { x: 2, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
47004
+ { x: 3, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
47005
+ { x: 4, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
47006
+ { x: 0, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
47007
+ { x: 1, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirtTiles_E.png` },
47008
+ { x: 2, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirtTiles_E.png` },
47009
+ { x: 3, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
47010
+ { x: 4, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
47011
+ { x: 0, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
47012
+ { x: 1, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
47013
+ { x: 2, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirtTiles_E.png` },
47014
+ { x: 3, y: 3, terrain: "stairs", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stairs_E.png` },
47015
+ { x: 4, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
47016
+ { x: 0, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
47017
+ { x: 1, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
47018
+ { x: 2, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
47019
+ { x: 3, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
47020
+ { x: 4, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` }
47021
+ ];
47022
+ DEFAULT_ENEMIES = [
47023
+ { id: "e1", x: 3, y: 1, hp: 5, attack: 2 },
47024
+ { id: "e2", x: 1, y: 3, hp: 4, attack: 1 }
47025
+ ];
47026
+ DEFAULT_ITEMS = [
47027
+ { id: "i1", x: 2, y: 2, kind: "health_potion" }
47028
+ ];
47029
+ FEATURE_SPRITE = {
47030
+ health_potion: `${CDN4}/isometric-dungeon/Isometric/chestClosed_E.png`,
47031
+ sword: `${CDN4}/isometric-dungeon/Isometric/barrel_E.png`,
47032
+ shield: `${CDN4}/isometric-dungeon/Isometric/barrel_E.png`
47033
+ };
47034
+ RoguelikeBoard.displayName = "RoguelikeBoard";
47035
+ }
47036
+ });
47037
+ function RoguelikeTemplate({
47038
+ entity,
47039
+ scale = 0.45,
47040
+ unitScale = 1,
47041
+ tiles = DEFAULT_ROGUELIKE_TILES,
47042
+ enemies,
47043
+ items,
47044
+ stairsX,
47045
+ stairsY,
47046
+ assetManifest = DEFAULT_ROGUELIKE_MANIFEST,
47047
+ gameEndEvent,
47048
+ className
47049
+ }) {
47050
+ const resolved = entity && typeof entity === "object" && !Array.isArray(entity) ? entity : void 0;
47051
+ if (!resolved && !tiles && !assetManifest) return null;
47052
+ return /* @__PURE__ */ jsx(
47053
+ RoguelikeBoard,
47054
+ {
47055
+ entity: resolved,
47056
+ tiles,
47057
+ enemies,
47058
+ items,
47059
+ stairsX,
47060
+ stairsY,
47061
+ assetManifest,
47062
+ scale,
47063
+ unitScale,
47064
+ moveEvent: "MOVE",
47065
+ playAgainEvent: "PLAY_AGAIN",
47066
+ gameEndEvent: gameEndEvent ?? "GAME_END",
47067
+ className
47068
+ }
47069
+ );
47070
+ }
47071
+ var CDN5, DEFAULT_ROGUELIKE_TILES, DEFAULT_ROGUELIKE_MANIFEST;
47072
+ var init_RoguelikeTemplate = __esm({
47073
+ "components/game/templates/RoguelikeTemplate.tsx"() {
47074
+ init_RoguelikeBoard();
47075
+ CDN5 = "https://almadar-kflow-assets.web.app/shared";
47076
+ DEFAULT_ROGUELIKE_TILES = [
47077
+ { x: 0, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47078
+ { x: 1, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47079
+ { x: 2, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47080
+ { x: 3, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47081
+ { x: 4, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47082
+ { x: 0, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47083
+ { x: 1, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
47084
+ { x: 2, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
47085
+ { x: 3, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
47086
+ { x: 4, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47087
+ { x: 0, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47088
+ { x: 1, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirtTiles_E.png` },
47089
+ { x: 2, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirtTiles_E.png` },
47090
+ { x: 3, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
47091
+ { x: 4, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47092
+ { x: 0, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47093
+ { x: 1, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
47094
+ { x: 2, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirtTiles_E.png` },
47095
+ { x: 3, y: 3, terrain: "stairs", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stairs_E.png` },
47096
+ { x: 4, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47097
+ { x: 0, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47098
+ { x: 1, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47099
+ { x: 2, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47100
+ { x: 3, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
47101
+ { x: 4, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` }
47102
+ ];
47103
+ DEFAULT_ROGUELIKE_MANIFEST = {
47104
+ baseUrl: CDN5,
47105
+ terrains: {
47106
+ floor: "/isometric-dungeon/Isometric/dirt_E.png",
47107
+ wall: "/isometric-dungeon/Isometric/stoneInset_E.png",
47108
+ stairs: "/isometric-dungeon/Isometric/stairs_E.png",
47109
+ planks: "/isometric-dungeon/Isometric/planks_E.png"
47110
+ },
47111
+ units: {
47112
+ player: "/isometric-dungeon/Characters/Male/Male_0_Idle0.png",
47113
+ enemy: "/sprite-sheets/shadow-legion-sprite-sheet-sw.png"
47114
+ },
47115
+ features: {
47116
+ health_potion: "/isometric-dungeon/Isometric/chestClosed_E.png",
47117
+ sword: "/isometric-dungeon/Isometric/barrel_E.png",
47118
+ shield: "/isometric-dungeon/Isometric/barrel_E.png"
47119
+ }
47120
+ };
47121
+ RoguelikeTemplate.displayName = "RoguelikeTemplate";
47122
+ }
47123
+ });
44848
47124
 
44849
47125
  // lib/traitRegistry.ts
44850
47126
  function notifyListeners2() {
@@ -46098,7 +48374,7 @@ function getAllEvents(traits2) {
46098
48374
  function EventDispatcherTab({ traits: traits2, schema }) {
46099
48375
  const eventBus = useEventBus();
46100
48376
  const { t } = useTranslate();
46101
- const [log13, setLog] = React91.useState([]);
48377
+ const [log15, setLog] = React91.useState([]);
46102
48378
  const prevStatesRef = React91.useRef(/* @__PURE__ */ new Map());
46103
48379
  React91.useEffect(() => {
46104
48380
  for (const trait of traits2) {
@@ -46162,9 +48438,9 @@ function EventDispatcherTab({ traits: traits2, schema }) {
46162
48438
  /* @__PURE__ */ jsx(Typography, { variant: "small", weight: "medium", className: "text-gray-500 mb-1", children: t("debug.otherEvents") }),
46163
48439
  /* @__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)) })
46164
48440
  ] }),
46165
- log13.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
48441
+ log15.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
46166
48442
  /* @__PURE__ */ jsx(Typography, { variant: "small", weight: "medium", className: "text-gray-500 mb-1", children: t("debug.recentTransitions") }),
46167
- /* @__PURE__ */ jsx(Stack, { gap: "xs", children: log13.map((entry, i) => /* @__PURE__ */ jsxs(Typography, { variant: "small", className: "font-mono text-xs", children: [
48443
+ /* @__PURE__ */ jsx(Stack, { gap: "xs", children: log15.map((entry, i) => /* @__PURE__ */ jsxs(Typography, { variant: "small", className: "font-mono text-xs", children: [
46168
48444
  /* @__PURE__ */ jsx("span", { className: "text-purple-400", children: entry.traitName }),
46169
48445
  " ",
46170
48446
  /* @__PURE__ */ jsx("span", { className: "text-gray-500", children: entry.from }),
@@ -46967,17 +49243,24 @@ function SequencerBoard({
46967
49243
  const { emit } = useEventBus();
46968
49244
  const { t } = useTranslate();
46969
49245
  const resolved = boardEntity(entity);
46970
- const maxSlots = num(resolved?.maxSlots);
49246
+ const maxSlots = num(resolved?.maxSlots) || 3;
46971
49247
  const solutions = Array.isArray(resolved?.solutions) ? resolved.solutions : [];
46972
49248
  const availableActions = Array.isArray(resolved?.availableActions) ? resolved.availableActions : [];
46973
49249
  const allowDuplicates = resolved?.allowDuplicates !== false;
49250
+ const entitySlots = Array.isArray(resolved?.slots) ? resolved.slots : [];
49251
+ const entityResult = str(resolved?.result);
49252
+ const entityAttempts = num(resolved?.attempts);
49253
+ const entityCurrentStep = typeof resolved?.currentStep === "number" ? resolved.currentStep : -1;
49254
+ const slots = Array.from({ length: maxSlots }, (_, i) => {
49255
+ const entitySlot = entitySlots.find((s) => s.index === i);
49256
+ if (!entitySlot?.placedActionId) return void 0;
49257
+ return availableActions.find((a) => a.id === entitySlot.placedActionId);
49258
+ });
49259
+ const isSuccess = entityResult === "win";
49260
+ const attempts = entityAttempts;
49261
+ const isPlayingBack = entityCurrentStep >= 0 && !isSuccess;
49262
+ const currentStep = entityCurrentStep;
46974
49263
  const [headerError, setHeaderError] = useState(false);
46975
- const [slots, setSlots] = useState(
46976
- () => Array.from({ length: maxSlots }, () => void 0)
46977
- );
46978
- const [playState, setPlayState] = useState("idle");
46979
- const [currentStep, setCurrentStep] = useState(-1);
46980
- const [attempts, setAttempts] = useState(0);
46981
49264
  const [slotFeedback, setSlotFeedback] = useState(
46982
49265
  () => Array.from({ length: maxSlots }, () => null)
46983
49266
  );
@@ -46986,11 +49269,6 @@ function SequencerBoard({
46986
49269
  if (timerRef.current) clearTimeout(timerRef.current);
46987
49270
  }, []);
46988
49271
  const handleSlotDrop = useCallback((index, item) => {
46989
- setSlots((prev) => {
46990
- const next = [...prev];
46991
- next[index] = item;
46992
- return next;
46993
- });
46994
49272
  setSlotFeedback((prev) => {
46995
49273
  const next = [...prev];
46996
49274
  next[index] = null;
@@ -47000,11 +49278,6 @@ function SequencerBoard({
47000
49278
  if (placeEvent) emit(`UI:${placeEvent}`, { slotIndex: index, actionId: item.id });
47001
49279
  }, [emit, placeEvent]);
47002
49280
  const handleSlotRemove = useCallback((index) => {
47003
- setSlots((prev) => {
47004
- const next = [...prev];
47005
- next[index] = void 0;
47006
- return next;
47007
- });
47008
49281
  setSlotFeedback((prev) => {
47009
49282
  const next = [...prev];
47010
49283
  next[index] = null;
@@ -47015,48 +49288,34 @@ function SequencerBoard({
47015
49288
  }, [emit, removeEvent]);
47016
49289
  const handleReset = useCallback(() => {
47017
49290
  if (timerRef.current) clearTimeout(timerRef.current);
47018
- setSlots(Array.from({ length: maxSlots }, () => void 0));
47019
- setPlayState("idle");
47020
- setCurrentStep(-1);
47021
- setAttempts(0);
47022
49291
  setSlotFeedback(Array.from({ length: maxSlots }, () => null));
47023
49292
  if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
47024
49293
  }, [maxSlots, playAgainEvent, emit]);
47025
49294
  const filledSlots = slots.filter((s) => !!s);
47026
- const canPlay = filledSlots.length > 0 && playState === "idle";
49295
+ const canPlay = filledSlots.length > 0 && !isPlayingBack && !isSuccess;
47027
49296
  const handlePlay = useCallback(() => {
47028
49297
  if (!canPlay) return;
47029
49298
  setSlotFeedback(Array.from({ length: maxSlots }, () => null));
47030
49299
  emit("UI:PLAY_SOUND", { key: "confirm" });
47031
49300
  const sequence = slots.map((s) => s?.id || "");
47032
- if (playEvent) {
47033
- emit(`UI:${playEvent}`, { sequence });
47034
- }
47035
- setPlayState("playing");
47036
- setCurrentStep(0);
49301
+ const playerIds = slots.filter(Boolean).map((s) => s?.id || "");
49302
+ if (playEvent) emit(`UI:${playEvent}`, { sequence });
47037
49303
  let step = 0;
47038
49304
  const advance = () => {
47039
49305
  step++;
49306
+ emit("UI:STEP", { step });
47040
49307
  if (step >= maxSlots) {
49308
+ if (checkEvent) emit(`UI:${checkEvent}`, { sequence: playerIds });
47041
49309
  const playerSeq = slots.map((s) => s?.id);
47042
- const playerIds = slots.filter(Boolean).map((s) => s?.id || "");
47043
49310
  const success = solutions.some(
47044
49311
  (sol) => sol.length === playerIds.length && sol.every((id, i) => id === playerIds[i])
47045
49312
  );
47046
- if (checkEvent) emit(`UI:${checkEvent}`, { sequence: playerIds });
47047
49313
  if (success) {
47048
- setPlayState("success");
47049
- setCurrentStep(-1);
47050
49314
  emit("UI:PLAY_SOUND", { key: "levelComplete" });
47051
- if (completeEvent) {
47052
- emit(`UI:${completeEvent}`, { success: true, sequence: playerIds });
47053
- }
49315
+ if (completeEvent) emit(`UI:${completeEvent}`, { success: true, sequence: playerIds });
47054
49316
  } else {
47055
- setAttempts((prev) => prev + 1);
47056
49317
  const feedback = computeSlotFeedback(playerSeq, solutions);
47057
49318
  setSlotFeedback(feedback);
47058
- setPlayState("idle");
47059
- setCurrentStep(-1);
47060
49319
  emit("UI:PLAY_SOUND", { key: "fail" });
47061
49320
  const correctCount2 = feedback.filter((f3) => f3 === "correct").length;
47062
49321
  for (let ci = 0; ci < correctCount2; ci++) {
@@ -47066,7 +49325,6 @@ function SequencerBoard({
47066
49325
  }
47067
49326
  }
47068
49327
  } else {
47069
- setCurrentStep(step);
47070
49328
  timerRef.current = setTimeout(advance, stepDurationMs);
47071
49329
  }
47072
49330
  };
@@ -47116,7 +49374,7 @@ function SequencerBoard({
47116
49374
  /* @__PURE__ */ jsxs(VStack, { gap: "xs", children: [
47117
49375
  /* @__PURE__ */ jsxs(HStack, { className: "items-center justify-between", children: [
47118
49376
  /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("sequencer.yourSequence") + ":" }),
47119
- hasFeedback && playState === "idle" && /* @__PURE__ */ jsxs(Typography, { variant: "caption", className: "text-muted-foreground", children: [
49377
+ hasFeedback && !isPlayingBack && !isSuccess && /* @__PURE__ */ jsxs(Typography, { variant: "caption", className: "text-muted-foreground", children: [
47120
49378
  `${correctCount}/${maxSlots} `,
47121
49379
  "\u2705"
47122
49380
  ] })
@@ -47128,7 +49386,7 @@ function SequencerBoard({
47128
49386
  maxSlots,
47129
49387
  onSlotDrop: handleSlotDrop,
47130
49388
  onSlotRemove: handleSlotRemove,
47131
- playing: playState === "playing",
49389
+ playing: isPlayingBack,
47132
49390
  currentStep,
47133
49391
  categoryColors,
47134
49392
  slotFeedback,
@@ -47136,7 +49394,7 @@ function SequencerBoard({
47136
49394
  }
47137
49395
  )
47138
49396
  ] }),
47139
- playState !== "playing" && /* @__PURE__ */ jsx(
49397
+ !isPlayingBack && /* @__PURE__ */ jsx(
47140
49398
  ActionPalette,
47141
49399
  {
47142
49400
  actions: availableActions,
@@ -47146,8 +49404,8 @@ function SequencerBoard({
47146
49404
  label: t("sequencer.dragActions")
47147
49405
  }
47148
49406
  ),
47149
- 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) }) }),
47150
- 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") }) }),
49407
+ 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) }) }),
49408
+ 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") }) }),
47151
49409
  /* @__PURE__ */ jsxs(HStack, { gap: "sm", children: [
47152
49410
  /* @__PURE__ */ jsx(
47153
49411
  Button,
@@ -48103,9 +50361,9 @@ function VariablePanel({
48103
50361
  /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("stateArchitect.variables", { name: entityName }) }),
48104
50362
  variables.map((v) => {
48105
50363
  const name = v.name == null ? "" : String(v.name);
48106
- const value = numField(v.value);
48107
- const max = numField(v.max, 100);
48108
- const min = numField(v.min, 0);
50364
+ const value = numField2(v.value);
50365
+ const max = numField2(v.max, 100);
50366
+ const min = numField2(v.min, 0);
48109
50367
  const unit = v.unit == null ? "" : String(v.unit);
48110
50368
  const pct = Math.round((value - min) / (max - min) * 100);
48111
50369
  const isHigh = pct > 80;
@@ -48135,12 +50393,12 @@ function VariablePanel({
48135
50393
  })
48136
50394
  ] });
48137
50395
  }
48138
- var numField;
50396
+ var numField2;
48139
50397
  var init_VariablePanel = __esm({
48140
50398
  "components/game/organisms/puzzles/state-architect/VariablePanel.tsx"() {
48141
50399
  init_atoms2();
48142
50400
  init_cn();
48143
- numField = (v, fallback = 0) => {
50401
+ numField2 = (v, fallback = 0) => {
48144
50402
  const n = Number(v);
48145
50403
  return Number.isFinite(n) ? n : fallback;
48146
50404
  };
@@ -48197,6 +50455,9 @@ function StateArchitectBoard({
48197
50455
  stepDurationMs = 600,
48198
50456
  testEvent,
48199
50457
  completeEvent,
50458
+ addTransitionEvent,
50459
+ removeTransitionEvent,
50460
+ playAgainEvent,
48200
50461
  className
48201
50462
  }) {
48202
50463
  const { emit } = useEventBus();
@@ -48209,14 +50470,16 @@ function StateArchitectBoard({
48209
50470
  const testCases = Array.isArray(resolved?.testCases) ? resolved.testCases : [];
48210
50471
  const entityTransitions = Array.isArray(resolved?.transitions) ? resolved.transitions : [];
48211
50472
  const entityVariables = rows(resolved?.variables);
48212
- const [transitions, setTransitions] = useState(entityTransitions);
50473
+ const transitions = entityTransitions;
50474
+ const attempts = typeof resolved?.attempts === "number" ? resolved.attempts : 0;
50475
+ const entityResult = str(resolved?.result) || "none";
50476
+ const isSuccess = entityResult === "win";
50477
+ const [isTesting, setIsTesting] = useState(false);
48213
50478
  const [headerError, setHeaderError] = useState(false);
48214
- const [playState, setPlayState] = useState("editing");
48215
50479
  const [currentState, setCurrentState] = useState(initialState);
48216
50480
  const [selectedState, setSelectedState] = useState(null);
48217
50481
  const [testResults, setTestResults] = useState([]);
48218
50482
  const [variables, setVariables] = useState(() => [...entityVariables]);
48219
- const [attempts, setAttempts] = useState(0);
48220
50483
  const timerRef = useRef(null);
48221
50484
  const [addingFrom, setAddingFrom] = useState(null);
48222
50485
  useEffect(() => () => {
@@ -48226,7 +50489,7 @@ function StateArchitectBoard({
48226
50489
  const GRAPH_H = 400;
48227
50490
  const positions = useMemo(() => layoutStates(entityStates, GRAPH_W, GRAPH_H), [entityStates]);
48228
50491
  const handleStateClick = useCallback((state) => {
48229
- if (playState !== "editing") return;
50492
+ if (isTesting) return;
48230
50493
  if (addingFrom) {
48231
50494
  if (addingFrom !== state) {
48232
50495
  const event = availableEvents[0] || "EVENT";
@@ -48236,20 +50499,20 @@ function StateArchitectBoard({
48236
50499
  to: state,
48237
50500
  event
48238
50501
  };
48239
- setTransitions((prev) => [...prev, newTrans]);
50502
+ if (addTransitionEvent) emit(`UI:${addTransitionEvent}`, { id: newTrans.id, from: newTrans.from, to: newTrans.to, event: newTrans.event });
48240
50503
  }
48241
50504
  setAddingFrom(null);
48242
50505
  } else {
48243
50506
  setSelectedState(state);
48244
50507
  }
48245
- }, [playState, addingFrom, availableEvents]);
50508
+ }, [isTesting, addingFrom, availableEvents, addTransitionEvent, emit]);
48246
50509
  const handleStartAddTransition = useCallback(() => {
48247
50510
  if (!selectedState) return;
48248
50511
  setAddingFrom(selectedState);
48249
50512
  }, [selectedState]);
48250
50513
  const handleRemoveTransition = useCallback((transId) => {
48251
- setTransitions((prev) => prev.filter((t2) => t2.id !== transId));
48252
- }, []);
50514
+ if (removeTransitionEvent) emit(`UI:${removeTransitionEvent}`, { id: transId });
50515
+ }, [removeTransitionEvent, emit]);
48253
50516
  const machine = useMemo(() => ({
48254
50517
  name: entityName,
48255
50518
  description: str(resolved?.description),
@@ -48263,16 +50526,16 @@ function StateArchitectBoard({
48263
50526
  }))
48264
50527
  }), [entityName, resolved, entityStates, currentState, transitions]);
48265
50528
  const handleTest = useCallback(() => {
48266
- if (playState !== "editing") return;
50529
+ if (isTesting) return;
48267
50530
  if (testEvent) emit(`UI:${testEvent}`, {});
48268
- setPlayState("testing");
50531
+ setIsTesting(true);
48269
50532
  setTestResults([]);
48270
50533
  const results = [];
48271
50534
  let testIdx = 0;
48272
50535
  const runNextTest = () => {
48273
50536
  if (testIdx >= testCases.length) {
48274
50537
  const allPassed = results.every((r2) => r2.passed);
48275
- setPlayState(allPassed ? "success" : "fail");
50538
+ setIsTesting(false);
48276
50539
  setTestResults(results);
48277
50540
  if (allPassed && completeEvent) {
48278
50541
  emit(`UI:${completeEvent}`, {
@@ -48280,9 +50543,6 @@ function StateArchitectBoard({
48280
50543
  passedTests: results.filter((r2) => r2.passed).length
48281
50544
  });
48282
50545
  }
48283
- if (!allPassed) {
48284
- setAttempts((prev) => prev + 1);
48285
- }
48286
50546
  return;
48287
50547
  }
48288
50548
  const testCase = testCases[testIdx];
@@ -48305,24 +50565,23 @@ function StateArchitectBoard({
48305
50565
  timerRef.current = setTimeout(runNextTest, stepDurationMs);
48306
50566
  };
48307
50567
  timerRef.current = setTimeout(runNextTest, stepDurationMs);
48308
- }, [playState, transitions, testCases, initialState, stepDurationMs, testEvent, completeEvent, emit]);
50568
+ }, [isTesting, transitions, testCases, initialState, stepDurationMs, testEvent, completeEvent, emit]);
48309
50569
  const handleTryAgain = useCallback(() => {
48310
50570
  if (timerRef.current) clearTimeout(timerRef.current);
48311
- setPlayState("editing");
50571
+ setIsTesting(false);
48312
50572
  setCurrentState(initialState);
48313
50573
  setTestResults([]);
48314
50574
  }, [initialState]);
48315
50575
  const handleReset = useCallback(() => {
48316
50576
  if (timerRef.current) clearTimeout(timerRef.current);
48317
- setTransitions(entityTransitions);
48318
- setPlayState("editing");
50577
+ setIsTesting(false);
48319
50578
  setCurrentState(initialState);
48320
50579
  setTestResults([]);
48321
50580
  setVariables([...entityVariables]);
48322
50581
  setSelectedState(null);
48323
50582
  setAddingFrom(null);
48324
- setAttempts(0);
48325
- }, [entityTransitions, initialState, entityVariables]);
50583
+ if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
50584
+ }, [initialState, entityVariables, playAgainEvent, emit]);
48326
50585
  const codeData = useMemo(() => ({
48327
50586
  name: entityName,
48328
50587
  states: entityStates,
@@ -48418,7 +50677,7 @@ function StateArchitectBoard({
48418
50677
  ]
48419
50678
  }
48420
50679
  ),
48421
- playState === "editing" && /* @__PURE__ */ jsx(HStack, { gap: "sm", children: /* @__PURE__ */ jsx(
50680
+ !isTesting && /* @__PURE__ */ jsx(HStack, { gap: "sm", children: /* @__PURE__ */ jsx(
48422
50681
  Button,
48423
50682
  {
48424
50683
  variant: "ghost",
@@ -48440,7 +50699,7 @@ function StateArchitectBoard({
48440
50699
  t2.guardHint,
48441
50700
  ")"
48442
50701
  ] }),
48443
- playState === "editing" && /* @__PURE__ */ jsx(
50702
+ !isTesting && /* @__PURE__ */ jsx(
48444
50703
  Button,
48445
50704
  {
48446
50705
  variant: "ghost",
@@ -48472,21 +50731,21 @@ function StateArchitectBoard({
48472
50731
  resolved.showCodeView !== false && /* @__PURE__ */ jsx(StateJsonView, { data: codeData, label: "View Code" })
48473
50732
  ] })
48474
50733
  ] }),
48475
- 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") }) }),
48476
- playState === "fail" && /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
50734
+ 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") }) }),
50735
+ !isTesting && !isSuccess && testResults.some((r2) => !r2.passed) && /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
48477
50736
  /* @__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]) }) }),
48478
- 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: [
50737
+ !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: [
48479
50738
  /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-accent font-bold shrink-0", children: "\u{1F4A1} " + t("game.hint") + ":" }),
48480
50739
  /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-foreground", children: hint })
48481
50740
  ] }) })
48482
50741
  ] }),
48483
50742
  /* @__PURE__ */ jsxs(HStack, { gap: "sm", children: [
48484
- playState === "fail" ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleTryAgain, children: "\u{1F504} " + t("puzzle.tryAgainButton") }) : /* @__PURE__ */ jsx(
50743
+ !isTesting && !isSuccess && testResults.some((r2) => !r2.passed) ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleTryAgain, children: "\u{1F504} " + t("puzzle.tryAgainButton") }) : /* @__PURE__ */ jsx(
48485
50744
  Button,
48486
50745
  {
48487
50746
  variant: "primary",
48488
50747
  onClick: handleTest,
48489
- disabled: playState !== "editing",
50748
+ disabled: isTesting,
48490
50749
  children: "\u25B6 " + t("game.runTests")
48491
50750
  }
48492
50751
  ),
@@ -48992,6 +51251,289 @@ var init_ToastSlot = __esm({
48992
51251
  ToastSlot.displayName = "ToastSlot";
48993
51252
  }
48994
51253
  });
51254
+ function tilesToIso(tiles) {
51255
+ return tiles.map((t) => ({
51256
+ x: t.x,
51257
+ y: t.y,
51258
+ terrain: t.terrain,
51259
+ terrainSprite: t.terrainSprite ?? TERRAIN_SPRITES[t.terrain ?? "ground"],
51260
+ passable: t.passable
51261
+ }));
51262
+ }
51263
+ function towersToUnits(towers) {
51264
+ return towers.map((t) => ({
51265
+ id: t.id,
51266
+ position: { x: t.x, y: t.y },
51267
+ name: "Tower",
51268
+ team: "player",
51269
+ sprite: TOWER_SPRITE,
51270
+ unitType: "guardian",
51271
+ health: 1,
51272
+ maxHealth: 1
51273
+ }));
51274
+ }
51275
+ function creepsToUnits(creeps) {
51276
+ return creeps.map((c) => ({
51277
+ id: c.id,
51278
+ position: { x: c.x, y: c.y },
51279
+ name: "Creep",
51280
+ team: "enemy",
51281
+ sprite: CREEP_SPRITE,
51282
+ unitType: "scrapper",
51283
+ health: c.hp,
51284
+ maxHealth: c.maxHp
51285
+ }));
51286
+ }
51287
+ function pathToFeatures(path) {
51288
+ return path.map((p2, i) => ({
51289
+ id: `path-${i}`,
51290
+ x: p2.x,
51291
+ y: p2.y,
51292
+ type: "path-marker",
51293
+ sprite: `${CDN6}world-map/road_straight.png`
51294
+ }));
51295
+ }
51296
+ function TowerDefenseBoard({
51297
+ entity,
51298
+ tiles: propTiles,
51299
+ path: propPath,
51300
+ towers: propTowers,
51301
+ creeps: propCreeps,
51302
+ gold: propGold,
51303
+ lives: propLives,
51304
+ wave: propWave,
51305
+ maxWaves: propMaxWaves,
51306
+ result: propResult,
51307
+ waveActive: propWaveActive,
51308
+ towerCost = 25,
51309
+ scale = 0.45,
51310
+ placeTowerEvent,
51311
+ startWaveEvent,
51312
+ playAgainEvent,
51313
+ gameEndEvent,
51314
+ className
51315
+ }) {
51316
+ const board = boardEntity(entity) ?? {};
51317
+ const eventBus = useEventBus();
51318
+ const { t } = useTranslate();
51319
+ const tiles = propTiles ?? rows(board.tiles);
51320
+ const path = propPath ?? rows(board.path);
51321
+ const towers = propTowers ?? rows(board.towers);
51322
+ const creeps = propCreeps ?? rows(board.creeps);
51323
+ const gold = propGold ?? num(board.gold, 100);
51324
+ const lives = propLives ?? num(board.lives, 20);
51325
+ const wave = propWave ?? num(board.wave, 1);
51326
+ const maxWaves = propMaxWaves ?? num(board.maxWaves, 5);
51327
+ const result = propResult ?? (str(board.result) || "none");
51328
+ const waveActive = propWaveActive ?? Boolean(board.waveActive);
51329
+ const [hoveredTile, setHoveredTile] = useState(null);
51330
+ const emittedGameEnd = useRef(false);
51331
+ if (result !== "none" && !emittedGameEnd.current) {
51332
+ emittedGameEnd.current = true;
51333
+ if (gameEndEvent) {
51334
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
51335
+ }
51336
+ }
51337
+ if (result === "none") {
51338
+ emittedGameEnd.current = false;
51339
+ }
51340
+ const towerPositions = useMemo(() => new Set(towers.map((t2) => `${t2.x},${t2.y}`)), [towers]);
51341
+ const pathPositions = useMemo(() => new Set(path.map((p2) => `${p2.x},${p2.y}`)), [path]);
51342
+ const validMoves = useMemo(() => {
51343
+ if (result !== "none" || gold < towerCost) return [];
51344
+ return tiles.filter(
51345
+ (t2) => t2.passable !== false && !towerPositions.has(`${t2.x},${t2.y}`) && !pathPositions.has(`${t2.x},${t2.y}`)
51346
+ ).map((t2) => ({ x: t2.x, y: t2.y }));
51347
+ }, [tiles, towerPositions, pathPositions, result, gold, towerCost]);
51348
+ const isoTiles = useMemo(() => tilesToIso(tiles), [tiles]);
51349
+ const towerUnits = useMemo(() => towersToUnits(towers), [towers]);
51350
+ const creepUnits = useMemo(() => creepsToUnits(creeps), [creeps]);
51351
+ const isoUnits = useMemo(() => [...towerUnits, ...creepUnits], [towerUnits, creepUnits]);
51352
+ const pathFeatures = useMemo(() => pathToFeatures(path), [path]);
51353
+ const handleTileClick = useCallback((x, y) => {
51354
+ if (result !== "none") return;
51355
+ if (pathPositions.has(`${x},${y}`)) return;
51356
+ if (towerPositions.has(`${x},${y}`)) return;
51357
+ if (gold < towerCost) return;
51358
+ if (placeTowerEvent) {
51359
+ eventBus.emit(`UI:${placeTowerEvent}`, { x, y });
51360
+ }
51361
+ }, [result, pathPositions, towerPositions, gold, towerCost, placeTowerEvent, eventBus]);
51362
+ const handleStartWave = useCallback(() => {
51363
+ if (startWaveEvent) {
51364
+ eventBus.emit(`UI:${startWaveEvent}`, { wave });
51365
+ }
51366
+ }, [startWaveEvent, wave, eventBus]);
51367
+ const handlePlayAgain = useCallback(() => {
51368
+ if (playAgainEvent) {
51369
+ eventBus.emit(`UI:${playAgainEvent}`, {});
51370
+ }
51371
+ }, [playAgainEvent, eventBus]);
51372
+ const canPlaceTower = gold >= towerCost && result === "none";
51373
+ const canStartWave = !waveActive && result === "none";
51374
+ return /* @__PURE__ */ jsxs(VStack, { className: cn("tower-defense-board relative min-h-[600px] bg-background", className), gap: "none", children: [
51375
+ /* @__PURE__ */ jsxs(
51376
+ HStack,
51377
+ {
51378
+ className: "px-4 py-2 border-b border-border bg-surface",
51379
+ gap: "lg",
51380
+ align: "center",
51381
+ children: [
51382
+ /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
51383
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", children: t("td.gold") ?? "Gold" }),
51384
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", weight: "bold", className: "text-warning", children: gold })
51385
+ ] }),
51386
+ /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
51387
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", children: t("td.lives") ?? "Lives" }),
51388
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", weight: "bold", className: lives <= 5 ? "text-error" : "text-success", children: lives })
51389
+ ] }),
51390
+ /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
51391
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", children: t("td.wave") ?? "Wave" }),
51392
+ /* @__PURE__ */ jsxs(Typography, { variant: "body2", weight: "bold", children: [
51393
+ wave,
51394
+ " / ",
51395
+ maxWaves
51396
+ ] })
51397
+ ] }),
51398
+ /* @__PURE__ */ jsxs(Box, { className: "ml-auto", children: [
51399
+ canStartWave && /* @__PURE__ */ jsx(Button, { variant: "primary", size: "sm", onClick: handleStartWave, children: t("td.startWave") ?? "Start Wave" }),
51400
+ waveActive && /* @__PURE__ */ jsx(Typography, { variant: "small", className: "text-warning animate-pulse", children: t("td.waveInProgress") ?? "Wave in progress\u2026" })
51401
+ ] })
51402
+ ]
51403
+ }
51404
+ ),
51405
+ /* @__PURE__ */ jsxs(Box, { className: "relative flex-1", children: [
51406
+ /* @__PURE__ */ jsx(
51407
+ IsometricCanvas_default,
51408
+ {
51409
+ tiles: isoTiles,
51410
+ units: isoUnits,
51411
+ features: pathFeatures,
51412
+ validMoves,
51413
+ hoveredTile,
51414
+ onTileClick: handleTileClick,
51415
+ onTileHover: (x, y) => setHoveredTile({ x, y }),
51416
+ onTileLeave: () => setHoveredTile(null),
51417
+ scale
51418
+ }
51419
+ ),
51420
+ 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)` }) })
51421
+ ] }),
51422
+ 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: [
51423
+ /* @__PURE__ */ jsx(
51424
+ Typography,
51425
+ {
51426
+ variant: "h2",
51427
+ className: cn(
51428
+ "text-4xl font-black tracking-widest uppercase",
51429
+ result === "won" ? "text-warning" : "text-error"
51430
+ ),
51431
+ children: result === "won" ? t("td.victory") ?? "Victory!" : t("td.defeat") ?? "Defeat!"
51432
+ }
51433
+ ),
51434
+ /* @__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." }),
51435
+ /* @__PURE__ */ jsx(
51436
+ Button,
51437
+ {
51438
+ variant: "primary",
51439
+ className: "px-8 py-3 font-semibold",
51440
+ onClick: handlePlayAgain,
51441
+ children: t("td.playAgain") ?? "Play Again"
51442
+ }
51443
+ )
51444
+ ] }) })
51445
+ ] });
51446
+ }
51447
+ var CDN6, TERRAIN_SPRITES, TOWER_SPRITE, CREEP_SPRITE;
51448
+ var init_TowerDefenseBoard = __esm({
51449
+ "components/game/organisms/TowerDefenseBoard.tsx"() {
51450
+ "use client";
51451
+ init_cn();
51452
+ init_useEventBus();
51453
+ init_Box();
51454
+ init_Button();
51455
+ init_Typography();
51456
+ init_Stack();
51457
+ init_IsometricCanvas();
51458
+ init_boardEntity();
51459
+ CDN6 = "https://almadar-kflow-assets.web.app/shared/";
51460
+ TERRAIN_SPRITES = {
51461
+ ground: `${CDN6}isometric-blocks/PNG/Platformer tiles/platformerTile_01.png`,
51462
+ path: `${CDN6}isometric-blocks/PNG/Platformer tiles/platformerTile_09.png`,
51463
+ wall: `${CDN6}isometric-blocks/PNG/Platformer tiles/platformerTile_05.png`
51464
+ };
51465
+ TOWER_SPRITE = `${CDN6}units/guardian.png`;
51466
+ CREEP_SPRITE = `${CDN6}units/scrapper.png`;
51467
+ TowerDefenseBoard.displayName = "TowerDefenseBoard";
51468
+ }
51469
+ });
51470
+ function TowerDefenseTemplate({
51471
+ entity,
51472
+ title = "Tower Defense",
51473
+ tiles,
51474
+ path,
51475
+ towers,
51476
+ gold,
51477
+ lives,
51478
+ maxWaves,
51479
+ towerCost,
51480
+ scale,
51481
+ placeTowerEvent,
51482
+ startWaveEvent,
51483
+ playAgainEvent,
51484
+ gameEndEvent,
51485
+ className
51486
+ }) {
51487
+ const resolved = entity && typeof entity === "object" && !Array.isArray(entity) ? entity : void 0;
51488
+ return /* @__PURE__ */ jsxs(
51489
+ Box,
51490
+ {
51491
+ display: "flex",
51492
+ fullHeight: true,
51493
+ className: cn("tower-defense-template flex-col", className),
51494
+ children: [
51495
+ /* @__PURE__ */ jsx(
51496
+ HStack,
51497
+ {
51498
+ gap: "sm",
51499
+ align: "center",
51500
+ className: "px-4 py-3 border-b-2 border-border bg-surface shrink-0",
51501
+ children: /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title })
51502
+ }
51503
+ ),
51504
+ /* @__PURE__ */ jsx(Box, { className: "flex-1 relative overflow-hidden", children: /* @__PURE__ */ jsx(
51505
+ TowerDefenseBoard,
51506
+ {
51507
+ entity: resolved,
51508
+ tiles,
51509
+ path,
51510
+ towers,
51511
+ gold,
51512
+ lives,
51513
+ maxWaves,
51514
+ towerCost,
51515
+ scale,
51516
+ placeTowerEvent,
51517
+ startWaveEvent,
51518
+ playAgainEvent,
51519
+ gameEndEvent,
51520
+ className: "h-full"
51521
+ }
51522
+ ) })
51523
+ ]
51524
+ }
51525
+ );
51526
+ }
51527
+ var init_TowerDefenseTemplate = __esm({
51528
+ "components/game/templates/TowerDefenseTemplate.tsx"() {
51529
+ init_cn();
51530
+ init_Box();
51531
+ init_Stack();
51532
+ init_Typography();
51533
+ init_TowerDefenseBoard();
51534
+ TowerDefenseTemplate.displayName = "TowerDefenseTemplate";
51535
+ }
51536
+ });
48995
51537
  function useBattleState(initialUnits, eventConfig = {}, callbacks = {}) {
48996
51538
  const eventBus = useEventBus();
48997
51539
  const {
@@ -49190,17 +51732,12 @@ var init_UncontrolledBattleBoard = __esm({
49190
51732
  }
49191
51733
  });
49192
51734
  function heroPosition(h) {
51735
+ if ("position" in h && h.position != null) return h.position;
49193
51736
  return vec2(h.position);
49194
51737
  }
49195
- function heroOwner(h) {
49196
- return str(h.owner);
49197
- }
49198
51738
  function heroMovement(h) {
49199
51739
  return num(h.movement);
49200
51740
  }
49201
- function hexPassable(h) {
49202
- return h.passable !== false;
49203
- }
49204
51741
  function defaultIsInRange(from, to, range) {
49205
51742
  return Math.abs(from.x - to.x) + Math.abs(from.y - to.y) <= range;
49206
51743
  }
@@ -49236,38 +51773,41 @@ function WorldMapBoard({
49236
51773
  }) {
49237
51774
  const eventBus = useEventBus();
49238
51775
  const resolved = boardEntity(entity);
49239
- const hexes = rows(resolved?.hexes);
49240
- const heroes = rows(resolved?.heroes);
51776
+ const entityUnits = rows(resolved?.units);
51777
+ const entityTiles = rows(resolved?.tiles);
49241
51778
  const features = propFeatures ?? (Array.isArray(resolved?.features) ? resolved.features : []);
49242
51779
  const selectedHeroId = resolved?.selectedHeroId ?? null;
49243
51780
  const assetManifest = propAssetManifest ?? resolved?.assetManifest;
49244
51781
  const backgroundImage = resolved?.backgroundImage;
49245
51782
  const [hoveredTile, setHoveredTile] = useState(null);
49246
- const selectedHero = useMemo(
49247
- () => heroes.find((h) => str(h.id) === selectedHeroId) ?? null,
49248
- [heroes, selectedHeroId]
49249
- );
49250
51783
  const derivedTiles = useMemo(
49251
- () => hexes.map((hex) => ({
49252
- x: num(hex.x),
49253
- y: num(hex.y),
49254
- terrain: str(hex.terrain),
49255
- terrainSprite: hex.terrainSprite == null ? void 0 : str(hex.terrainSprite)
51784
+ () => entityTiles.map((t) => ({
51785
+ x: num(t.x),
51786
+ y: num(t.y),
51787
+ terrain: str(t.terrain),
51788
+ terrainSprite: t.terrainSprite == null ? void 0 : str(t.terrainSprite),
51789
+ passable: t.passable !== false
49256
51790
  })),
49257
- [hexes]
51791
+ [entityTiles]
49258
51792
  );
49259
51793
  const tiles = propTiles ?? derivedTiles;
49260
51794
  const baseUnits = useMemo(
49261
- () => propUnits ?? heroes.map((hero) => ({
49262
- id: str(hero.id),
49263
- position: heroPosition(hero),
49264
- name: str(hero.name),
49265
- team: heroOwner(hero) === "enemy" ? "enemy" : "player",
49266
- health: 100,
49267
- maxHealth: 100,
49268
- sprite: hero.sprite == null ? void 0 : str(hero.sprite)
51795
+ () => propUnits ?? entityUnits.map((u) => ({
51796
+ id: str(u.id),
51797
+ position: heroPosition(u),
51798
+ name: str(u.name),
51799
+ // lolo uses `team` field (not `owner`)
51800
+ team: str(u.team) === "enemy" ? "enemy" : "player",
51801
+ health: num(u.health) || 100,
51802
+ maxHealth: num(u.maxHealth) || 100,
51803
+ sprite: u.sprite == null ? void 0 : str(u.sprite)
49269
51804
  })),
49270
- [heroes, propUnits]
51805
+ [entityUnits, propUnits]
51806
+ );
51807
+ const gameUnits = baseUnits;
51808
+ const selectedHero = useMemo(
51809
+ () => gameUnits.find((u) => u.id === selectedHeroId) ?? null,
51810
+ [gameUnits, selectedHeroId]
49271
51811
  );
49272
51812
  const MOVE_SPEED_MS_PER_TILE = 300;
49273
51813
  const movementAnimRef = useRef(null);
@@ -49313,51 +51853,50 @@ function WorldMapBoard({
49313
51853
  const validMoves = useMemo(() => {
49314
51854
  if (!selectedHero || heroMovement(selectedHero) <= 0) return [];
49315
51855
  const sp = heroPosition(selectedHero);
49316
- const sOwner = heroOwner(selectedHero);
51856
+ const sTeam = str(selectedHero.team);
49317
51857
  const range = heroMovement(selectedHero);
49318
51858
  const moves = [];
49319
- hexes.forEach((hex) => {
49320
- const hx = num(hex.x);
49321
- const hy = num(hex.y);
49322
- if (!hexPassable(hex)) return;
49323
- if (hx === sp.x && hy === sp.y) return;
49324
- if (!isInRange(sp, { x: hx, y: hy }, range)) return;
49325
- if (heroes.some((h) => {
49326
- const hp = heroPosition(h);
49327
- return hp.x === hx && hp.y === hy && heroOwner(h) === sOwner;
51859
+ tiles.forEach((t) => {
51860
+ const tx = t.x;
51861
+ const ty = t.y;
51862
+ if (t.passable === false) return;
51863
+ if (tx === sp.x && ty === sp.y) return;
51864
+ if (!isInRange(sp, { x: tx, y: ty }, range)) return;
51865
+ if (gameUnits.some((u) => {
51866
+ const up = u.position ?? { x: u.x ?? -1, y: u.y ?? -1 };
51867
+ return up.x === tx && up.y === ty && str(u.team) === sTeam;
49328
51868
  })) return;
49329
- moves.push({ x: hx, y: hy });
51869
+ moves.push({ x: tx, y: ty });
49330
51870
  });
49331
51871
  return moves;
49332
- }, [selectedHero, hexes, heroes, isInRange]);
51872
+ }, [selectedHero, tiles, gameUnits, isInRange]);
49333
51873
  const attackTargets = useMemo(() => {
49334
51874
  if (!selectedHero || heroMovement(selectedHero) <= 0) return [];
49335
51875
  const sp = heroPosition(selectedHero);
49336
- const sOwner = heroOwner(selectedHero);
51876
+ const sTeam = str(selectedHero.team);
49337
51877
  const range = heroMovement(selectedHero);
49338
- return heroes.filter((h) => heroOwner(h) !== sOwner).filter((h) => isInRange(sp, heroPosition(h), range)).map((h) => heroPosition(h));
49339
- }, [selectedHero, heroes, isInRange]);
49340
- const maxY = Math.max(...hexes.map((h) => num(h.y)), 0);
51878
+ 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 });
51879
+ }, [selectedHero, gameUnits, isInRange]);
51880
+ const maxY = Math.max(...tiles.map((t) => t.y), 0);
49341
51881
  const baseOffsetX = (maxY + 1) * (TILE_WIDTH * scale / 2);
49342
51882
  const tileToScreen = useCallback(
49343
51883
  (tx, ty) => isoToScreen(tx, ty, scale, baseOffsetX),
49344
51884
  [scale, baseOffsetX]
49345
51885
  );
49346
51886
  const hoveredHex = useMemo(
49347
- () => hoveredTile ? hexes.find((h) => num(h.x) === hoveredTile.x && num(h.y) === hoveredTile.y) ?? null : null,
49348
- [hoveredTile, hexes]
51887
+ () => hoveredTile ? tiles.find((t) => t.x === hoveredTile.x && t.y === hoveredTile.y) ?? null : null,
51888
+ [hoveredTile, tiles]
49349
51889
  );
49350
51890
  const hoveredHero = useMemo(
49351
- () => hoveredTile ? heroes.find((h) => {
49352
- const hp = heroPosition(h);
49353
- return hp.x === hoveredTile.x && hp.y === hoveredTile.y;
51891
+ () => hoveredTile ? gameUnits.find((u) => {
51892
+ const up = u.position ?? { x: u.x ?? -1, y: u.y ?? -1 };
51893
+ return up.x === hoveredTile.x && up.y === hoveredTile.y;
49354
51894
  }) ?? null : null,
49355
- [hoveredTile, heroes]
51895
+ [hoveredTile, gameUnits]
49356
51896
  );
49357
51897
  const handleTileClick = useCallback((x, y) => {
49358
51898
  if (movementAnimRef.current) return;
49359
- const hex = hexes.find((h) => num(h.x) === x && num(h.y) === y);
49360
- if (!hex) return;
51899
+ const tile = tiles.find((t) => t.x === x && t.y === y);
49361
51900
  if (tileClickEvent) {
49362
51901
  eventBus.emit(`UI:${tileClickEvent}`, { x, y });
49363
51902
  }
@@ -49368,38 +51907,39 @@ function WorldMapBoard({
49368
51907
  if (heroMoveEvent) {
49369
51908
  eventBus.emit(`UI:${heroMoveEvent}`, { heroId, toX: x, toY: y });
49370
51909
  }
49371
- const feature = str(hex.feature);
51910
+ const feature = tile ? str(tile.feature) : "";
49372
51911
  if (feature && feature !== "none") {
49373
- onFeatureEnter?.(heroId, hex);
51912
+ const tileRow = tile;
51913
+ onFeatureEnter?.(heroId, tileRow);
49374
51914
  if (featureEnterEvent) {
49375
- eventBus.emit(`UI:${featureEnterEvent}`, { heroId, feature, hex });
51915
+ eventBus.emit(`UI:${featureEnterEvent}`, { heroId, feature, hex: tileRow });
49376
51916
  }
49377
51917
  }
49378
51918
  });
49379
51919
  return;
49380
51920
  }
49381
- const enemy = heroes.find((h) => {
49382
- const hp = heroPosition(h);
49383
- return hp.x === x && hp.y === y && heroOwner(h) === "enemy";
51921
+ const enemy = gameUnits.find((u) => {
51922
+ const up = u.position ?? { x: u.x ?? -1, y: u.y ?? -1 };
51923
+ return up.x === x && up.y === y && str(u.team) === "enemy";
49384
51924
  });
49385
51925
  if (selectedHero && enemy && attackTargets.some((t) => t.x === x && t.y === y)) {
49386
51926
  const attackerId = str(selectedHero.id);
49387
- const defenderId = str(enemy.id);
51927
+ const defenderId = enemy.id;
49388
51928
  onBattleEncounter?.(attackerId, defenderId);
49389
51929
  if (battleEncounterEvent) {
49390
51930
  eventBus.emit(`UI:${battleEncounterEvent}`, { attackerId, defenderId });
49391
51931
  }
49392
51932
  }
49393
- }, [hexes, heroes, selectedHero, validMoves, attackTargets, startMoveAnimation, onHeroMove, onFeatureEnter, onBattleEncounter, eventBus, tileClickEvent, heroMoveEvent, featureEnterEvent, battleEncounterEvent]);
51933
+ }, [tiles, gameUnits, selectedHero, validMoves, attackTargets, startMoveAnimation, onHeroMove, onFeatureEnter, onBattleEncounter, eventBus, tileClickEvent, heroMoveEvent, featureEnterEvent, battleEncounterEvent]);
49394
51934
  const handleUnitClick = useCallback((unitId) => {
49395
- const hero = heroes.find((h) => str(h.id) === unitId);
49396
- if (hero && (heroOwner(hero) === "player" || allowMoveAllHeroes)) {
51935
+ const unit = gameUnits.find((u) => u.id === unitId);
51936
+ if (unit && (str(unit.team) === "player" || allowMoveAllHeroes)) {
49397
51937
  onHeroSelect?.(unitId);
49398
51938
  if (heroSelectEvent) {
49399
51939
  eventBus.emit(`UI:${heroSelectEvent}`, { heroId: unitId });
49400
51940
  }
49401
51941
  }
49402
- }, [heroes, onHeroSelect, allowMoveAllHeroes, eventBus, heroSelectEvent]);
51942
+ }, [gameUnits, onHeroSelect, allowMoveAllHeroes, eventBus, heroSelectEvent]);
49403
51943
  const selectHero = useCallback((id) => {
49404
51944
  onHeroSelect?.(id);
49405
51945
  if (heroSelectEvent) {
@@ -49503,48 +52043,48 @@ function WorldMapTemplate({
49503
52043
  }
49504
52044
  );
49505
52045
  }
49506
- var CDN3, DEFAULT_WORLDMAP_TILES, DEFAULT_WORLDMAP_UNITS, DEFAULT_WORLDMAP_FEATURES, DEFAULT_WORLDMAP_MANIFEST;
52046
+ var CDN7, DEFAULT_WORLDMAP_TILES, DEFAULT_WORLDMAP_UNITS, DEFAULT_WORLDMAP_FEATURES, DEFAULT_WORLDMAP_MANIFEST;
49507
52047
  var init_WorldMapTemplate = __esm({
49508
52048
  "components/game/templates/WorldMapTemplate.tsx"() {
49509
52049
  init_WorldMapBoard();
49510
- CDN3 = "https://almadar-kflow-assets.web.app/shared";
52050
+ CDN7 = "https://almadar-kflow-assets.web.app/shared";
49511
52051
  DEFAULT_WORLDMAP_TILES = [
49512
- { x: 0, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49513
- { x: 1, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49514
- { x: 2, y: 0, terrain: "water", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
49515
- { x: 3, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49516
- { x: 4, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49517
- { x: 0, y: 1, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49518
- { x: 1, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49519
- { x: 2, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49520
- { x: 3, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49521
- { x: 4, y: 1, terrain: "water", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
49522
- { x: 0, y: 2, terrain: "water", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
49523
- { x: 1, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49524
- { x: 2, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
49525
- { x: 3, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49526
- { x: 4, y: 2, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49527
- { x: 0, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49528
- { x: 1, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49529
- { x: 2, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49530
- { x: 3, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
49531
- { x: 4, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49532
- { x: 0, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49533
- { x: 1, y: 4, terrain: "water", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
49534
- { x: 2, y: 4, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
49535
- { x: 3, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
49536
- { x: 4, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` }
52052
+ { x: 0, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
52053
+ { x: 1, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
52054
+ { x: 2, y: 0, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
52055
+ { x: 3, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
52056
+ { x: 4, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
52057
+ { x: 0, y: 1, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
52058
+ { x: 1, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
52059
+ { x: 2, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
52060
+ { x: 3, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
52061
+ { x: 4, y: 1, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
52062
+ { x: 0, y: 2, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
52063
+ { x: 1, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
52064
+ { x: 2, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
52065
+ { x: 3, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
52066
+ { x: 4, y: 2, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
52067
+ { x: 0, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
52068
+ { x: 1, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
52069
+ { x: 2, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
52070
+ { x: 3, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
52071
+ { x: 4, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
52072
+ { x: 0, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
52073
+ { x: 1, y: 4, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
52074
+ { x: 2, y: 4, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
52075
+ { x: 3, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
52076
+ { x: 4, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` }
49537
52077
  ];
49538
52078
  DEFAULT_WORLDMAP_UNITS = [
49539
- { 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` },
49540
- { 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` }
52079
+ { 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` },
52080
+ { 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` }
49541
52081
  ];
49542
52082
  DEFAULT_WORLDMAP_FEATURES = [
49543
- { id: "f1", x: 2, y: 2, type: "capital", sprite: `${CDN3}/scenes/world/capital.png` },
49544
- { id: "f2", x: 4, y: 2, type: "power_node", sprite: `${CDN3}/world-map/power_node.png` }
52083
+ { id: "f1", x: 2, y: 2, type: "capital", sprite: `${CDN7}/scenes/world/capital.png` },
52084
+ { id: "f2", x: 4, y: 2, type: "power_node", sprite: `${CDN7}/world-map/power_node.png` }
49545
52085
  ];
49546
52086
  DEFAULT_WORLDMAP_MANIFEST = {
49547
- baseUrl: CDN3,
52087
+ baseUrl: CDN7,
49548
52088
  terrains: {
49549
52089
  grass: "/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png",
49550
52090
  water: "/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png",
@@ -49589,7 +52129,7 @@ function lazyThree(name, loader) {
49589
52129
  ThreeWrapper.displayName = `Lazy(${name})`;
49590
52130
  return ThreeWrapper;
49591
52131
  }
49592
- var ThreeBoundary, FeatureRenderer, GameCanvas3D, GameCanvas3DBattleTemplate, GameCanvas3DCastleTemplate, GameCanvas3DWorldMapTemplate, COMPONENT_REGISTRY;
52132
+ var ThreeBoundary, FeatureRenderer, GameCanvas3D2, GameCanvas3DBattleTemplate, GameCanvas3DCastleTemplate, GameCanvas3DWorldMapTemplate, COMPONENT_REGISTRY;
49593
52133
  var init_component_registry_generated = __esm({
49594
52134
  "components/core/organisms/component-registry.generated.ts"() {
49595
52135
  init_AboutPageTemplate();
@@ -49699,6 +52239,7 @@ var init_component_registry_generated = __esm({
49699
52239
  init_FormSectionHeader();
49700
52240
  init_GameAudioProvider();
49701
52241
  init_GameAudioToggle();
52242
+ init_GameBoard3D();
49702
52243
  init_GameCanvas2D();
49703
52244
  init_GameHud();
49704
52245
  init_GameMenu();
@@ -49756,7 +52297,9 @@ var init_component_registry_generated = __esm({
49756
52297
  init_PageHeader();
49757
52298
  init_Pagination();
49758
52299
  init_PatternTile();
52300
+ init_PlatformerBoard();
49759
52301
  init_PlatformerCanvas();
52302
+ init_PlatformerTemplate();
49760
52303
  init_Popover();
49761
52304
  init_PositionedCanvas();
49762
52305
  init_PowerupSlots();
@@ -49780,6 +52323,8 @@ var init_component_registry_generated = __esm({
49780
52323
  init_ResourceBar();
49781
52324
  init_ResourceCounter();
49782
52325
  init_RichBlockEditor();
52326
+ init_RoguelikeBoard();
52327
+ init_RoguelikeTemplate();
49783
52328
  init_RuntimeDebugger2();
49784
52329
  init_ScaledDiagram();
49785
52330
  init_ScoreBoard();
@@ -49852,6 +52397,8 @@ var init_component_registry_generated = __esm({
49852
52397
  init_Toast();
49853
52398
  init_ToastSlot();
49854
52399
  init_Tooltip();
52400
+ init_TowerDefenseBoard();
52401
+ init_TowerDefenseTemplate();
49855
52402
  init_TraitFrame();
49856
52403
  init_TraitSlot();
49857
52404
  init_TrendIndicator();
@@ -49896,7 +52443,7 @@ var init_component_registry_generated = __esm({
49896
52443
  }
49897
52444
  };
49898
52445
  FeatureRenderer = lazyThree("FeatureRenderer", () => import('@almadar/ui/components/molecules/game/three'));
49899
- GameCanvas3D = lazyThree("GameCanvas3D", () => import('@almadar/ui/components/molecules/game/three'));
52446
+ GameCanvas3D2 = lazyThree("GameCanvas3D", () => import('@almadar/ui/components/molecules/game/three'));
49900
52447
  GameCanvas3DBattleTemplate = lazyThree("GameCanvas3DBattleTemplate", () => import('@almadar/ui/components/molecules/game/three'));
49901
52448
  GameCanvas3DCastleTemplate = lazyThree("GameCanvas3DCastleTemplate", () => import('@almadar/ui/components/molecules/game/three'));
49902
52449
  GameCanvas3DWorldMapTemplate = lazyThree("GameCanvas3DWorldMapTemplate", () => import('@almadar/ui/components/molecules/game/three'));
@@ -50015,8 +52562,9 @@ var init_component_registry_generated = __esm({
50015
52562
  "FormSectionHeader": FormSectionHeader,
50016
52563
  "GameAudioProvider": GameAudioProvider,
50017
52564
  "GameAudioToggle": GameAudioToggle,
52565
+ "GameBoard3D": GameBoard3D,
50018
52566
  "GameCanvas2D": GameCanvas2D,
50019
- "GameCanvas3D": GameCanvas3D,
52567
+ "GameCanvas3D": GameCanvas3D2,
50020
52568
  "GameCanvas3DBattleTemplate": GameCanvas3DBattleTemplate,
50021
52569
  "GameCanvas3DCastleTemplate": GameCanvas3DCastleTemplate,
50022
52570
  "GameCanvas3DWorldMapTemplate": GameCanvas3DWorldMapTemplate,
@@ -50078,7 +52626,9 @@ var init_component_registry_generated = __esm({
50078
52626
  "PageHeader": PageHeader,
50079
52627
  "Pagination": Pagination,
50080
52628
  "PatternTile": PatternTile,
52629
+ "PlatformerBoard": PlatformerBoard,
50081
52630
  "PlatformerCanvas": PlatformerCanvas,
52631
+ "PlatformerTemplate": PlatformerTemplate,
50082
52632
  "Popover": Popover,
50083
52633
  "PositionedCanvas": PositionedCanvas,
50084
52634
  "PowerupSlots": PowerupSlots,
@@ -50102,6 +52652,8 @@ var init_component_registry_generated = __esm({
50102
52652
  "ResourceBar": ResourceBar,
50103
52653
  "ResourceCounter": ResourceCounter,
50104
52654
  "RichBlockEditor": RichBlockEditor,
52655
+ "RoguelikeBoard": RoguelikeBoard,
52656
+ "RoguelikeTemplate": RoguelikeTemplate,
50105
52657
  "RuntimeDebugger": RuntimeDebugger,
50106
52658
  "ScaledDiagram": ScaledDiagram,
50107
52659
  "ScoreBoard": ScoreBoard,
@@ -50177,6 +52729,8 @@ var init_component_registry_generated = __esm({
50177
52729
  "Toast": Toast,
50178
52730
  "ToastSlot": ToastSlot,
50179
52731
  "Tooltip": Tooltip,
52732
+ "TowerDefenseBoard": TowerDefenseBoard,
52733
+ "TowerDefenseTemplate": TowerDefenseTemplate,
50180
52734
  "TraitFrame": TraitFrame,
50181
52735
  "TraitSlot": TraitSlot,
50182
52736
  "TrendIndicator": TrendIndicator,
@@ -54663,7 +57217,7 @@ function convertObjectProps(props) {
54663
57217
  function convertFnFormLambdasInProps(props) {
54664
57218
  return convertObjectProps(props);
54665
57219
  }
54666
- var log9 = createLogger("almadar:ui:effects:client-handlers");
57220
+ var log11 = createLogger("almadar:ui:effects:client-handlers");
54667
57221
  function createClientEffectHandlers(options) {
54668
57222
  const { eventBus, slotSetter, navigate, notify, callService } = options;
54669
57223
  return {
@@ -54672,10 +57226,10 @@ function createClientEffectHandlers(options) {
54672
57226
  eventBus.emit(prefixedEvent, payload);
54673
57227
  },
54674
57228
  persist: async () => {
54675
- log9.warn("persist is server-side only, ignored on client");
57229
+ log11.warn("persist is server-side only, ignored on client");
54676
57230
  },
54677
57231
  set: () => {
54678
- log9.warn("set is server-side only, ignored on client");
57232
+ log11.warn("set is server-side only, ignored on client");
54679
57233
  },
54680
57234
  callService: async (service, action, params) => {
54681
57235
  if (callService) return callService(service, action, params);
@@ -54704,10 +57258,10 @@ function createClientEffectHandlers(options) {
54704
57258
  slotSetter.addPattern(slot, pattern, props);
54705
57259
  },
54706
57260
  navigate: navigate ?? ((path) => {
54707
- log9.warn("No navigate handler, ignoring", { path });
57261
+ log11.warn("No navigate handler, ignoring", { path });
54708
57262
  }),
54709
57263
  notify: notify ?? ((msg, type) => {
54710
- log9.debug("notify", { type, message: msg });
57264
+ log11.debug("notify", { type, message: msg });
54711
57265
  })
54712
57266
  };
54713
57267
  }
@@ -55872,7 +58426,7 @@ OrbitalThemeProvider.displayName = "OrbitalThemeProvider";
55872
58426
  init_navigation();
55873
58427
  init_verificationRegistry();
55874
58428
  var PERF_NAMESPACE = "almadar:perf:canvas";
55875
- var log10 = createLogger(PERF_NAMESPACE);
58429
+ var log12 = createLogger(PERF_NAMESPACE);
55876
58430
  var RING_SIZE = 50;
55877
58431
  var ring = [];
55878
58432
  var writeIdx = 0;
@@ -55923,7 +58477,7 @@ function perfEnd(name, startToken, detail) {
55923
58477
  }
55924
58478
  }
55925
58479
  push({ name, durationMs, ts: endTs, detail });
55926
- log10.debug(name, () => ({ durationMs, ...detail ?? {} }));
58480
+ log12.debug(name, () => ({ durationMs, ...detail ?? {} }));
55927
58481
  }
55928
58482
  var profilerOnRender = (id, phase, actualDuration, baseDuration, _startTime, commitTime) => {
55929
58483
  if (!isEnabled()) return;
@@ -55933,7 +58487,7 @@ var profilerOnRender = (id, phase, actualDuration, baseDuration, _startTime, com
55933
58487
  ts: commitTime,
55934
58488
  detail: { baseDuration }
55935
58489
  });
55936
- log10.debug(`profiler:${id}:${phase}`, () => ({ actualDuration, baseDuration }));
58490
+ log12.debug(`profiler:${id}:${phase}`, () => ({ actualDuration, baseDuration }));
55937
58491
  };
55938
58492
 
55939
58493
  // runtime/prepareSchemaForPreview.ts
@@ -56576,7 +59130,7 @@ init_useEventBus();
56576
59130
  // components/avl/molecules/useCanvasDnd.tsx
56577
59131
  init_useEventBus();
56578
59132
  init_useAlmadarDndCollision();
56579
- var log11 = createLogger("almadar:ui:canvas-dnd");
59133
+ var log13 = createLogger("almadar:ui:canvas-dnd");
56580
59134
  function useCanvasDraggable({
56581
59135
  id,
56582
59136
  payload,
@@ -56615,7 +59169,7 @@ function defaultEmit(eventBus, drop) {
56615
59169
  if (payload.kind === "pattern") {
56616
59170
  const patternType = payload.data.type;
56617
59171
  if (typeof patternType !== "string") {
56618
- log11.warn("default-emit:pattern:missing-type");
59172
+ log13.warn("default-emit:pattern:missing-type");
56619
59173
  return;
56620
59174
  }
56621
59175
  const out = { patternType, containerNode: target.containerNode };
@@ -56624,23 +59178,23 @@ function defaultEmit(eventBus, drop) {
56624
59178
  out.index = resolved.index;
56625
59179
  }
56626
59180
  eventBus.emit("UI:PATTERN_DROP", out);
56627
- log11.info("default-emit:pattern", { patternType, level: target.level });
59181
+ log13.info("default-emit:pattern", { patternType, level: target.level });
56628
59182
  return;
56629
59183
  }
56630
59184
  if (payload.kind === "behavior") {
56631
59185
  const behaviorName = payload.data.name;
56632
59186
  if (typeof behaviorName !== "string") {
56633
- log11.warn("default-emit:behavior:missing-name");
59187
+ log13.warn("default-emit:behavior:missing-name");
56634
59188
  return;
56635
59189
  }
56636
59190
  eventBus.emit("UI:BEHAVIOR_DROP", {
56637
59191
  behaviorName,
56638
59192
  containerNode: target.containerNode
56639
59193
  });
56640
- log11.info("default-emit:behavior", { behaviorName, level: target.level });
59194
+ log13.info("default-emit:behavior", { behaviorName, level: target.level });
56641
59195
  return;
56642
59196
  }
56643
- log11.debug("default-emit:unhandled-kind", { kind: payload.kind });
59197
+ log13.debug("default-emit:unhandled-kind", { kind: payload.kind });
56644
59198
  }
56645
59199
  function CanvasDndProvider({
56646
59200
  children,
@@ -56656,9 +59210,9 @@ function CanvasDndProvider({
56656
59210
  if (payload) {
56657
59211
  setActivePayload(payload);
56658
59212
  eventBus.emit("UI:DRAG_START", { kind: payload.kind, data: payload.data });
56659
- log11.info("dragStart", { id: e.active.id, kind: payload.kind });
59213
+ log13.info("dragStart", { id: e.active.id, kind: payload.kind });
56660
59214
  } else {
56661
- log11.warn("dragStart:missing-payload", { id: e.active.id });
59215
+ log13.warn("dragStart:missing-payload", { id: e.active.id });
56662
59216
  }
56663
59217
  }, [eventBus]);
56664
59218
  const handleDragEnd = React91__default.useCallback((e) => {
@@ -56668,7 +59222,7 @@ function CanvasDndProvider({
56668
59222
  const overData = e.over?.data.current;
56669
59223
  const target = overData?.target;
56670
59224
  const accepts = overData?.accepts;
56671
- log11.info("dragEnd", {
59225
+ log13.info("dragEnd", {
56672
59226
  activeId: e.active.id,
56673
59227
  overId: e.over?.id,
56674
59228
  hasPayload: !!payload,
@@ -56680,7 +59234,7 @@ function CanvasDndProvider({
56680
59234
  }
56681
59235
  if (!payload || !target) return;
56682
59236
  if (accepts && !accepts.includes(payload.kind)) {
56683
- log11.debug("dragEnd:rejected:kind", { kind: payload.kind, accepts: [...accepts] });
59237
+ log13.debug("dragEnd:rejected:kind", { kind: payload.kind, accepts: [...accepts] });
56684
59238
  return;
56685
59239
  }
56686
59240
  const activator = e.activatorEvent;
@@ -56692,7 +59246,7 @@ function CanvasDndProvider({
56692
59246
  }, [eventBus, onDrop]);
56693
59247
  const handleDragCancel = React91__default.useCallback(() => {
56694
59248
  setActivePayload(null);
56695
- log11.info("dragCancel");
59249
+ log13.info("dragCancel");
56696
59250
  }, []);
56697
59251
  return /* @__PURE__ */ jsxs(
56698
59252
  DndContext,
@@ -58507,7 +61061,7 @@ init_AvlTransitionLane();
58507
61061
  init_AvlSwimLane();
58508
61062
  init_types();
58509
61063
  init_avl_elk_layout();
58510
- var log12 = createLogger("almadar:ui:avl:trait-scene");
61064
+ var log14 = createLogger("almadar:ui:avl:trait-scene");
58511
61065
  var SWIM_GUTTER2 = 120;
58512
61066
  var CENTER_W2 = 360;
58513
61067
  var AvlTraitScene = ({
@@ -58520,7 +61074,7 @@ var AvlTraitScene = ({
58520
61074
  const dataKey = useMemo(() => JSON.stringify(data), [data]);
58521
61075
  useEffect(() => {
58522
61076
  computeTraitLayout(data).then(setLayout).catch((error) => {
58523
- log12.error("computeTraitLayout failed", { error: error instanceof Error ? error : String(error) });
61077
+ log14.error("computeTraitLayout failed", { error: error instanceof Error ? error : String(error) });
58524
61078
  });
58525
61079
  }, [dataKey]);
58526
61080
  if (!layout) {