@almadar/ui 5.47.0 → 5.49.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/avl/index.cjs +2998 -443
  2. package/dist/avl/index.css +504 -0
  3. package/dist/avl/index.js +2998 -444
  4. package/dist/components/core/templates/index.d.ts +3 -0
  5. package/dist/components/game/atoms/ActionButton.d.ts +4 -1
  6. package/dist/components/game/atoms/ComboCounter.d.ts +4 -1
  7. package/dist/components/game/atoms/ControlButton.d.ts +4 -2
  8. package/dist/components/game/atoms/DamageNumber.d.ts +4 -1
  9. package/dist/components/game/atoms/ResourceCounter.d.ts +4 -1
  10. package/dist/components/game/atoms/ScoreDisplay.d.ts +4 -1
  11. package/dist/components/game/atoms/StateIndicator.d.ts +4 -7
  12. package/dist/components/game/atoms/StatusEffect.d.ts +4 -1
  13. package/dist/components/game/atoms/TurnIndicator.d.ts +4 -1
  14. package/dist/components/game/atoms/WaypointMarker.d.ts +4 -1
  15. package/dist/components/game/molecules/EnemyPlate.d.ts +4 -1
  16. package/dist/components/game/molecules/StatBadge.d.ts +4 -1
  17. package/dist/components/game/molecules/three/index.cjs +1428 -346
  18. package/dist/components/game/molecules/three/index.css +0 -5
  19. package/dist/components/game/molecules/three/index.js +1429 -347
  20. package/dist/components/game/organisms/CastleBoard.d.ts +10 -1
  21. package/dist/components/game/organisms/GameBoard3D.d.ts +62 -0
  22. package/dist/components/game/organisms/PlatformerBoard.d.ts +51 -0
  23. package/dist/components/game/organisms/RoguelikeBoard.d.ts +59 -0
  24. package/dist/components/game/organisms/TowerDefenseBoard.d.ts +64 -0
  25. package/dist/components/game/organisms/index.d.ts +4 -0
  26. package/dist/components/game/organisms/puzzles/event-handler/EventHandlerBoard.d.ts +9 -1
  27. package/dist/components/game/organisms/puzzles/state-architect/StateArchitectBoard.d.ts +14 -1
  28. package/dist/components/game/templates/GameCanvas3DBattleTemplate.d.ts +22 -35
  29. package/dist/components/game/templates/GameCanvas3DCastleTemplate.d.ts +23 -23
  30. package/dist/components/game/templates/GameCanvas3DWorldMapTemplate.d.ts +27 -29
  31. package/dist/components/game/templates/PlatformerTemplate.d.ts +26 -0
  32. package/dist/components/game/templates/RoguelikeTemplate.d.ts +23 -0
  33. package/dist/components/game/templates/TowerDefenseTemplate.d.ts +43 -0
  34. package/dist/components/index.cjs +2991 -422
  35. package/dist/components/index.css +504 -0
  36. package/dist/components/index.js +2985 -424
  37. package/dist/docs/index.css +504 -0
  38. package/dist/providers/index.cjs +2981 -426
  39. package/dist/providers/index.css +504 -0
  40. package/dist/providers/index.js +2981 -427
  41. package/dist/runtime/index.cjs +2982 -427
  42. package/dist/runtime/index.css +504 -0
  43. package/dist/runtime/index.js +2982 -428
  44. package/package.json +1 -1
  45. package/tailwind-preset.cjs +1 -0
@@ -1,6 +1,6 @@
1
1
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
2
2
  import * as React77 from 'react';
3
- import React77__default, { useContext, useMemo, useRef, useEffect, useCallback, useState, Suspense, lazy, createContext, useLayoutEffect, useId, useSyncExternalStore } from 'react';
3
+ import React77__default, { useContext, useMemo, useRef, useEffect, useCallback, useState, Suspense, lazy, createContext, useLayoutEffect, useId, forwardRef, useImperativeHandle, useSyncExternalStore, Component } from 'react';
4
4
  import { clsx } from 'clsx';
5
5
  import { twMerge } from 'tailwind-merge';
6
6
  import { EventBusContext, useTraitScope, TraitScopeProvider } from '@almadar/ui/providers';
@@ -41,6 +41,12 @@ import { DndContext, useSensors, useSensor, PointerSensor, KeyboardSensor, useDr
41
41
  import { useSortable, arrayMove, sortableKeyboardCoordinates, SortableContext, rectSortingStrategy, verticalListSortingStrategy } from '@dnd-kit/sortable';
42
42
  import { CSS } from '@dnd-kit/utilities';
43
43
  import { useNodeId, ReactFlowProvider, Handle, Position } from '@xyflow/react';
44
+ import * as THREE from 'three';
45
+ import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
46
+ import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
47
+ import { GLTFLoader as GLTFLoader$1 } from 'three/examples/jsm/loaders/GLTFLoader';
48
+ import { Canvas, useThree } from '@react-three/fiber';
49
+ import { Grid as Grid$1, OrbitControls } from '@react-three/drei';
44
50
  import { useUISlots } from '@almadar/ui/context';
45
51
  import { getPatternDefinition as getPatternDefinition$1, getComponentForPattern as getComponentForPattern$1 } from '@almadar/patterns';
46
52
 
@@ -1276,9 +1282,9 @@ function resolvePhosphor(name, weight, family) {
1276
1282
  (lib) => {
1277
1283
  const PhosphorComp = lib[target];
1278
1284
  if (!PhosphorComp || typeof PhosphorComp !== "object") return null;
1279
- const Component = PhosphorComp;
1285
+ const Component2 = PhosphorComp;
1280
1286
  const Adapter = (props) => /* @__PURE__ */ jsx(
1281
- Component,
1287
+ Component2,
1282
1288
  {
1283
1289
  weight,
1284
1290
  className: props.className,
@@ -1302,9 +1308,9 @@ function resolveTabler(name, family) {
1302
1308
  (lib) => {
1303
1309
  const TablerComp = lib[target];
1304
1310
  if (!TablerComp || typeof TablerComp !== "object") return null;
1305
- const Component = TablerComp;
1311
+ const Component2 = TablerComp;
1306
1312
  const Adapter = (props) => /* @__PURE__ */ jsx(
1307
- Component,
1313
+ Component2,
1308
1314
  {
1309
1315
  stroke: props.strokeWidth ?? 1.5,
1310
1316
  className: props.className,
@@ -1328,9 +1334,9 @@ function resolveFa(name, family) {
1328
1334
  (lib) => {
1329
1335
  const FaComp = lib[target];
1330
1336
  if (!FaComp || typeof FaComp !== "function") return null;
1331
- const Component = FaComp;
1337
+ const Component2 = FaComp;
1332
1338
  const Adapter = (props) => /* @__PURE__ */ jsx(
1333
- Component,
1339
+ Component2,
1334
1340
  {
1335
1341
  className: props.className,
1336
1342
  style: props.style,
@@ -3323,7 +3329,7 @@ var init_Box = __esm({
3323
3329
  position,
3324
3330
  className,
3325
3331
  children,
3326
- as: Component = "div",
3332
+ as: Component2 = "div",
3327
3333
  action,
3328
3334
  actionPayload,
3329
3335
  hoverEvent,
@@ -3355,7 +3361,7 @@ var init_Box = __esm({
3355
3361
  }, [hoverEvent, eventBus, onMouseLeave]);
3356
3362
  const isClickable = action || onClick;
3357
3363
  return React77__default.createElement(
3358
- Component,
3364
+ Component2,
3359
3365
  {
3360
3366
  ref,
3361
3367
  className: cn(
@@ -3404,10 +3410,10 @@ var init_Center = __esm({
3404
3410
  className,
3405
3411
  style,
3406
3412
  children,
3407
- as: Component = "div"
3413
+ as: Component2 = "div"
3408
3414
  }) => {
3409
3415
  const mergedStyle = minHeight ? { minHeight, ...style } : style;
3410
- const Comp = Component;
3416
+ const Comp = Component2;
3411
3417
  return /* @__PURE__ */ jsx(
3412
3418
  Comp,
3413
3419
  {
@@ -4038,7 +4044,7 @@ var init_Stack = __esm({
4038
4044
  className,
4039
4045
  style,
4040
4046
  children,
4041
- as: Component = "div",
4047
+ as: Component2 = "div",
4042
4048
  onClick,
4043
4049
  onKeyDown,
4044
4050
  role,
@@ -4056,7 +4062,7 @@ var init_Stack = __esm({
4056
4062
  };
4057
4063
  const isHorizontal = direction === "horizontal";
4058
4064
  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";
4059
- const Comp = Component;
4065
+ const Comp = Component2;
4060
4066
  return /* @__PURE__ */ jsx(
4061
4067
  Comp,
4062
4068
  {
@@ -4247,8 +4253,8 @@ var init_Typography = __esm({
4247
4253
  children
4248
4254
  }) => {
4249
4255
  const variant = variantProp ?? (level ? `h${level}` : "body1");
4250
- const Component = as || defaultElements[variant];
4251
- const Comp = Component;
4256
+ const Component2 = as || defaultElements[variant];
4257
+ const Comp = Component2;
4252
4258
  return /* @__PURE__ */ jsx(
4253
4259
  Comp,
4254
4260
  {
@@ -7461,10 +7467,10 @@ var init_Container = __esm({
7461
7467
  center = true,
7462
7468
  className,
7463
7469
  children,
7464
- as: Component = "div"
7470
+ as: Component2 = "div"
7465
7471
  }) => {
7466
7472
  const resolvedSize = maxWidth ?? size ?? "lg";
7467
- const Comp = Component;
7473
+ const Comp = Component2;
7468
7474
  return /* @__PURE__ */ jsx(
7469
7475
  Comp,
7470
7476
  {
@@ -8179,6 +8185,7 @@ var init_Accordion = __esm({
8179
8185
  }
8180
8186
  });
8181
8187
  function ActionButton({
8188
+ assetUrl = DEFAULT_ASSET_URL,
8182
8189
  label = "Attack",
8183
8190
  icon,
8184
8191
  cooldown = 0,
@@ -8218,13 +8225,23 @@ function ActionButton({
8218
8225
  }
8219
8226
  }
8220
8227
  ),
8221
- icon && /* @__PURE__ */ jsx("span", { className: cn("flex-shrink-0", sizes.icon), children: typeof icon === "string" ? (() => {
8228
+ assetUrl ? /* @__PURE__ */ jsx(
8229
+ "img",
8230
+ {
8231
+ src: assetUrl,
8232
+ alt: "",
8233
+ width: 16,
8234
+ height: 16,
8235
+ style: { imageRendering: "pixelated", objectFit: "contain" },
8236
+ className: cn("flex-shrink-0", sizes.icon)
8237
+ }
8238
+ ) : icon ? /* @__PURE__ */ jsx("span", { className: cn("flex-shrink-0", sizes.icon), children: typeof icon === "string" ? (() => {
8222
8239
  const I = resolveIcon(icon);
8223
8240
  return I ? /* @__PURE__ */ jsx(I, { className: "w-4 h-4" }) : null;
8224
8241
  })() : /* @__PURE__ */ (() => {
8225
8242
  const I = icon;
8226
8243
  return /* @__PURE__ */ jsx(I, { className: "w-4 h-4" });
8227
- })() }),
8244
+ })() }) : null,
8228
8245
  /* @__PURE__ */ jsx("span", { className: "relative z-10", children: label }),
8229
8246
  hotkey && /* @__PURE__ */ jsx(
8230
8247
  "span",
@@ -8240,7 +8257,7 @@ function ActionButton({
8240
8257
  }
8241
8258
  );
8242
8259
  }
8243
- var sizeMap, variantStyles7;
8260
+ var sizeMap, variantStyles7, DEFAULT_ASSET_URL;
8244
8261
  var init_ActionButton = __esm({
8245
8262
  "components/game/atoms/ActionButton.tsx"() {
8246
8263
  init_cn();
@@ -8255,10 +8272,12 @@ var init_ActionButton = __esm({
8255
8272
  secondary: "bg-secondary text-secondary-foreground hover:bg-secondary-hover border-border",
8256
8273
  danger: "bg-error text-error-foreground hover:bg-error/90 border-error"
8257
8274
  };
8275
+ DEFAULT_ASSET_URL = "https://almadar-kflow-assets.web.app/shared/effects/particles/slash_01.png";
8258
8276
  ActionButton.displayName = "ActionButton";
8259
8277
  }
8260
8278
  });
8261
8279
  function ControlButton({
8280
+ assetUrl = DEFAULT_ASSET_URL2,
8262
8281
  label,
8263
8282
  icon,
8264
8283
  size = "md",
@@ -8305,7 +8324,7 @@ function ControlButton({
8305
8324
  },
8306
8325
  [isPressed, releaseEvent, eventBus, onRelease]
8307
8326
  );
8308
- return /* @__PURE__ */ jsxs(
8327
+ return /* @__PURE__ */ jsx(
8309
8328
  "button",
8310
8329
  {
8311
8330
  type: "button",
@@ -8326,20 +8345,27 @@ function ControlButton({
8326
8345
  disabled && "opacity-50 cursor-not-allowed",
8327
8346
  className
8328
8347
  ),
8329
- children: [
8330
- icon && /* @__PURE__ */ jsx("span", { className: "text-2xl", children: typeof icon === "string" ? /^[a-zA-Z0-9-]+$/.test(icon) ? (() => {
8331
- const I = resolveIcon(icon);
8332
- return I ? /* @__PURE__ */ jsx(I, { className: "w-6 h-6" }) : null;
8333
- })() : icon : /* @__PURE__ */ (() => {
8334
- const I = icon;
8335
- return /* @__PURE__ */ jsx(I, { className: "w-6 h-6" });
8336
- })() }),
8337
- label && !icon && /* @__PURE__ */ jsx("span", { children: label })
8338
- ]
8348
+ children: assetUrl ? /* @__PURE__ */ jsx(
8349
+ "img",
8350
+ {
8351
+ src: assetUrl,
8352
+ alt: "",
8353
+ width: 24,
8354
+ height: 24,
8355
+ style: { imageRendering: "pixelated", objectFit: "contain" },
8356
+ className: "flex-shrink-0"
8357
+ }
8358
+ ) : icon ? /* @__PURE__ */ jsx("span", { className: "text-2xl", children: typeof icon === "string" ? /^[a-zA-Z0-9-]+$/.test(icon) ? (() => {
8359
+ const I = resolveIcon(icon);
8360
+ return I ? /* @__PURE__ */ jsx(I, { className: "w-6 h-6" }) : null;
8361
+ })() : icon : /* @__PURE__ */ (() => {
8362
+ const I = icon;
8363
+ return /* @__PURE__ */ jsx(I, { className: "w-6 h-6" });
8364
+ })() }) : label ? /* @__PURE__ */ jsx("span", { children: label }) : null
8339
8365
  }
8340
8366
  );
8341
8367
  }
8342
- var sizeMap2, shapeMap, variantMap;
8368
+ var sizeMap2, shapeMap, variantMap, DEFAULT_ASSET_URL2;
8343
8369
  var init_ControlButton = __esm({
8344
8370
  "components/game/atoms/ControlButton.tsx"() {
8345
8371
  "use client";
@@ -8362,6 +8388,7 @@ var init_ControlButton = __esm({
8362
8388
  secondary: "bg-secondary text-secondary-foreground border-border hover:bg-secondary-hover",
8363
8389
  ghost: "bg-transparent text-foreground border-border hover:bg-muted"
8364
8390
  };
8391
+ DEFAULT_ASSET_URL2 = "https://almadar-kflow-assets.web.app/shared/effects/particles/circle_01.png";
8365
8392
  ControlButton.displayName = "ControlButton";
8366
8393
  }
8367
8394
  });
@@ -10384,15 +10411,15 @@ function BattleBoard({
10384
10411
  const board = boardEntity(entity) ?? {};
10385
10412
  const tiles = propTiles ?? (Array.isArray(board.tiles) ? board.tiles : []);
10386
10413
  const features = propFeatures ?? (Array.isArray(board.features) ? board.features : []);
10387
- const boardWidth = num(board.boardWidth, 8);
10388
- const boardHeight = num(board.boardHeight, 6);
10414
+ const boardWidth = num(board.gridWidth ?? board.boardWidth, 8);
10415
+ const boardHeight = num(board.gridHeight ?? board.boardHeight, 6);
10389
10416
  const assetManifest = propAssetManifest ?? board.assetManifest;
10390
10417
  const backgroundImage = board.backgroundImage;
10391
10418
  const units = rows(board.units);
10392
10419
  const selectedUnitId = board.selectedUnitId ?? null;
10393
10420
  const currentPhase = str(board.phase) || "observation";
10394
10421
  const currentTurn = num(board.turn, 1);
10395
- const gameResult = board.gameResult ?? null;
10422
+ const gameResult = board.result ?? null;
10396
10423
  const eventBus = useEventBus();
10397
10424
  const { t } = useTranslate();
10398
10425
  const [hoveredTile, setHoveredTile] = useState(null);
@@ -10413,7 +10440,7 @@ function BattleBoard({
10413
10440
  const validMoves = useMemo(() => {
10414
10441
  if (!selectedUnit || currentPhase !== "movement") return [];
10415
10442
  const moves = [];
10416
- const range = num(selectedUnit.movement);
10443
+ const range = num(board.movementRange, 2);
10417
10444
  const origin = unitPosition(selectedUnit);
10418
10445
  for (let dy = -range; dy <= range; dy++) {
10419
10446
  for (let dx = -range; dx <= range; dx++) {
@@ -18698,15 +18725,24 @@ function CastleBoard({
18698
18725
  featureClickEvent,
18699
18726
  unitClickEvent,
18700
18727
  tileClickEvent,
18728
+ playAgainEvent,
18701
18729
  className
18702
18730
  }) {
18703
18731
  const eventBus = useEventBus();
18732
+ const { t } = useTranslate();
18704
18733
  const resolved = boardEntity(entity);
18705
18734
  const tiles = propTiles ?? (Array.isArray(resolved?.tiles) ? resolved.tiles : []);
18706
18735
  const features = propFeatures ?? (Array.isArray(resolved?.features) ? resolved.features : []);
18707
18736
  const units = propUnits ?? (Array.isArray(resolved?.units) ? resolved.units : []);
18708
18737
  const assetManifest = propAssetManifest ?? resolved?.assetManifest;
18709
18738
  const backgroundImage = resolved?.backgroundImage;
18739
+ const gold = num(resolved?.gold);
18740
+ const health = num(resolved?.health);
18741
+ const maxHealth = num(resolved?.maxHealth);
18742
+ const wave = num(resolved?.wave);
18743
+ const tickCount = num(resolved?.tickCount);
18744
+ const buildings = rows(resolved?.buildings);
18745
+ const result = str(resolved?.result) || "none";
18710
18746
  const [hoveredTile, setHoveredTile] = useState(null);
18711
18747
  const [selectedFeature, setSelectedFeature] = useState(null);
18712
18748
  const hoveredFeature = useMemo(() => {
@@ -18719,7 +18755,7 @@ function CastleBoard({
18719
18755
  (u) => u.position?.x === hoveredTile.x && u.position?.y === hoveredTile.y
18720
18756
  ) ?? null;
18721
18757
  }, [hoveredTile, units]);
18722
- const maxY = Math.max(...tiles.map((t) => t.y), 0);
18758
+ const maxY = Math.max(...tiles.map((t2) => t2.y), 0);
18723
18759
  const baseOffsetX = (maxY + 1) * (TILE_WIDTH * scale / 2);
18724
18760
  const tileToScreen = useCallback(
18725
18761
  (tx, ty) => isoToScreen(tx, ty, scale, baseOffsetX),
@@ -18754,6 +18790,11 @@ function CastleBoard({
18754
18790
  }
18755
18791
  }, [units, onUnitClick, unitClickEvent, eventBus]);
18756
18792
  const clearSelection = useCallback(() => setSelectedFeature(null), []);
18793
+ const handlePlayAgain = useCallback(() => {
18794
+ if (playAgainEvent) {
18795
+ eventBus.emit(`UI:${playAgainEvent}`, {});
18796
+ }
18797
+ }, [playAgainEvent, eventBus]);
18757
18798
  const ctx = useMemo(
18758
18799
  () => ({
18759
18800
  hoveredTile,
@@ -18762,11 +18803,18 @@ function CastleBoard({
18762
18803
  selectedFeature,
18763
18804
  clearSelection,
18764
18805
  tileToScreen,
18765
- scale
18806
+ scale,
18807
+ gold,
18808
+ health,
18809
+ maxHealth,
18810
+ wave,
18811
+ tickCount,
18812
+ buildings,
18813
+ result
18766
18814
  }),
18767
- [hoveredTile, hoveredFeature, hoveredUnit, selectedFeature, clearSelection, tileToScreen, scale]
18815
+ [hoveredTile, hoveredFeature, hoveredUnit, selectedFeature, clearSelection, tileToScreen, scale, gold, health, maxHealth, wave, tickCount, buildings, result]
18768
18816
  );
18769
- return /* @__PURE__ */ jsxs("div", { className: cn("castle-board min-h-screen flex flex-col bg-background", className), children: [
18817
+ return /* @__PURE__ */ jsxs("div", { className: cn("castle-board relative min-h-screen flex flex-col bg-background", className), children: [
18770
18818
  header && header(ctx),
18771
18819
  /* @__PURE__ */ jsxs("div", { className: "flex flex-1 overflow-hidden", children: [
18772
18820
  /* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-auto p-4 relative", children: [
@@ -18791,7 +18839,29 @@ function CastleBoard({
18791
18839
  ] }),
18792
18840
  sidePanel && /* @__PURE__ */ jsx("div", { className: "w-96 shrink-0 border-l border-border bg-surface overflow-y-auto", children: sidePanel(ctx) })
18793
18841
  ] }),
18794
- footer && footer(ctx)
18842
+ footer && footer(ctx),
18843
+ 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: [
18844
+ /* @__PURE__ */ jsx(
18845
+ Typography,
18846
+ {
18847
+ variant: "h2",
18848
+ className: cn(
18849
+ "text-4xl font-black tracking-widest uppercase",
18850
+ result === "victory" ? "text-warning" : "text-error"
18851
+ ),
18852
+ children: result === "victory" ? t("battle.victory") : t("battle.defeat")
18853
+ }
18854
+ ),
18855
+ /* @__PURE__ */ jsx(
18856
+ Button,
18857
+ {
18858
+ variant: "primary",
18859
+ className: "px-8 py-3 font-semibold",
18860
+ onClick: handlePlayAgain,
18861
+ children: t("battle.playAgain")
18862
+ }
18863
+ )
18864
+ ] }) })
18795
18865
  ] });
18796
18866
  }
18797
18867
  var init_CastleBoard = __esm({
@@ -18799,6 +18869,10 @@ var init_CastleBoard = __esm({
18799
18869
  "use client";
18800
18870
  init_cn();
18801
18871
  init_useEventBus();
18872
+ init_Box();
18873
+ init_Button();
18874
+ init_Typography();
18875
+ init_Stack();
18802
18876
  init_IsometricCanvas();
18803
18877
  init_boardEntity();
18804
18878
  init_isometric();
@@ -19721,22 +19795,16 @@ function ClassifierBoard({
19721
19795
  const { emit } = useEventBus();
19722
19796
  const { t } = useTranslate();
19723
19797
  const resolved = boardEntity(entity);
19724
- const [localAssignments, setLocalAssignments] = useState({});
19725
19798
  const [headerError, setHeaderError] = useState(false);
19726
- const [localSubmitted, setLocalSubmitted] = useState(false);
19727
- const [localAttempts, setLocalAttempts] = useState(0);
19728
- const [showHint, setShowHint] = useState(false);
19729
19799
  const items = Array.isArray(resolved?.items) ? resolved.items : [];
19730
19800
  const categories = Array.isArray(resolved?.categories) ? resolved.categories : [];
19731
- const entityResult = str(resolved?.result);
19732
- const entityDrivesResult = entityResult.length > 0;
19733
- const entityHasAssignments = items.some((item) => item.assignedCategory != null);
19734
- const assignments = entityHasAssignments ? items.reduce((acc, item) => {
19735
- if (item.assignedCategory != null) acc[item.id] = item.assignedCategory;
19801
+ const result = str(resolved?.result);
19802
+ const submitted = result === "win";
19803
+ const attempts = num(resolved?.attempts);
19804
+ const assignments = items.reduce((acc, item) => {
19805
+ if (item.assignedCategory != null && item.assignedCategory !== "") acc[item.id] = item.assignedCategory;
19736
19806
  return acc;
19737
- }, {}) : localAssignments;
19738
- const attempts = entityDrivesResult ? num(resolved?.attempts) : localAttempts;
19739
- const submitted = entityDrivesResult || localSubmitted;
19807
+ }, {});
19740
19808
  const unassignedItems = items.filter((item) => !assignments[item.id]);
19741
19809
  const allAssigned = items.length > 0 && Object.keys(assignments).length === items.length;
19742
19810
  const results = submitted ? items.map((item) => ({
@@ -19744,54 +19812,25 @@ function ClassifierBoard({
19744
19812
  assigned: assignments[item.id],
19745
19813
  correct: assignments[item.id] === item.correctCategory
19746
19814
  })) : [];
19747
- const allCorrect = entityDrivesResult ? entityResult === "success" : results.length > 0 && results.every((r) => r.correct);
19815
+ const allCorrect = result === "win";
19748
19816
  const correctCount = results.filter((r) => r.correct).length;
19817
+ const showHint = attempts >= 2 && Boolean(str(resolved?.hint));
19749
19818
  const handleAssign = (itemId, categoryId) => {
19750
19819
  if (submitted) return;
19751
- if (assignEvent) {
19752
- emit(`UI:${assignEvent}`, { itemId, categoryId });
19753
- }
19754
- if (!entityHasAssignments) {
19755
- setLocalAssignments((prev) => ({ ...prev, [itemId]: categoryId }));
19756
- }
19820
+ if (assignEvent) emit(`UI:${assignEvent}`, { itemId, categoryId });
19757
19821
  };
19758
19822
  const handleUnassign = (itemId) => {
19759
19823
  if (submitted) return;
19760
- if (!entityHasAssignments) {
19761
- setLocalAssignments((prev) => {
19762
- const next = { ...prev };
19763
- delete next[itemId];
19764
- return next;
19765
- });
19766
- }
19824
+ if (assignEvent) emit(`UI:${assignEvent}`, { itemId, categoryId: "" });
19767
19825
  };
19768
- const handleSubmit = useCallback(() => {
19769
- if (checkEvent) {
19770
- emit(`UI:${checkEvent}`, {});
19771
- }
19772
- if (!entityDrivesResult) {
19773
- setLocalSubmitted(true);
19774
- setLocalAttempts((a) => a + 1);
19775
- const correct = items.every((item) => assignments[item.id] === item.correctCategory);
19776
- if (correct) {
19777
- emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
19778
- }
19779
- }
19780
- }, [checkEvent, entityDrivesResult, items, assignments, attempts, completeEvent, emit]);
19781
- const handleReset = () => {
19782
- if (!entityDrivesResult) setLocalSubmitted(false);
19783
- if (attempts >= 2 && str(resolved?.hint)) {
19784
- setShowHint(true);
19826
+ const handleSubmit = () => {
19827
+ if (checkEvent) emit(`UI:${checkEvent}`, {});
19828
+ if (allAssigned && items.every((item) => assignments[item.id] === item.correctCategory)) {
19829
+ emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
19785
19830
  }
19786
19831
  };
19787
19832
  const handleFullReset = () => {
19788
- if (playAgainEvent) {
19789
- emit(`UI:${playAgainEvent}`, {});
19790
- }
19791
- setLocalAssignments({});
19792
- setLocalSubmitted(false);
19793
- setLocalAttempts(0);
19794
- setShowHint(false);
19833
+ if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
19795
19834
  };
19796
19835
  if (!resolved) return null;
19797
19836
  const theme = resolved.theme ?? void 0;
@@ -19830,17 +19869,17 @@ function ClassifierBoard({
19830
19869
  /* @__PURE__ */ jsx(Badge, { size: "sm", children: catItems.length })
19831
19870
  ] }),
19832
19871
  /* @__PURE__ */ jsx(HStack, { gap: "xs", className: "flex-wrap min-h-[2rem]", children: catItems.map((item) => {
19833
- const result = results.find((r) => r.item.id === item.id);
19872
+ const result2 = results.find((r) => r.item.id === item.id);
19834
19873
  return /* @__PURE__ */ jsxs(
19835
19874
  Badge,
19836
19875
  {
19837
19876
  size: "sm",
19838
- className: `cursor-pointer ${result ? result.correct ? "border-success bg-success/10" : "border-error bg-error/10" : ""}`,
19877
+ className: `cursor-pointer ${result2 ? result2.correct ? "border-success bg-success/10" : "border-error bg-error/10" : ""}`,
19839
19878
  onClick: () => handleUnassign(item.id),
19840
19879
  children: [
19841
19880
  item.iconUrl && /* @__PURE__ */ jsx("img", { src: item.iconUrl, alt: "", className: "w-3 h-3 object-contain inline-block" }),
19842
19881
  item.label,
19843
- result && /* @__PURE__ */ jsx(Icon, { icon: result.correct ? CheckCircle : XCircle, size: "xs", className: result.correct ? "text-success" : "text-error" })
19882
+ result2 && /* @__PURE__ */ jsx(Icon, { icon: result2.correct ? CheckCircle : XCircle, size: "xs", className: result2.correct ? "text-success" : "text-error" })
19844
19883
  ]
19845
19884
  },
19846
19885
  item.id
@@ -19869,10 +19908,10 @@ function ClassifierBoard({
19869
19908
  ] }) }),
19870
19909
  showHint && hint && /* @__PURE__ */ jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsx(Typography, { variant: "body", children: hint }) }),
19871
19910
  /* @__PURE__ */ jsxs(HStack, { gap: "sm", justify: "center", children: [
19872
- !submitted ? /* @__PURE__ */ jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: !allAssigned, children: [
19911
+ !submitted && /* @__PURE__ */ jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: !allAssigned, children: [
19873
19912
  /* @__PURE__ */ jsx(Icon, { icon: Send, size: "sm" }),
19874
19913
  t("classifier.check")
19875
- ] }) : !allCorrect ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleReset, children: t("classifier.tryAgain") }) : null,
19914
+ ] }),
19876
19915
  /* @__PURE__ */ jsxs(Button, { variant: "secondary", onClick: handleFullReset, children: [
19877
19916
  /* @__PURE__ */ jsx(Icon, { icon: RotateCcw, size: "sm" }),
19878
19917
  t("classifier.reset")
@@ -20159,6 +20198,7 @@ function getComboScale(combo) {
20159
20198
  return "";
20160
20199
  }
20161
20200
  function ComboCounter({
20201
+ assetUrl = DEFAULT_ASSET_URL3,
20162
20202
  combo = 5,
20163
20203
  multiplier,
20164
20204
  streak,
@@ -20178,6 +20218,17 @@ function ComboCounter({
20178
20218
  className
20179
20219
  ),
20180
20220
  children: [
20221
+ assetUrl && /* @__PURE__ */ jsx(
20222
+ "img",
20223
+ {
20224
+ src: assetUrl,
20225
+ alt: "combo",
20226
+ width: 24,
20227
+ height: 24,
20228
+ style: { imageRendering: "pixelated", objectFit: "contain" },
20229
+ className: "flex-shrink-0 mb-0.5"
20230
+ }
20231
+ ),
20181
20232
  /* @__PURE__ */ jsx("span", { className: cn("font-black tabular-nums leading-none", sizes.combo, getComboIntensity(combo)), children: combo }),
20182
20233
  /* @__PURE__ */ jsx("span", { className: cn("font-bold uppercase tracking-wider text-muted-foreground", sizes.label), children: "combo" }),
20183
20234
  multiplier != null && multiplier > 1 && /* @__PURE__ */ jsxs("span", { className: cn("font-bold text-warning tabular-nums", sizes.multiplier), children: [
@@ -20192,10 +20243,11 @@ function ComboCounter({
20192
20243
  }
20193
20244
  );
20194
20245
  }
20195
- var sizeMap4;
20246
+ var DEFAULT_ASSET_URL3, sizeMap4;
20196
20247
  var init_ComboCounter = __esm({
20197
20248
  "components/game/atoms/ComboCounter.tsx"() {
20198
20249
  init_cn();
20250
+ DEFAULT_ASSET_URL3 = "https://almadar-kflow-assets.web.app/shared/effects/flash/flash00.png";
20199
20251
  sizeMap4 = {
20200
20252
  sm: { combo: "text-lg", label: "text-xs", multiplier: "text-xs" },
20201
20253
  md: { combo: "text-2xl", label: "text-xs", multiplier: "text-sm" },
@@ -20604,7 +20656,7 @@ var init_CounterTemplate = __esm({
20604
20656
  }
20605
20657
  });
20606
20658
  function ItemSlot({
20607
- assetUrl = DEFAULT_ASSET_URL,
20659
+ assetUrl = DEFAULT_ASSET_URL4,
20608
20660
  icon = "sword",
20609
20661
  label = "Iron Sword",
20610
20662
  quantity,
@@ -20663,7 +20715,7 @@ function ItemSlot({
20663
20715
  }
20664
20716
  );
20665
20717
  }
20666
- var sizeMap5, rarityBorderMap, rarityGlowMap, DEFAULT_ASSET_URL, assetSizeMap;
20718
+ var sizeMap5, rarityBorderMap, rarityGlowMap, DEFAULT_ASSET_URL4, assetSizeMap;
20667
20719
  var init_ItemSlot = __esm({
20668
20720
  "components/game/atoms/ItemSlot.tsx"() {
20669
20721
  "use client";
@@ -20688,7 +20740,7 @@ var init_ItemSlot = __esm({
20688
20740
  epic: "shadow-lg",
20689
20741
  legendary: "shadow-lg"
20690
20742
  };
20691
- DEFAULT_ASSET_URL = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
20743
+ DEFAULT_ASSET_URL4 = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
20692
20744
  assetSizeMap = {
20693
20745
  sm: 28,
20694
20746
  md: 40,
@@ -20867,6 +20919,7 @@ var init_DPad = __esm({
20867
20919
  }
20868
20920
  });
20869
20921
  function DamageNumber({
20922
+ assetUrl = DEFAULT_ASSET_URL5,
20870
20923
  value = 42,
20871
20924
  type = "damage",
20872
20925
  size = "md",
@@ -20875,22 +20928,35 @@ function DamageNumber({
20875
20928
  const displayText = type === "miss" ? "MISS" : type === "heal" ? `+${value}` : `${value}`;
20876
20929
  return /* @__PURE__ */ jsxs(Fragment, { children: [
20877
20930
  /* @__PURE__ */ jsx("style", { children: floatKeyframes }),
20878
- /* @__PURE__ */ jsx(
20931
+ /* @__PURE__ */ jsxs(
20879
20932
  "span",
20880
20933
  {
20881
20934
  className: cn(
20882
- "inline-block font-mono select-none pointer-events-none",
20935
+ "inline-flex items-center gap-0.5 font-mono select-none pointer-events-none",
20883
20936
  sizeMap7[size],
20884
20937
  typeStyles[type],
20885
20938
  className
20886
20939
  ),
20887
20940
  style: { animation: "damageFloat 1s ease-out forwards" },
20888
- children: displayText
20941
+ children: [
20942
+ assetUrl && /* @__PURE__ */ jsx(
20943
+ "img",
20944
+ {
20945
+ src: assetUrl,
20946
+ alt: "",
20947
+ width: 14,
20948
+ height: 14,
20949
+ style: { imageRendering: "pixelated", objectFit: "contain" },
20950
+ className: "flex-shrink-0"
20951
+ }
20952
+ ),
20953
+ displayText
20954
+ ]
20889
20955
  }
20890
20956
  )
20891
20957
  ] });
20892
20958
  }
20893
- var sizeMap7, typeStyles, floatKeyframes;
20959
+ var sizeMap7, typeStyles, floatKeyframes, DEFAULT_ASSET_URL5;
20894
20960
  var init_DamageNumber = __esm({
20895
20961
  "components/game/atoms/DamageNumber.tsx"() {
20896
20962
  init_cn();
@@ -20912,6 +20978,7 @@ var init_DamageNumber = __esm({
20912
20978
  100% { opacity: 0; transform: translateY(-32px) scale(0.8); }
20913
20979
  }
20914
20980
  `;
20981
+ DEFAULT_ASSET_URL5 = "https://almadar-kflow-assets.web.app/shared/effects/particles/spark_01.png";
20915
20982
  DamageNumber.displayName = "DamageNumber";
20916
20983
  }
20917
20984
  });
@@ -24732,7 +24799,7 @@ var init_Flex = __esm({
24732
24799
  basis,
24733
24800
  className,
24734
24801
  children,
24735
- as: Component = "div"
24802
+ as: Component2 = "div"
24736
24803
  }) => {
24737
24804
  const flexStyle = {};
24738
24805
  if (grow !== void 0 || shrink !== void 0 || basis !== void 0) {
@@ -24744,7 +24811,7 @@ var init_Flex = __esm({
24744
24811
  flexStyle.flexBasis = typeof basis === "number" ? `${basis}px` : basis;
24745
24812
  }
24746
24813
  }
24747
- const Comp = Component;
24814
+ const Comp = Component2;
24748
24815
  return /* @__PURE__ */ jsx(
24749
24816
  Comp,
24750
24817
  {
@@ -24864,11 +24931,11 @@ var init_Grid = __esm({
24864
24931
  className,
24865
24932
  style,
24866
24933
  children,
24867
- as: Component = "div"
24934
+ as: Component2 = "div"
24868
24935
  }) => {
24869
24936
  const mergedStyle = rows2 ? { gridTemplateRows: `repeat(${rows2}, minmax(0, 1fr))`, ...style } : style;
24870
24937
  return React77__default.createElement(
24871
- Component,
24938
+ Component2,
24872
24939
  {
24873
24940
  className: cn(
24874
24941
  "grid",
@@ -27017,6 +27084,7 @@ var init_HealthBar = __esm({
27017
27084
  }
27018
27085
  });
27019
27086
  function ScoreDisplay({
27087
+ assetUrl = DEFAULT_ASSET_URL6,
27020
27088
  value,
27021
27089
  label,
27022
27090
  icon,
@@ -27063,14 +27131,24 @@ function ScoreDisplay({
27063
27131
  className
27064
27132
  ),
27065
27133
  children: [
27066
- icon && /* @__PURE__ */ jsx("span", { className: "flex-shrink-0", children: typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon }) : /* @__PURE__ */ jsx(Icon, { icon }) }),
27134
+ assetUrl ? /* @__PURE__ */ jsx(
27135
+ "img",
27136
+ {
27137
+ src: assetUrl,
27138
+ alt: "",
27139
+ width: 20,
27140
+ height: 20,
27141
+ style: { imageRendering: "pixelated", objectFit: "contain" },
27142
+ className: "flex-shrink-0"
27143
+ }
27144
+ ) : icon ? /* @__PURE__ */ jsx("span", { className: "flex-shrink-0", children: typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon }) : /* @__PURE__ */ jsx(Icon, { icon }) }) : null,
27067
27145
  label && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: label }),
27068
27146
  /* @__PURE__ */ jsx("span", { className: "tabular-nums", children: formattedValue })
27069
27147
  ]
27070
27148
  }
27071
27149
  );
27072
27150
  }
27073
- var sizeMap10;
27151
+ var sizeMap10, DEFAULT_ASSET_URL6;
27074
27152
  var init_ScoreDisplay = __esm({
27075
27153
  "components/game/atoms/ScoreDisplay.tsx"() {
27076
27154
  "use client";
@@ -27082,10 +27160,12 @@ var init_ScoreDisplay = __esm({
27082
27160
  lg: "text-2xl",
27083
27161
  xl: "text-4xl"
27084
27162
  };
27163
+ DEFAULT_ASSET_URL6 = "https://almadar-kflow-assets.web.app/shared/effects/particles/star_01.png";
27085
27164
  ScoreDisplay.displayName = "ScoreDisplay";
27086
27165
  }
27087
27166
  });
27088
27167
  function StatBadge({
27168
+ assetUrl = DEFAULT_ASSET_URL7,
27089
27169
  label,
27090
27170
  value = 0,
27091
27171
  max,
@@ -27109,7 +27189,17 @@ function StatBadge({
27109
27189
  className
27110
27190
  ),
27111
27191
  children: [
27112
- 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" }) }),
27192
+ assetUrl ? /* @__PURE__ */ jsx(
27193
+ "img",
27194
+ {
27195
+ src: assetUrl,
27196
+ alt: "",
27197
+ width: 16,
27198
+ height: 16,
27199
+ style: { imageRendering: "pixelated", objectFit: "contain" },
27200
+ className: "flex-shrink-0"
27201
+ }
27202
+ ) : 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,
27113
27203
  /* @__PURE__ */ jsx("span", { className: "text-muted-foreground font-medium", children: label }),
27114
27204
  format === "hearts" && max && /* @__PURE__ */ jsx(
27115
27205
  HealthBar,
@@ -27142,7 +27232,7 @@ function StatBadge({
27142
27232
  }
27143
27233
  );
27144
27234
  }
27145
- var sizeMap11, variantMap2;
27235
+ var sizeMap11, variantMap2, DEFAULT_ASSET_URL7;
27146
27236
  var init_StatBadge = __esm({
27147
27237
  "components/game/molecules/StatBadge.tsx"() {
27148
27238
  init_cn();
@@ -27161,6 +27251,7 @@ var init_StatBadge = __esm({
27161
27251
  warning: "bg-warning/15 border-warning/40 text-foreground",
27162
27252
  danger: "bg-error/15 border-error/40 text-foreground"
27163
27253
  };
27254
+ DEFAULT_ASSET_URL7 = "https://almadar-kflow-assets.web.app/shared/effects/particles/light_01.png";
27164
27255
  StatBadge.displayName = "StatBadge";
27165
27256
  }
27166
27257
  });
@@ -27244,6 +27335,7 @@ var init_InventoryGrid = __esm({
27244
27335
  }
27245
27336
  });
27246
27337
  function WaypointMarker({
27338
+ assetUrl = DEFAULT_ASSET_URL8,
27247
27339
  label,
27248
27340
  icon,
27249
27341
  active = true,
@@ -27282,7 +27374,17 @@ function WaypointMarker({
27282
27374
  active && !completed && "bg-info text-foreground",
27283
27375
  !active && !completed && "bg-muted"
27284
27376
  ),
27285
- children: completed ? checkIcon : icon && (typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon }) : /* @__PURE__ */ jsx(Icon, { icon }))
27377
+ children: completed ? checkIcon : assetUrl ? /* @__PURE__ */ jsx(
27378
+ "img",
27379
+ {
27380
+ src: assetUrl,
27381
+ alt: label,
27382
+ width: sizes.img,
27383
+ height: sizes.img,
27384
+ style: { imageRendering: "pixelated", objectFit: "contain" },
27385
+ className: "flex-shrink-0"
27386
+ }
27387
+ ) : icon ? typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon }) : /* @__PURE__ */ jsx(Icon, { icon }) : null
27286
27388
  }
27287
27389
  )
27288
27390
  ] }),
@@ -27299,15 +27401,16 @@ function WaypointMarker({
27299
27401
  )
27300
27402
  ] });
27301
27403
  }
27302
- var sizeMap12, checkIcon;
27404
+ var DEFAULT_ASSET_URL8, sizeMap12, checkIcon;
27303
27405
  var init_WaypointMarker = __esm({
27304
27406
  "components/game/atoms/WaypointMarker.tsx"() {
27305
27407
  init_cn();
27306
27408
  init_Icon();
27409
+ DEFAULT_ASSET_URL8 = "https://almadar-kflow-assets.web.app/shared/world-map/battle_marker.png";
27307
27410
  sizeMap12 = {
27308
- sm: { dot: "w-4 h-4", ring: "w-6 h-6", label: "text-xs mt-1" },
27309
- md: { dot: "w-6 h-6", ring: "w-8 h-8", label: "text-sm mt-1.5" },
27310
- lg: { dot: "w-8 h-8", ring: "w-10 h-10", label: "text-base mt-2" }
27411
+ sm: { dot: "w-4 h-4", ring: "w-6 h-6", label: "text-xs mt-1", img: 12 },
27412
+ md: { dot: "w-6 h-6", ring: "w-8 h-8", label: "text-sm mt-1.5", img: 18 },
27413
+ lg: { dot: "w-8 h-8", ring: "w-10 h-10", label: "text-base mt-2", img: 24 }
27311
27414
  };
27312
27415
  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" }) });
27313
27416
  WaypointMarker.displayName = "WaypointMarker";
@@ -27821,6 +27924,7 @@ var init_ResourceBar = __esm({
27821
27924
  }
27822
27925
  });
27823
27926
  function TurnIndicator({
27927
+ assetUrl = DEFAULT_ASSET_URL9,
27824
27928
  currentTurn = 1,
27825
27929
  maxTurns,
27826
27930
  activeTeam,
@@ -27853,14 +27957,24 @@ function TurnIndicator({
27853
27957
  ] }),
27854
27958
  activeTeam && /* @__PURE__ */ jsxs(Fragment, { children: [
27855
27959
  /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "|" }),
27856
- /* @__PURE__ */ jsx("span", { className: cn("rounded-full bg-success/20", sizes.dot) }),
27960
+ assetUrl ? /* @__PURE__ */ jsx(
27961
+ "img",
27962
+ {
27963
+ src: assetUrl,
27964
+ alt: "",
27965
+ width: 12,
27966
+ height: 12,
27967
+ style: { imageRendering: "pixelated", objectFit: "contain" },
27968
+ className: "flex-shrink-0"
27969
+ }
27970
+ ) : /* @__PURE__ */ jsx("span", { className: cn("rounded-full bg-success/20", sizes.dot) }),
27857
27971
  /* @__PURE__ */ jsx("span", { className: "text-success", children: activeTeam })
27858
27972
  ] })
27859
27973
  ]
27860
27974
  }
27861
27975
  );
27862
27976
  }
27863
- var sizeMap14;
27977
+ var sizeMap14, DEFAULT_ASSET_URL9;
27864
27978
  var init_TurnIndicator = __esm({
27865
27979
  "components/game/atoms/TurnIndicator.tsx"() {
27866
27980
  init_cn();
@@ -27869,6 +27983,7 @@ var init_TurnIndicator = __esm({
27869
27983
  md: { wrapper: "text-sm gap-2 px-3 py-1", dot: "w-2 h-2" },
27870
27984
  lg: { wrapper: "text-base gap-2.5 px-4 py-1.5", dot: "w-2.5 h-2.5" }
27871
27985
  };
27986
+ DEFAULT_ASSET_URL9 = "https://almadar-kflow-assets.web.app/shared/effects/particles/symbol_01.png";
27872
27987
  TurnIndicator.displayName = "TurnIndicator";
27873
27988
  }
27874
27989
  });
@@ -27939,6 +28054,7 @@ var init_TurnPanel = __esm({
27939
28054
  }
27940
28055
  });
27941
28056
  function EnemyPlate({
28057
+ assetUrl = DEFAULT_ASSET_URL10,
27942
28058
  name = "Shadow Guard",
27943
28059
  health = 80,
27944
28060
  maxHealth = 100,
@@ -27956,15 +28072,28 @@ function EnemyPlate({
27956
28072
  ),
27957
28073
  children: [
27958
28074
  /* @__PURE__ */ jsxs(Box, { className: "flex items-center justify-between gap-2", children: [
27959
- /* @__PURE__ */ jsx(
27960
- Typography,
27961
- {
27962
- variant: "caption",
27963
- weight: "bold",
27964
- className: "text-[var(--color-foreground)] truncate",
27965
- children: name
27966
- }
27967
- ),
28075
+ /* @__PURE__ */ jsxs(Box, { className: "flex items-center gap-1.5 min-w-0", children: [
28076
+ assetUrl && /* @__PURE__ */ jsx(
28077
+ "img",
28078
+ {
28079
+ src: assetUrl,
28080
+ alt: name,
28081
+ width: 24,
28082
+ height: 24,
28083
+ style: { objectFit: "cover", borderRadius: "50%" },
28084
+ className: "flex-shrink-0"
28085
+ }
28086
+ ),
28087
+ /* @__PURE__ */ jsx(
28088
+ Typography,
28089
+ {
28090
+ variant: "caption",
28091
+ weight: "bold",
28092
+ className: "text-[var(--color-foreground)] truncate",
28093
+ children: name
28094
+ }
28095
+ )
28096
+ ] }),
27968
28097
  level != null && /* @__PURE__ */ jsxs(Badge, { variant: "neutral", size: "sm", children: [
27969
28098
  "Lv.",
27970
28099
  level
@@ -28009,7 +28138,7 @@ function EnemyPlate({
28009
28138
  }
28010
28139
  );
28011
28140
  }
28012
- var effectVariantMap2, DEFAULT_ENEMY_EFFECTS;
28141
+ var effectVariantMap2, DEFAULT_ENEMY_EFFECTS, DEFAULT_ASSET_URL10;
28013
28142
  var init_EnemyPlate = __esm({
28014
28143
  "components/game/molecules/EnemyPlate.tsx"() {
28015
28144
  "use client";
@@ -28026,6 +28155,7 @@ var init_EnemyPlate = __esm({
28026
28155
  DEFAULT_ENEMY_EFFECTS = [
28027
28156
  { icon: "flame", label: "Burn", variant: "debuff" }
28028
28157
  ];
28158
+ DEFAULT_ASSET_URL10 = "https://almadar-kflow-assets.web.app/shared/portraits/shadow-legion.png";
28029
28159
  EnemyPlate.displayName = "EnemyPlate";
28030
28160
  }
28031
28161
  });
@@ -29579,13 +29709,13 @@ var init_MapView = __esm({
29579
29709
  shadowSize: [41, 41]
29580
29710
  });
29581
29711
  L.Marker.prototype.options.icon = defaultIcon;
29582
- const { useEffect: useEffect74, useRef: useRef71, useCallback: useCallback114, useState: useState105 } = React77__default;
29712
+ const { useEffect: useEffect80, useRef: useRef77, useCallback: useCallback120, useState: useState110 } = React77__default;
29583
29713
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
29584
- const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
29714
+ const { useEventBus: useEventBus3 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
29585
29715
  function MapUpdater({ centerLat, centerLng, zoom }) {
29586
29716
  const map = useMap();
29587
- const prevRef = useRef71({ centerLat, centerLng, zoom });
29588
- useEffect74(() => {
29717
+ const prevRef = useRef77({ centerLat, centerLng, zoom });
29718
+ useEffect80(() => {
29589
29719
  const prev = prevRef.current;
29590
29720
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
29591
29721
  map.setView([centerLat, centerLng], zoom);
@@ -29596,7 +29726,7 @@ var init_MapView = __esm({
29596
29726
  }
29597
29727
  function MapClickHandler({ onMapClick }) {
29598
29728
  const map = useMap();
29599
- useEffect74(() => {
29729
+ useEffect80(() => {
29600
29730
  if (!onMapClick) return;
29601
29731
  const handler = (e) => {
29602
29732
  onMapClick(e.latlng.lat, e.latlng.lng);
@@ -29623,9 +29753,9 @@ var init_MapView = __esm({
29623
29753
  className,
29624
29754
  showAttribution = true
29625
29755
  }) {
29626
- const eventBus = useEventBus2();
29627
- const [clickedPosition, setClickedPosition] = useState105(null);
29628
- const handleMapClick = useCallback114((lat, lng) => {
29756
+ const eventBus = useEventBus3();
29757
+ const [clickedPosition, setClickedPosition] = useState110(null);
29758
+ const handleMapClick = useCallback120((lat, lng) => {
29629
29759
  if (showClickedPin) {
29630
29760
  setClickedPosition({ lat, lng });
29631
29761
  }
@@ -29634,7 +29764,7 @@ var init_MapView = __esm({
29634
29764
  eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
29635
29765
  }
29636
29766
  }, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
29637
- const handleMarkerClick = useCallback114((marker) => {
29767
+ const handleMarkerClick = useCallback120((marker) => {
29638
29768
  onMarkerClick?.(marker);
29639
29769
  if (markerClickEvent) {
29640
29770
  eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
@@ -36761,10 +36891,10 @@ var init_Section = __esm({
36761
36891
  children,
36762
36892
  headerClassName,
36763
36893
  contentClassName,
36764
- as: Component = "section"
36894
+ as: Component2 = "section"
36765
36895
  }) => {
36766
36896
  const hasHeader = title || description || action;
36767
- const Comp = Component;
36897
+ const Comp = Component2;
36768
36898
  return /* @__PURE__ */ jsxs(
36769
36899
  Comp,
36770
36900
  {
@@ -39077,44 +39207,27 @@ function DebuggerBoard({
39077
39207
  const { t } = useTranslate();
39078
39208
  const resolved = boardEntity(entity);
39079
39209
  const [headerError, setHeaderError] = useState(false);
39080
- const [showHint, setShowHint] = useState(false);
39081
39210
  const lines = Array.isArray(resolved?.lines) ? resolved.lines : [];
39082
- const result = resolved?.result ?? null;
39211
+ const result = str(resolved?.result) || "none";
39083
39212
  const attempts = num(resolved?.attempts);
39084
- const submitted = result != null;
39213
+ const submitted = result === "win";
39214
+ const showHint = !submitted && attempts >= 2 && Boolean(str(resolved?.hint));
39085
39215
  const bugLines = lines.filter((l) => l.isBug);
39086
39216
  const flaggedLines = lines.filter((l) => l.isFlagged);
39087
39217
  const correctFlags = lines.filter((l) => l.isBug && l.isFlagged);
39088
39218
  const falseFlags = lines.filter((l) => !l.isBug && l.isFlagged);
39089
- const allCorrect = result === "win";
39219
+ const allCorrect = submitted;
39090
39220
  const toggleLine = (lineId) => {
39091
39221
  if (submitted) return;
39092
- if (toggleFlagEvent) {
39093
- emit(`UI:${toggleFlagEvent}`, { lineId });
39094
- }
39222
+ if (toggleFlagEvent) emit(`UI:${toggleFlagEvent}`, { lineId });
39095
39223
  };
39096
39224
  const handleSubmit = useCallback(() => {
39097
- if (checkEvent) {
39098
- emit(`UI:${checkEvent}`, {});
39099
- }
39225
+ if (checkEvent) emit(`UI:${checkEvent}`, {});
39100
39226
  const correct = correctFlags.length === bugLines.length && falseFlags.length === 0;
39101
- if (correct) {
39102
- emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
39103
- }
39227
+ if (correct) emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
39104
39228
  }, [checkEvent, correctFlags.length, bugLines.length, falseFlags.length, attempts, completeEvent, emit]);
39105
39229
  const handleReset = () => {
39106
- if (playAgainEvent) {
39107
- emit(`UI:${playAgainEvent}`, {});
39108
- }
39109
- if (attempts >= 2 && str(resolved?.hint)) {
39110
- setShowHint(true);
39111
- }
39112
- };
39113
- const handleFullReset = () => {
39114
- if (playAgainEvent) {
39115
- emit(`UI:${playAgainEvent}`, {});
39116
- }
39117
- setShowHint(false);
39230
+ if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
39118
39231
  };
39119
39232
  if (!resolved) return null;
39120
39233
  const theme = resolved.theme ?? void 0;
@@ -39138,7 +39251,7 @@ function DebuggerBoard({
39138
39251
  /* @__PURE__ */ jsx(Typography, { variant: "h4", weight: "bold", children: str(resolved.title) })
39139
39252
  ] }),
39140
39253
  /* @__PURE__ */ jsx(Typography, { variant: "body", children: str(resolved.description) }),
39141
- /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-muted-foreground", children: t("debugger.findBugs", { count: String(num(resolved.bugCount)) }) })
39254
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-muted-foreground", children: t("debugger.findBugs", { count: String(lines.filter((l) => l.isBug).length) }) })
39142
39255
  ] }) }),
39143
39256
  /* @__PURE__ */ jsx(Card, { className: "p-0 overflow-hidden", children: /* @__PURE__ */ jsx(VStack, { gap: "none", children: lines.map((line, i) => {
39144
39257
  const isFlagged = !!line.isFlagged;
@@ -39188,11 +39301,11 @@ function DebuggerBoard({
39188
39301
  ] }) }),
39189
39302
  showHint && hint && /* @__PURE__ */ jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsx(Typography, { variant: "body", children: hint }) }),
39190
39303
  /* @__PURE__ */ jsxs(HStack, { gap: "sm", justify: "center", children: [
39191
- !submitted ? /* @__PURE__ */ jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: flaggedLines.length === 0, children: [
39304
+ !submitted && /* @__PURE__ */ jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: flaggedLines.length === 0, children: [
39192
39305
  /* @__PURE__ */ jsx(Icon, { icon: Send, size: "sm" }),
39193
39306
  t("debugger.submit")
39194
- ] }) : !allCorrect ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleReset, children: t("debugger.tryAgain") }) : null,
39195
- /* @__PURE__ */ jsxs(Button, { variant: "secondary", onClick: handleFullReset, children: [
39307
+ ] }),
39308
+ /* @__PURE__ */ jsxs(Button, { variant: "secondary", onClick: handleReset, children: [
39196
39309
  /* @__PURE__ */ jsx(Icon, { icon: RotateCcw, size: "sm" }),
39197
39310
  t("debugger.reset")
39198
39311
  ] })
@@ -39782,6 +39895,7 @@ var init_DrawerSlot = __esm({
39782
39895
  }
39783
39896
  });
39784
39897
  function StateIndicator({
39898
+ assetUrl = DEFAULT_ASSET_URL11,
39785
39899
  state = "idle",
39786
39900
  label,
39787
39901
  size = "md",
@@ -39804,18 +39918,29 @@ function StateIndicator({
39804
39918
  className
39805
39919
  ),
39806
39920
  children: [
39807
- /* @__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 }) }),
39921
+ /* @__PURE__ */ jsx(Box, { as: "span", children: assetUrl ? /* @__PURE__ */ jsx(
39922
+ "img",
39923
+ {
39924
+ src: assetUrl,
39925
+ alt: displayLabel,
39926
+ width: 16,
39927
+ height: 16,
39928
+ style: { imageRendering: "pixelated", objectFit: "contain" },
39929
+ className: "flex-shrink-0"
39930
+ }
39931
+ ) : 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 }) }),
39808
39932
  /* @__PURE__ */ jsx(Box, { as: "span", children: displayLabel })
39809
39933
  ]
39810
39934
  }
39811
39935
  );
39812
39936
  }
39813
- var DEFAULT_STATE_STYLES, DEFAULT_STYLE, SIZE_CLASSES;
39937
+ var DEFAULT_ASSET_URL11, DEFAULT_STATE_STYLES, DEFAULT_STYLE, SIZE_CLASSES;
39814
39938
  var init_StateIndicator = __esm({
39815
39939
  "components/game/atoms/StateIndicator.tsx"() {
39816
39940
  init_Box();
39817
39941
  init_Icon();
39818
39942
  init_cn();
39943
+ DEFAULT_ASSET_URL11 = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
39819
39944
  DEFAULT_STATE_STYLES = {
39820
39945
  idle: { icon: "\u23F8", bgClass: "bg-muted" },
39821
39946
  active: { icon: "\u25B6", bgClass: "bg-success" },
@@ -40289,39 +40414,40 @@ function EventHandlerBoard({
40289
40414
  stepDurationMs = 800,
40290
40415
  playEvent,
40291
40416
  completeEvent,
40417
+ editRuleEvent,
40418
+ playAgainEvent,
40292
40419
  className
40293
40420
  }) {
40294
40421
  const { emit } = useEventBus();
40295
40422
  const { t } = useTranslate();
40296
40423
  const resolved = boardEntity(entity);
40297
- const entityObjects = rows(resolved?.objects);
40298
- const [objects, setObjects] = useState(() => [...entityObjects]);
40424
+ const objects = rows(resolved?.objects);
40425
+ const entityResult = str(resolved?.result) || "none";
40426
+ const isSuccess = entityResult === "win";
40427
+ const attempts = typeof resolved?.attempts === "number" ? resolved.attempts : 0;
40299
40428
  const [selectedObjectId, setSelectedObjectId] = useState(
40300
- entityObjects[0] ? objId(entityObjects[0]) : null
40429
+ objects[0] ? objId(objects[0]) : null
40301
40430
  );
40302
40431
  const [headerError, setHeaderError] = useState(false);
40303
- const [playState, setPlayState] = useState("editing");
40432
+ const [isPlaying, setIsPlaying] = useState(false);
40304
40433
  const [eventLog, setEventLog] = useState([]);
40305
- const [attempts, setAttempts] = useState(0);
40306
40434
  const timerRef = useRef(null);
40307
40435
  const logIdCounter = useRef(0);
40308
40436
  useEffect(() => () => {
40309
40437
  if (timerRef.current) clearTimeout(timerRef.current);
40310
40438
  }, []);
40311
- const selectedObject = objects.find((o) => objId(o) === selectedObjectId) || null;
40439
+ const selectedObject = objects.find((o) => objId(o) === selectedObjectId) ?? null;
40312
40440
  const handleRulesChange = useCallback((objectId, rules) => {
40313
- setObjects((prev) => prev.map(
40314
- (o) => objId(o) === objectId ? { ...o, rules } : o
40315
- ));
40316
- }, []);
40441
+ if (editRuleEvent) emit(`UI:${editRuleEvent}`, { objectId, rules });
40442
+ }, [editRuleEvent, emit]);
40317
40443
  const addLogEntry = useCallback((icon, message, status = "done") => {
40318
40444
  const id = `log-${logIdCounter.current++}`;
40319
40445
  setEventLog((prev) => [...prev, { id, timestamp: Date.now(), icon, message, status }]);
40320
40446
  }, []);
40321
40447
  const handlePlay = useCallback(() => {
40322
- if (playState !== "editing") return;
40448
+ if (isPlaying || isSuccess) return;
40323
40449
  if (playEvent) emit(`UI:${playEvent}`, {});
40324
- setPlayState("playing");
40450
+ setIsPlaying(true);
40325
40451
  setEventLog([]);
40326
40452
  const allRules = [];
40327
40453
  objects.forEach((obj) => {
@@ -40337,15 +40463,8 @@ function EventHandlerBoard({
40337
40463
  let goalReached = false;
40338
40464
  const processNext = () => {
40339
40465
  if (eventQueue.length === 0 || stepIdx > 20) {
40340
- if (goalReached) {
40341
- setPlayState("success");
40342
- if (completeEvent) {
40343
- emit(`UI:${completeEvent}`, { success: true });
40344
- }
40345
- } else {
40346
- setAttempts((prev) => prev + 1);
40347
- setPlayState("fail");
40348
- }
40466
+ setIsPlaying(false);
40467
+ if (goalReached && completeEvent) emit(`UI:${completeEvent}`, { success: true });
40349
40468
  return;
40350
40469
  }
40351
40470
  const currentEvent = eventQueue.shift();
@@ -40376,21 +40495,19 @@ function EventHandlerBoard({
40376
40495
  addLogEntry("\u{1F3AC}", t("eventHandler.simulationStarted", { events: triggers.join(", ") }), "active");
40377
40496
  }
40378
40497
  timerRef.current = setTimeout(processNext, stepDurationMs);
40379
- }, [playState, objects, resolved, stepDurationMs, playEvent, completeEvent, emit, addLogEntry, t]);
40498
+ }, [isPlaying, isSuccess, objects, resolved, stepDurationMs, playEvent, completeEvent, emit, addLogEntry, t]);
40380
40499
  const handleTryAgain = useCallback(() => {
40381
40500
  if (timerRef.current) clearTimeout(timerRef.current);
40382
- setPlayState("editing");
40501
+ setIsPlaying(false);
40383
40502
  setEventLog([]);
40384
40503
  }, []);
40385
40504
  const handleReset = useCallback(() => {
40386
40505
  if (timerRef.current) clearTimeout(timerRef.current);
40387
- const resetObjects = rows(resolved?.objects);
40388
- setObjects([...resetObjects]);
40389
- setPlayState("editing");
40506
+ setIsPlaying(false);
40390
40507
  setEventLog([]);
40391
- setSelectedObjectId(resetObjects[0] ? objId(resetObjects[0]) : null);
40392
- setAttempts(0);
40393
- }, [resolved?.objects]);
40508
+ setSelectedObjectId(objects[0] ? objId(objects[0]) : null);
40509
+ if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
40510
+ }, [objects, playAgainEvent, emit]);
40394
40511
  if (!resolved) return null;
40395
40512
  const objectViewers = objects.map((obj) => {
40396
40513
  const states = objStates(obj);
@@ -40459,12 +40576,12 @@ function EventHandlerBoard({
40459
40576
  {
40460
40577
  object: selectedObject,
40461
40578
  onRulesChange: handleRulesChange,
40462
- disabled: playState !== "editing"
40579
+ disabled: isPlaying
40463
40580
  }
40464
40581
  ),
40465
40582
  eventLog.length > 0 && /* @__PURE__ */ jsx(EventLog, { entries: eventLog }),
40466
- 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") }) }),
40467
- playState === "fail" && /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
40583
+ 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") }) }),
40584
+ !isPlaying && !isSuccess && attempts > 0 && /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
40468
40585
  /* @__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) }) }),
40469
40586
  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: [
40470
40587
  /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-accent font-bold shrink-0", children: "\u{1F4A1} " + t("game.hint") + ":" }),
@@ -40472,12 +40589,12 @@ function EventHandlerBoard({
40472
40589
  ] }) })
40473
40590
  ] }),
40474
40591
  /* @__PURE__ */ jsxs(HStack, { gap: "sm", children: [
40475
- playState === "fail" ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleTryAgain, children: "\u{1F504} " + t("puzzle.tryAgainButton") }) : /* @__PURE__ */ jsx(
40592
+ !isPlaying && !isSuccess && attempts > 0 ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleTryAgain, children: "\u{1F504} " + t("puzzle.tryAgainButton") }) : /* @__PURE__ */ jsx(
40476
40593
  Button,
40477
40594
  {
40478
40595
  variant: "primary",
40479
40596
  onClick: handlePlay,
40480
- disabled: playState !== "editing",
40597
+ disabled: isPlaying || isSuccess,
40481
40598
  children: "\u25B6 " + t("game.play")
40482
40599
  }
40483
40600
  ),
@@ -41669,6 +41786,1568 @@ var init_GameAudioToggle = __esm({
41669
41786
  GameAudioToggle.displayName = "GameAudioToggle";
41670
41787
  }
41671
41788
  });
41789
+ function detectAssetRoot(modelUrl) {
41790
+ const idx = modelUrl.indexOf("/3d/");
41791
+ if (idx !== -1) {
41792
+ return modelUrl.substring(0, idx + 4);
41793
+ }
41794
+ return modelUrl.substring(0, modelUrl.lastIndexOf("/") + 1);
41795
+ }
41796
+ function createGLTFLoaderForUrl(url) {
41797
+ const loader = new GLTFLoader();
41798
+ loader.setResourcePath(detectAssetRoot(url));
41799
+ return loader;
41800
+ }
41801
+ var AssetLoader;
41802
+ var init_AssetLoader = __esm({
41803
+ "components/game/molecules/three/loaders/AssetLoader.ts"() {
41804
+ AssetLoader = class {
41805
+ constructor() {
41806
+ __publicField(this, "objLoader");
41807
+ __publicField(this, "textureLoader");
41808
+ __publicField(this, "modelCache");
41809
+ __publicField(this, "textureCache");
41810
+ __publicField(this, "loadingPromises");
41811
+ this.objLoader = new OBJLoader();
41812
+ this.textureLoader = new THREE.TextureLoader();
41813
+ this.modelCache = /* @__PURE__ */ new Map();
41814
+ this.textureCache = /* @__PURE__ */ new Map();
41815
+ this.loadingPromises = /* @__PURE__ */ new Map();
41816
+ }
41817
+ /**
41818
+ * Load a GLB/GLTF model
41819
+ * @param url - URL to the .glb or .gltf file
41820
+ * @returns Promise with loaded model scene and animations
41821
+ */
41822
+ async loadModel(url) {
41823
+ if (this.modelCache.has(url)) {
41824
+ return this.modelCache.get(url);
41825
+ }
41826
+ if (this.loadingPromises.has(url)) {
41827
+ return this.loadingPromises.get(url);
41828
+ }
41829
+ const loader = createGLTFLoaderForUrl(url);
41830
+ const loadPromise = loader.loadAsync(url).then((gltf) => {
41831
+ const result = {
41832
+ scene: gltf.scene,
41833
+ animations: gltf.animations || []
41834
+ };
41835
+ this.modelCache.set(url, result);
41836
+ this.loadingPromises.delete(url);
41837
+ return result;
41838
+ }).catch((error) => {
41839
+ this.loadingPromises.delete(url);
41840
+ throw new Error(`Failed to load model ${url}: ${error.message}`);
41841
+ });
41842
+ this.loadingPromises.set(url, loadPromise);
41843
+ return loadPromise;
41844
+ }
41845
+ /**
41846
+ * Load an OBJ model (fallback for non-GLB assets)
41847
+ * @param url - URL to the .obj file
41848
+ * @returns Promise with loaded object group
41849
+ */
41850
+ async loadOBJ(url) {
41851
+ if (this.modelCache.has(url)) {
41852
+ return this.modelCache.get(url).scene;
41853
+ }
41854
+ if (this.loadingPromises.has(url)) {
41855
+ const result = await this.loadingPromises.get(url);
41856
+ return result.scene;
41857
+ }
41858
+ const loadPromise = this.objLoader.loadAsync(url).then((group) => {
41859
+ const result = {
41860
+ scene: group,
41861
+ animations: []
41862
+ };
41863
+ this.modelCache.set(url, result);
41864
+ this.loadingPromises.delete(url);
41865
+ return result;
41866
+ }).catch((error) => {
41867
+ this.loadingPromises.delete(url);
41868
+ throw new Error(`Failed to load OBJ ${url}: ${error.message}`);
41869
+ });
41870
+ this.loadingPromises.set(url, loadPromise);
41871
+ return (await loadPromise).scene;
41872
+ }
41873
+ /**
41874
+ * Load a texture
41875
+ * @param url - URL to the texture image
41876
+ * @returns Promise with loaded texture
41877
+ */
41878
+ async loadTexture(url) {
41879
+ if (this.textureCache.has(url)) {
41880
+ return this.textureCache.get(url);
41881
+ }
41882
+ if (this.loadingPromises.has(`texture:${url}`)) {
41883
+ return this.loadingPromises.get(`texture:${url}`);
41884
+ }
41885
+ const loadPromise = this.textureLoader.loadAsync(url).then((texture) => {
41886
+ texture.colorSpace = THREE.SRGBColorSpace;
41887
+ this.textureCache.set(url, texture);
41888
+ this.loadingPromises.delete(`texture:${url}`);
41889
+ return texture;
41890
+ }).catch((error) => {
41891
+ this.loadingPromises.delete(`texture:${url}`);
41892
+ throw new Error(`Failed to load texture ${url}: ${error.message}`);
41893
+ });
41894
+ this.loadingPromises.set(`texture:${url}`, loadPromise);
41895
+ return loadPromise;
41896
+ }
41897
+ /**
41898
+ * Preload multiple assets
41899
+ * @param urls - Array of asset URLs to preload
41900
+ * @returns Promise that resolves when all assets are loaded
41901
+ */
41902
+ async preload(urls) {
41903
+ const promises = urls.map((url) => {
41904
+ if (url.endsWith(".glb") || url.endsWith(".gltf")) {
41905
+ return this.loadModel(url).catch(() => null);
41906
+ } else if (url.endsWith(".obj")) {
41907
+ return this.loadOBJ(url).catch(() => null);
41908
+ } else if (/\.(png|jpg|jpeg|webp)$/i.test(url)) {
41909
+ return this.loadTexture(url).catch(() => null);
41910
+ }
41911
+ return Promise.resolve(null);
41912
+ });
41913
+ await Promise.all(promises);
41914
+ }
41915
+ /**
41916
+ * Check if a model is cached
41917
+ * @param url - Model URL
41918
+ */
41919
+ hasModel(url) {
41920
+ return this.modelCache.has(url);
41921
+ }
41922
+ /**
41923
+ * Check if a texture is cached
41924
+ * @param url - Texture URL
41925
+ */
41926
+ hasTexture(url) {
41927
+ return this.textureCache.has(url);
41928
+ }
41929
+ /**
41930
+ * Get cached model (throws if not cached)
41931
+ * @param url - Model URL
41932
+ */
41933
+ getModel(url) {
41934
+ const model = this.modelCache.get(url);
41935
+ if (!model) {
41936
+ throw new Error(`Model ${url} not in cache`);
41937
+ }
41938
+ return model;
41939
+ }
41940
+ /**
41941
+ * Get cached texture (throws if not cached)
41942
+ * @param url - Texture URL
41943
+ */
41944
+ getTexture(url) {
41945
+ const texture = this.textureCache.get(url);
41946
+ if (!texture) {
41947
+ throw new Error(`Texture ${url} not in cache`);
41948
+ }
41949
+ return texture;
41950
+ }
41951
+ /**
41952
+ * Clear all caches
41953
+ */
41954
+ clearCache() {
41955
+ this.textureCache.forEach((texture) => {
41956
+ texture.dispose();
41957
+ });
41958
+ this.modelCache.forEach((model) => {
41959
+ model.scene.traverse((child) => {
41960
+ if (child instanceof THREE.Mesh) {
41961
+ child.geometry.dispose();
41962
+ if (Array.isArray(child.material)) {
41963
+ child.material.forEach((m) => m.dispose());
41964
+ } else {
41965
+ child.material.dispose();
41966
+ }
41967
+ }
41968
+ });
41969
+ });
41970
+ this.modelCache.clear();
41971
+ this.textureCache.clear();
41972
+ this.loadingPromises.clear();
41973
+ }
41974
+ /**
41975
+ * Get cache statistics
41976
+ */
41977
+ getStats() {
41978
+ return {
41979
+ models: this.modelCache.size,
41980
+ textures: this.textureCache.size,
41981
+ loading: this.loadingPromises.size
41982
+ };
41983
+ }
41984
+ };
41985
+ new AssetLoader();
41986
+ }
41987
+ });
41988
+ function useAssetLoader(options = {}) {
41989
+ const { preloadUrls = [], loader: customLoader } = options;
41990
+ const loaderRef = useRef(customLoader || new AssetLoader());
41991
+ const [state, setState] = useState({
41992
+ isLoading: false,
41993
+ progress: 0,
41994
+ loaded: 0,
41995
+ total: 0,
41996
+ errors: []
41997
+ });
41998
+ useEffect(() => {
41999
+ if (preloadUrls.length > 0) {
42000
+ preload(preloadUrls);
42001
+ }
42002
+ }, []);
42003
+ const updateProgress = useCallback((loaded, total) => {
42004
+ setState((prev) => ({
42005
+ ...prev,
42006
+ loaded,
42007
+ total,
42008
+ progress: total > 0 ? Math.round(loaded / total * 100) : 0
42009
+ }));
42010
+ }, []);
42011
+ const loadModel = useCallback(
42012
+ async (url) => {
42013
+ setState((prev) => ({ ...prev, isLoading: true }));
42014
+ try {
42015
+ const model = await loaderRef.current.loadModel(url);
42016
+ setState((prev) => ({
42017
+ ...prev,
42018
+ isLoading: false,
42019
+ loaded: prev.loaded + 1
42020
+ }));
42021
+ return model;
42022
+ } catch (error) {
42023
+ const errorMsg = error instanceof Error ? error.message : String(error);
42024
+ setState((prev) => ({
42025
+ ...prev,
42026
+ isLoading: false,
42027
+ errors: [...prev.errors, errorMsg]
42028
+ }));
42029
+ throw error;
42030
+ }
42031
+ },
42032
+ []
42033
+ );
42034
+ const loadOBJ = useCallback(
42035
+ async (url) => {
42036
+ setState((prev) => ({ ...prev, isLoading: true }));
42037
+ try {
42038
+ const model = await loaderRef.current.loadOBJ(url);
42039
+ setState((prev) => ({
42040
+ ...prev,
42041
+ isLoading: false,
42042
+ loaded: prev.loaded + 1
42043
+ }));
42044
+ return model;
42045
+ } catch (error) {
42046
+ const errorMsg = error instanceof Error ? error.message : String(error);
42047
+ setState((prev) => ({
42048
+ ...prev,
42049
+ isLoading: false,
42050
+ errors: [...prev.errors, errorMsg]
42051
+ }));
42052
+ throw error;
42053
+ }
42054
+ },
42055
+ []
42056
+ );
42057
+ const loadTexture = useCallback(
42058
+ async (url) => {
42059
+ setState((prev) => ({ ...prev, isLoading: true }));
42060
+ try {
42061
+ const texture = await loaderRef.current.loadTexture(url);
42062
+ setState((prev) => ({
42063
+ ...prev,
42064
+ isLoading: false,
42065
+ loaded: prev.loaded + 1
42066
+ }));
42067
+ return texture;
42068
+ } catch (error) {
42069
+ const errorMsg = error instanceof Error ? error.message : String(error);
42070
+ setState((prev) => ({
42071
+ ...prev,
42072
+ isLoading: false,
42073
+ errors: [...prev.errors, errorMsg]
42074
+ }));
42075
+ throw error;
42076
+ }
42077
+ },
42078
+ []
42079
+ );
42080
+ const preload = useCallback(
42081
+ async (urls) => {
42082
+ setState((prev) => ({
42083
+ ...prev,
42084
+ isLoading: true,
42085
+ total: urls.length,
42086
+ loaded: 0,
42087
+ errors: []
42088
+ }));
42089
+ let completed = 0;
42090
+ const errors = [];
42091
+ await Promise.all(
42092
+ urls.map(async (url) => {
42093
+ try {
42094
+ if (url.endsWith(".glb") || url.endsWith(".gltf")) {
42095
+ await loaderRef.current.loadModel(url);
42096
+ } else if (url.endsWith(".obj")) {
42097
+ await loaderRef.current.loadOBJ(url);
42098
+ } else if (/\.(png|jpg|jpeg|webp)$/i.test(url)) {
42099
+ await loaderRef.current.loadTexture(url);
42100
+ }
42101
+ completed++;
42102
+ updateProgress(completed, urls.length);
42103
+ } catch (error) {
42104
+ const errorMsg = error instanceof Error ? error.message : String(error);
42105
+ errors.push(`${url}: ${errorMsg}`);
42106
+ completed++;
42107
+ updateProgress(completed, urls.length);
42108
+ }
42109
+ })
42110
+ );
42111
+ setState((prev) => ({
42112
+ ...prev,
42113
+ isLoading: false,
42114
+ errors
42115
+ }));
42116
+ },
42117
+ [updateProgress]
42118
+ );
42119
+ const hasModel = useCallback((url) => {
42120
+ return loaderRef.current.hasModel(url);
42121
+ }, []);
42122
+ const hasTexture = useCallback((url) => {
42123
+ return loaderRef.current.hasTexture(url);
42124
+ }, []);
42125
+ const getModel = useCallback((url) => {
42126
+ try {
42127
+ return loaderRef.current.getModel(url);
42128
+ } catch {
42129
+ return void 0;
42130
+ }
42131
+ }, []);
42132
+ const getTexture = useCallback((url) => {
42133
+ try {
42134
+ return loaderRef.current.getTexture(url);
42135
+ } catch {
42136
+ return void 0;
42137
+ }
42138
+ }, []);
42139
+ const clearCache = useCallback(() => {
42140
+ loaderRef.current.clearCache();
42141
+ setState({
42142
+ isLoading: false,
42143
+ progress: 0,
42144
+ loaded: 0,
42145
+ total: 0,
42146
+ errors: []
42147
+ });
42148
+ }, []);
42149
+ return {
42150
+ ...state,
42151
+ loadModel,
42152
+ loadOBJ,
42153
+ loadTexture,
42154
+ preload,
42155
+ hasModel,
42156
+ hasTexture,
42157
+ getModel,
42158
+ getTexture,
42159
+ clearCache
42160
+ };
42161
+ }
42162
+ var init_useAssetLoader = __esm({
42163
+ "components/game/molecules/three/hooks/useAssetLoader.ts"() {
42164
+ "use client";
42165
+ init_AssetLoader();
42166
+ }
42167
+ });
42168
+ function useGameCanvas3DEvents(options) {
42169
+ const {
42170
+ tileClickEvent,
42171
+ unitClickEvent,
42172
+ featureClickEvent,
42173
+ canvasClickEvent,
42174
+ tileHoverEvent,
42175
+ tileLeaveEvent,
42176
+ unitAnimationEvent,
42177
+ cameraChangeEvent,
42178
+ onTileClick,
42179
+ onUnitClick,
42180
+ onFeatureClick,
42181
+ onCanvasClick,
42182
+ onTileHover,
42183
+ onUnitAnimation
42184
+ } = options;
42185
+ const emit = useEmitEvent();
42186
+ const optionsRef = useRef(options);
42187
+ optionsRef.current = options;
42188
+ const handleTileClick = useCallback(
42189
+ (tile, event) => {
42190
+ if (tileClickEvent) {
42191
+ emit(tileClickEvent, {
42192
+ tileId: tile.id,
42193
+ x: tile.x,
42194
+ z: tile.z ?? tile.y ?? 0,
42195
+ type: tile.type,
42196
+ terrain: tile.terrain,
42197
+ elevation: tile.elevation
42198
+ });
42199
+ }
42200
+ optionsRef.current.onTileClick?.(tile, event);
42201
+ },
42202
+ [tileClickEvent, emit]
42203
+ );
42204
+ const handleUnitClick = useCallback(
42205
+ (unit, event) => {
42206
+ if (unitClickEvent) {
42207
+ emit(unitClickEvent, {
42208
+ unitId: unit.id,
42209
+ x: unit.x,
42210
+ z: unit.z ?? unit.y ?? 0,
42211
+ unitType: unit.unitType,
42212
+ name: unit.name,
42213
+ team: unit.team,
42214
+ faction: unit.faction,
42215
+ health: unit.health,
42216
+ maxHealth: unit.maxHealth
42217
+ });
42218
+ }
42219
+ optionsRef.current.onUnitClick?.(unit, event);
42220
+ },
42221
+ [unitClickEvent, emit]
42222
+ );
42223
+ const handleFeatureClick = useCallback(
42224
+ (feature, event) => {
42225
+ if (featureClickEvent) {
42226
+ emit(featureClickEvent, {
42227
+ featureId: feature.id,
42228
+ x: feature.x,
42229
+ z: feature.z ?? feature.y ?? 0,
42230
+ type: feature.type,
42231
+ elevation: feature.elevation
42232
+ });
42233
+ }
42234
+ optionsRef.current.onFeatureClick?.(feature, event);
42235
+ },
42236
+ [featureClickEvent, emit]
42237
+ );
42238
+ const handleCanvasClick = useCallback(
42239
+ (event) => {
42240
+ if (canvasClickEvent) {
42241
+ emit(canvasClickEvent, {
42242
+ clientX: event.clientX,
42243
+ clientY: event.clientY,
42244
+ button: event.button
42245
+ });
42246
+ }
42247
+ optionsRef.current.onCanvasClick?.(event);
42248
+ },
42249
+ [canvasClickEvent, emit]
42250
+ );
42251
+ const handleTileHover = useCallback(
42252
+ (tile, event) => {
42253
+ if (tile) {
42254
+ if (tileHoverEvent) {
42255
+ emit(tileHoverEvent, {
42256
+ tileId: tile.id,
42257
+ x: tile.x,
42258
+ z: tile.z ?? tile.y ?? 0,
42259
+ type: tile.type
42260
+ });
42261
+ }
42262
+ } else {
42263
+ if (tileLeaveEvent) {
42264
+ emit(tileLeaveEvent, {});
42265
+ }
42266
+ }
42267
+ optionsRef.current.onTileHover?.(tile, event);
42268
+ },
42269
+ [tileHoverEvent, tileLeaveEvent, emit]
42270
+ );
42271
+ const handleUnitAnimation = useCallback(
42272
+ (unitId, state) => {
42273
+ if (unitAnimationEvent) {
42274
+ emit(unitAnimationEvent, {
42275
+ unitId,
42276
+ state,
42277
+ timestamp: Date.now()
42278
+ });
42279
+ }
42280
+ optionsRef.current.onUnitAnimation?.(unitId, state);
42281
+ },
42282
+ [unitAnimationEvent, emit]
42283
+ );
42284
+ const handleCameraChange = useCallback(
42285
+ (position) => {
42286
+ if (cameraChangeEvent) {
42287
+ emit(cameraChangeEvent, {
42288
+ position,
42289
+ timestamp: Date.now()
42290
+ });
42291
+ }
42292
+ },
42293
+ [cameraChangeEvent, emit]
42294
+ );
42295
+ return {
42296
+ handleTileClick,
42297
+ handleUnitClick,
42298
+ handleFeatureClick,
42299
+ handleCanvasClick,
42300
+ handleTileHover,
42301
+ handleUnitAnimation,
42302
+ handleCameraChange
42303
+ };
42304
+ }
42305
+ var init_useGameCanvas3DEvents = __esm({
42306
+ "components/game/molecules/three/hooks/useGameCanvas3DEvents.ts"() {
42307
+ "use client";
42308
+ init_useEventBus();
42309
+ }
42310
+ });
42311
+
42312
+ // components/game/molecules/three/components/Canvas3DLoadingState.css
42313
+ var init_Canvas3DLoadingState = __esm({
42314
+ "components/game/molecules/three/components/Canvas3DLoadingState.css"() {
42315
+ }
42316
+ });
42317
+ function Canvas3DLoadingState({
42318
+ progress = 0,
42319
+ loaded = 0,
42320
+ total = 0,
42321
+ message = "Loading 3D Scene...",
42322
+ details,
42323
+ showSpinner = true,
42324
+ className
42325
+ }) {
42326
+ const clampedProgress = Math.max(0, Math.min(100, progress));
42327
+ const hasProgress = total > 0;
42328
+ return /* @__PURE__ */ jsxs("div", { className: `canvas-3d-loading ${className || ""}`, children: [
42329
+ /* @__PURE__ */ jsxs("div", { className: "canvas-3d-loading__content", children: [
42330
+ showSpinner && /* @__PURE__ */ jsxs("div", { className: "canvas-3d-loading__spinner", children: [
42331
+ /* @__PURE__ */ jsx("div", { className: "spinner__ring" }),
42332
+ /* @__PURE__ */ jsx("div", { className: "spinner__ring spinner__ring--secondary" })
42333
+ ] }),
42334
+ /* @__PURE__ */ jsx("div", { className: "canvas-3d-loading__message", children: message }),
42335
+ details && /* @__PURE__ */ jsx("div", { className: "canvas-3d-loading__details", children: details }),
42336
+ hasProgress && /* @__PURE__ */ jsxs("div", { className: "canvas-3d-loading__progress", children: [
42337
+ /* @__PURE__ */ jsx("div", { className: "progress__bar", children: /* @__PURE__ */ jsx(
42338
+ "div",
42339
+ {
42340
+ className: "progress__fill",
42341
+ style: { width: `${clampedProgress}%` }
42342
+ }
42343
+ ) }),
42344
+ /* @__PURE__ */ jsxs("div", { className: "progress__text", children: [
42345
+ /* @__PURE__ */ jsxs("span", { className: "progress__percentage", children: [
42346
+ clampedProgress,
42347
+ "%"
42348
+ ] }),
42349
+ /* @__PURE__ */ jsxs("span", { className: "progress__count", children: [
42350
+ "(",
42351
+ loaded,
42352
+ "/",
42353
+ total,
42354
+ ")"
42355
+ ] })
42356
+ ] })
42357
+ ] })
42358
+ ] }),
42359
+ /* @__PURE__ */ jsx("div", { className: "canvas-3d-loading__background", children: /* @__PURE__ */ jsx("div", { className: "bg__grid" }) })
42360
+ ] });
42361
+ }
42362
+ var init_Canvas3DLoadingState2 = __esm({
42363
+ "components/game/molecules/three/components/Canvas3DLoadingState.tsx"() {
42364
+ init_Canvas3DLoadingState();
42365
+ }
42366
+ });
42367
+
42368
+ // components/game/molecules/three/components/Canvas3DErrorBoundary.css
42369
+ var init_Canvas3DErrorBoundary = __esm({
42370
+ "components/game/molecules/three/components/Canvas3DErrorBoundary.css"() {
42371
+ }
42372
+ });
42373
+ var log9, Canvas3DErrorBoundary;
42374
+ var init_Canvas3DErrorBoundary2 = __esm({
42375
+ "components/game/molecules/three/components/Canvas3DErrorBoundary.tsx"() {
42376
+ init_Canvas3DErrorBoundary();
42377
+ log9 = createLogger("almadar:ui:game:canvas3d:error-boundary");
42378
+ Canvas3DErrorBoundary = class extends Component {
42379
+ constructor(props) {
42380
+ super(props);
42381
+ __publicField(this, "handleReset", () => {
42382
+ this.setState({
42383
+ hasError: false,
42384
+ error: null,
42385
+ errorInfo: null
42386
+ });
42387
+ this.props.onReset?.();
42388
+ });
42389
+ this.state = {
42390
+ hasError: false,
42391
+ error: null,
42392
+ errorInfo: null
42393
+ };
42394
+ }
42395
+ static getDerivedStateFromError(error) {
42396
+ return {
42397
+ hasError: true,
42398
+ error,
42399
+ errorInfo: null
42400
+ };
42401
+ }
42402
+ componentDidCatch(error, errorInfo) {
42403
+ this.setState({ errorInfo });
42404
+ this.props.onError?.(error, errorInfo);
42405
+ log9.error("Error caught", { error });
42406
+ log9.error("Component stack", { componentStack: errorInfo.componentStack ?? "<none>" });
42407
+ }
42408
+ render() {
42409
+ if (this.state.hasError) {
42410
+ if (this.props.fallback) {
42411
+ return this.props.fallback;
42412
+ }
42413
+ return /* @__PURE__ */ jsx("div", { className: "canvas-3d-error", children: /* @__PURE__ */ jsxs("div", { className: "canvas-3d-error__content", children: [
42414
+ /* @__PURE__ */ jsx("div", { className: "canvas-3d-error__icon", children: "\u26A0\uFE0F" }),
42415
+ /* @__PURE__ */ jsx("h2", { className: "canvas-3d-error__title", children: "3D Scene Error" }),
42416
+ /* @__PURE__ */ jsx("p", { className: "canvas-3d-error__message", children: "Something went wrong while rendering the 3D scene." }),
42417
+ this.state.error && /* @__PURE__ */ jsxs("details", { className: "canvas-3d-error__details", children: [
42418
+ /* @__PURE__ */ jsx("summary", { children: "Error Details" }),
42419
+ /* @__PURE__ */ jsxs("pre", { className: "error__stack", children: [
42420
+ this.state.error.message,
42421
+ "\n",
42422
+ this.state.error.stack
42423
+ ] }),
42424
+ this.state.errorInfo && /* @__PURE__ */ jsx("pre", { className: "error__component-stack", children: this.state.errorInfo.componentStack })
42425
+ ] }),
42426
+ /* @__PURE__ */ jsxs("div", { className: "canvas-3d-error__actions", children: [
42427
+ /* @__PURE__ */ jsx(
42428
+ "button",
42429
+ {
42430
+ className: "error__button error__button--primary",
42431
+ onClick: this.handleReset,
42432
+ children: "Try Again"
42433
+ }
42434
+ ),
42435
+ /* @__PURE__ */ jsx(
42436
+ "button",
42437
+ {
42438
+ className: "error__button error__button--secondary",
42439
+ onClick: () => window.location.reload(),
42440
+ children: "Reload Page"
42441
+ }
42442
+ )
42443
+ ] })
42444
+ ] }) });
42445
+ }
42446
+ return this.props.children;
42447
+ }
42448
+ };
42449
+ }
42450
+ });
42451
+ function detectAssetRoot2(modelUrl) {
42452
+ const idx = modelUrl.indexOf("/3d/");
42453
+ if (idx !== -1) {
42454
+ return modelUrl.substring(0, idx + 4);
42455
+ }
42456
+ return modelUrl.substring(0, modelUrl.lastIndexOf("/") + 1);
42457
+ }
42458
+ function useGLTFModel(url, resourceBasePath) {
42459
+ const [state, setState] = useState({
42460
+ model: null,
42461
+ isLoading: false,
42462
+ error: null
42463
+ });
42464
+ useEffect(() => {
42465
+ if (!url) {
42466
+ setState({ model: null, isLoading: false, error: null });
42467
+ return;
42468
+ }
42469
+ log10.debug("Loading", { url });
42470
+ setState((prev) => ({ ...prev, isLoading: true, error: null }));
42471
+ const assetRoot = resourceBasePath || detectAssetRoot2(url);
42472
+ const loader = new GLTFLoader$1();
42473
+ loader.setResourcePath(assetRoot);
42474
+ loader.load(
42475
+ url,
42476
+ (gltf) => {
42477
+ log10.debug("Loaded", { url });
42478
+ setState({
42479
+ model: gltf.scene,
42480
+ isLoading: false,
42481
+ error: null
42482
+ });
42483
+ },
42484
+ void 0,
42485
+ (err) => {
42486
+ log10.warn("Failed", { url, error: err instanceof Error ? err : String(err) });
42487
+ setState({
42488
+ model: null,
42489
+ isLoading: false,
42490
+ error: err instanceof Error ? err : new Error(String(err))
42491
+ });
42492
+ }
42493
+ );
42494
+ }, [url, resourceBasePath]);
42495
+ return state;
42496
+ }
42497
+ function ModelLoader({
42498
+ url,
42499
+ position = [0, 0, 0],
42500
+ scale = 1,
42501
+ rotation = [0, 0, 0],
42502
+ isSelected = false,
42503
+ isHovered = false,
42504
+ onClick,
42505
+ onHover,
42506
+ fallbackGeometry = "box",
42507
+ castShadow = true,
42508
+ receiveShadow = true,
42509
+ resourceBasePath
42510
+ }) {
42511
+ const { model: loadedModel, isLoading, error } = useGLTFModel(url, resourceBasePath);
42512
+ const model = useMemo(() => {
42513
+ if (!loadedModel) return null;
42514
+ const cloned = loadedModel.clone();
42515
+ cloned.traverse((child) => {
42516
+ if (child instanceof THREE.Mesh) {
42517
+ child.castShadow = castShadow;
42518
+ child.receiveShadow = receiveShadow;
42519
+ }
42520
+ });
42521
+ return cloned;
42522
+ }, [loadedModel, castShadow, receiveShadow]);
42523
+ const scaleArray = useMemo(() => {
42524
+ if (typeof scale === "number") {
42525
+ return [scale, scale, scale];
42526
+ }
42527
+ return scale;
42528
+ }, [scale]);
42529
+ const rotationRad = useMemo(() => {
42530
+ return [
42531
+ rotation[0] * Math.PI / 180,
42532
+ rotation[1] * Math.PI / 180,
42533
+ rotation[2] * Math.PI / 180
42534
+ ];
42535
+ }, [rotation]);
42536
+ if (isLoading) {
42537
+ return /* @__PURE__ */ jsx("group", { position, children: /* @__PURE__ */ jsxs("mesh", { rotation: [Math.PI / 2, 0, 0], children: [
42538
+ /* @__PURE__ */ jsx("ringGeometry", { args: [0.3, 0.35, 16] }),
42539
+ /* @__PURE__ */ jsx("meshBasicMaterial", { color: "#4a90d9", transparent: true, opacity: 0.8 })
42540
+ ] }) });
42541
+ }
42542
+ if (error || !model) {
42543
+ if (fallbackGeometry === "none") {
42544
+ return /* @__PURE__ */ jsx("group", { position });
42545
+ }
42546
+ const fallbackProps = {
42547
+ onClick,
42548
+ onPointerOver: () => onHover?.(true),
42549
+ onPointerOut: () => onHover?.(false)
42550
+ };
42551
+ return /* @__PURE__ */ jsxs("group", { position, children: [
42552
+ (isSelected || isHovered) && /* @__PURE__ */ jsxs("mesh", { position: [0, 0.02, 0], rotation: [-Math.PI / 2, 0, 0], children: [
42553
+ /* @__PURE__ */ jsx("ringGeometry", { args: [0.6, 0.7, 32] }),
42554
+ /* @__PURE__ */ jsx(
42555
+ "meshBasicMaterial",
42556
+ {
42557
+ color: isSelected ? 16755200 : 16777215,
42558
+ transparent: true,
42559
+ opacity: 0.5
42560
+ }
42561
+ )
42562
+ ] }),
42563
+ fallbackGeometry === "box" && /* @__PURE__ */ jsxs("mesh", { ...fallbackProps, position: [0, 0.5, 0], children: [
42564
+ /* @__PURE__ */ jsx("boxGeometry", { args: [0.8, 0.8, 0.8] }),
42565
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color: error ? 16729156 : 8947848 })
42566
+ ] }),
42567
+ fallbackGeometry === "sphere" && /* @__PURE__ */ jsxs("mesh", { ...fallbackProps, position: [0, 0.5, 0], children: [
42568
+ /* @__PURE__ */ jsx("sphereGeometry", { args: [0.4, 16, 16] }),
42569
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color: error ? 16729156 : 8947848 })
42570
+ ] }),
42571
+ fallbackGeometry === "cylinder" && /* @__PURE__ */ jsxs("mesh", { ...fallbackProps, position: [0, 0.5, 0], children: [
42572
+ /* @__PURE__ */ jsx("cylinderGeometry", { args: [0.3, 0.3, 0.8, 16] }),
42573
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color: error ? 16729156 : 8947848 })
42574
+ ] })
42575
+ ] });
42576
+ }
42577
+ return /* @__PURE__ */ jsxs(
42578
+ "group",
42579
+ {
42580
+ position,
42581
+ rotation: rotationRad,
42582
+ onClick,
42583
+ onPointerOver: () => onHover?.(true),
42584
+ onPointerOut: () => onHover?.(false),
42585
+ children: [
42586
+ (isSelected || isHovered) && /* @__PURE__ */ jsxs("mesh", { position: [0, 0.02, 0], rotation: [-Math.PI / 2, 0, 0], children: [
42587
+ /* @__PURE__ */ jsx("ringGeometry", { args: [0.6, 0.7, 32] }),
42588
+ /* @__PURE__ */ jsx(
42589
+ "meshBasicMaterial",
42590
+ {
42591
+ color: isSelected ? 16755200 : 16777215,
42592
+ transparent: true,
42593
+ opacity: 0.5
42594
+ }
42595
+ )
42596
+ ] }),
42597
+ /* @__PURE__ */ jsx("primitive", { object: model, scale: scaleArray })
42598
+ ]
42599
+ }
42600
+ );
42601
+ }
42602
+ var log10;
42603
+ var init_ModelLoader = __esm({
42604
+ "components/game/molecules/three/components/ModelLoader.tsx"() {
42605
+ "use client";
42606
+ log10 = createLogger("almadar:ui:game:model-loader");
42607
+ }
42608
+ });
42609
+
42610
+ // components/game/molecules/GameCanvas3D.css
42611
+ var init_GameCanvas3D = __esm({
42612
+ "components/game/molecules/GameCanvas3D.css"() {
42613
+ }
42614
+ });
42615
+ function CameraController({
42616
+ onCameraChange
42617
+ }) {
42618
+ const { camera } = useThree();
42619
+ useEffect(() => {
42620
+ if (onCameraChange) {
42621
+ onCameraChange({
42622
+ x: camera.position.x,
42623
+ y: camera.position.y,
42624
+ z: camera.position.z
42625
+ });
42626
+ }
42627
+ }, [camera.position, onCameraChange]);
42628
+ return null;
42629
+ }
42630
+ var DEFAULT_GRID_CONFIG, GameCanvas3D;
42631
+ var init_GameCanvas3D2 = __esm({
42632
+ "components/game/molecules/GameCanvas3D.tsx"() {
42633
+ "use client";
42634
+ init_AssetLoader();
42635
+ init_useAssetLoader();
42636
+ init_useGameCanvas3DEvents();
42637
+ init_Canvas3DLoadingState2();
42638
+ init_Canvas3DErrorBoundary2();
42639
+ init_ModelLoader();
42640
+ init_cn();
42641
+ init_GameCanvas3D();
42642
+ DEFAULT_GRID_CONFIG = {
42643
+ cellSize: 1,
42644
+ offsetX: 0,
42645
+ offsetZ: 0
42646
+ };
42647
+ GameCanvas3D = forwardRef(
42648
+ ({
42649
+ tiles = [],
42650
+ units = [],
42651
+ features = [],
42652
+ events: events2 = [],
42653
+ orientation = "standard",
42654
+ cameraMode = "isometric",
42655
+ showGrid = true,
42656
+ showCoordinates = false,
42657
+ showTileInfo = false,
42658
+ overlay = "default",
42659
+ shadows = true,
42660
+ backgroundColor = "#1a1a2e",
42661
+ onTileClick,
42662
+ onUnitClick,
42663
+ onFeatureClick,
42664
+ onCanvasClick,
42665
+ onTileHover,
42666
+ onUnitAnimation,
42667
+ assetLoader: customAssetLoader,
42668
+ tileRenderer: CustomTileRenderer,
42669
+ unitRenderer: CustomUnitRenderer,
42670
+ featureRenderer: CustomFeatureRenderer,
42671
+ className,
42672
+ isLoading: externalLoading,
42673
+ error: externalError,
42674
+ entity,
42675
+ preloadAssets = [],
42676
+ tileClickEvent,
42677
+ unitClickEvent,
42678
+ featureClickEvent,
42679
+ canvasClickEvent,
42680
+ tileHoverEvent,
42681
+ tileLeaveEvent,
42682
+ unitAnimationEvent,
42683
+ cameraChangeEvent,
42684
+ loadingMessage = "Loading 3D Scene...",
42685
+ useInstancing = true,
42686
+ validMoves = [],
42687
+ attackTargets = [],
42688
+ selectedTileIds = [],
42689
+ selectedUnitId = null,
42690
+ children
42691
+ }, ref) => {
42692
+ const containerRef = useRef(null);
42693
+ const controlsRef = useRef(null);
42694
+ const [hoveredTile, setHoveredTile] = useState(null);
42695
+ const [internalError, setInternalError] = useState(null);
42696
+ const { isLoading: assetsLoading, progress, loaded, total } = useAssetLoader({
42697
+ preloadUrls: preloadAssets,
42698
+ loader: customAssetLoader
42699
+ });
42700
+ const eventHandlers = useGameCanvas3DEvents({
42701
+ tileClickEvent,
42702
+ unitClickEvent,
42703
+ featureClickEvent,
42704
+ canvasClickEvent,
42705
+ tileHoverEvent,
42706
+ tileLeaveEvent,
42707
+ unitAnimationEvent,
42708
+ cameraChangeEvent,
42709
+ onTileClick,
42710
+ onUnitClick,
42711
+ onFeatureClick,
42712
+ onCanvasClick,
42713
+ onTileHover,
42714
+ onUnitAnimation
42715
+ });
42716
+ const gridBounds = useMemo(() => {
42717
+ if (tiles.length === 0) {
42718
+ return { minX: 0, maxX: 10, minZ: 0, maxZ: 10 };
42719
+ }
42720
+ const xs = tiles.map((t) => t.x);
42721
+ const zs = tiles.map((t) => t.z || t.y || 0);
42722
+ return {
42723
+ minX: Math.min(...xs),
42724
+ maxX: Math.max(...xs),
42725
+ minZ: Math.min(...zs),
42726
+ maxZ: Math.max(...zs)
42727
+ };
42728
+ }, [tiles]);
42729
+ const cameraTarget = useMemo(() => {
42730
+ return [
42731
+ (gridBounds.minX + gridBounds.maxX) / 2,
42732
+ 0,
42733
+ (gridBounds.minZ + gridBounds.maxZ) / 2
42734
+ ];
42735
+ }, [gridBounds]);
42736
+ const gridConfig = useMemo(
42737
+ () => ({
42738
+ ...DEFAULT_GRID_CONFIG,
42739
+ offsetX: -(gridBounds.maxX - gridBounds.minX) / 2,
42740
+ offsetZ: -(gridBounds.maxZ - gridBounds.minZ) / 2
42741
+ }),
42742
+ [gridBounds]
42743
+ );
42744
+ const gridToWorld = useCallback(
42745
+ (x, z, y = 0) => {
42746
+ const worldX = (x - gridBounds.minX) * gridConfig.cellSize;
42747
+ const worldZ = (z - gridBounds.minZ) * gridConfig.cellSize;
42748
+ return [worldX, y * gridConfig.cellSize, worldZ];
42749
+ },
42750
+ [gridBounds, gridConfig]
42751
+ );
42752
+ useImperativeHandle(ref, () => ({
42753
+ getCameraPosition: () => {
42754
+ if (controlsRef.current) {
42755
+ const pos = controlsRef.current.object.position;
42756
+ return new THREE.Vector3(pos.x, pos.y, pos.z);
42757
+ }
42758
+ return null;
42759
+ },
42760
+ setCameraPosition: (x, y, z) => {
42761
+ if (controlsRef.current) {
42762
+ controlsRef.current.object.position.set(x, y, z);
42763
+ controlsRef.current.update();
42764
+ }
42765
+ },
42766
+ lookAt: (x, y, z) => {
42767
+ if (controlsRef.current) {
42768
+ controlsRef.current.target.set(x, y, z);
42769
+ controlsRef.current.update();
42770
+ }
42771
+ },
42772
+ resetCamera: () => {
42773
+ if (controlsRef.current) {
42774
+ controlsRef.current.reset();
42775
+ }
42776
+ },
42777
+ screenshot: () => {
42778
+ const canvas = containerRef.current?.querySelector("canvas");
42779
+ if (canvas) {
42780
+ return canvas.toDataURL("image/png");
42781
+ }
42782
+ return null;
42783
+ },
42784
+ export: () => ({
42785
+ tiles,
42786
+ units,
42787
+ features
42788
+ })
42789
+ }));
42790
+ const handleTileClick = useCallback(
42791
+ (tile, event) => {
42792
+ eventHandlers.handleTileClick(tile, event);
42793
+ },
42794
+ [eventHandlers]
42795
+ );
42796
+ const handleUnitClick = useCallback(
42797
+ (unit, event) => {
42798
+ eventHandlers.handleUnitClick(unit, event);
42799
+ },
42800
+ [eventHandlers]
42801
+ );
42802
+ const handleFeatureClick = useCallback(
42803
+ (feature, event) => {
42804
+ if (event) {
42805
+ eventHandlers.handleFeatureClick(feature, event);
42806
+ }
42807
+ },
42808
+ [eventHandlers]
42809
+ );
42810
+ const handleTileHover = useCallback(
42811
+ (tile, event) => {
42812
+ setHoveredTile(tile);
42813
+ if (event) {
42814
+ eventHandlers.handleTileHover(tile, event);
42815
+ }
42816
+ },
42817
+ [eventHandlers]
42818
+ );
42819
+ const cameraConfig = useMemo(() => {
42820
+ const size = Math.max(
42821
+ gridBounds.maxX - gridBounds.minX,
42822
+ gridBounds.maxZ - gridBounds.minZ
42823
+ );
42824
+ const distance = size * 1.5;
42825
+ switch (cameraMode) {
42826
+ case "isometric":
42827
+ return {
42828
+ position: [distance, distance * 0.8, distance],
42829
+ fov: 45
42830
+ };
42831
+ case "top-down":
42832
+ return {
42833
+ position: [0, distance * 2, 0],
42834
+ fov: 45
42835
+ };
42836
+ case "perspective":
42837
+ default:
42838
+ return {
42839
+ position: [distance, distance, distance],
42840
+ fov: 45
42841
+ };
42842
+ }
42843
+ }, [cameraMode, gridBounds]);
42844
+ const DefaultTileRenderer = useCallback(
42845
+ ({ tile, position }) => {
42846
+ const isSelected = tile.id ? selectedTileIds.includes(tile.id) : false;
42847
+ const isHovered = hoveredTile?.id === tile.id;
42848
+ const isValidMove = validMoves.some(
42849
+ (m) => m.x === tile.x && m.z === (tile.z ?? tile.y ?? 0)
42850
+ );
42851
+ const isAttackTarget = attackTargets.some(
42852
+ (m) => m.x === tile.x && m.z === (tile.z ?? tile.y ?? 0)
42853
+ );
42854
+ let color = 8421504;
42855
+ if (tile.type === "water") color = 4491468;
42856
+ else if (tile.type === "grass") color = 4500036;
42857
+ else if (tile.type === "sand") color = 14535816;
42858
+ else if (tile.type === "rock") color = 8947848;
42859
+ else if (tile.type === "snow") color = 15658734;
42860
+ let emissive = 0;
42861
+ if (isSelected) emissive = 4473924;
42862
+ else if (isAttackTarget) emissive = 4456448;
42863
+ else if (isValidMove) emissive = 17408;
42864
+ else if (isHovered) emissive = 2236962;
42865
+ if (tile.modelUrl) {
42866
+ return /* @__PURE__ */ jsx(
42867
+ "group",
42868
+ {
42869
+ position,
42870
+ onClick: (e) => handleTileClick(tile, e),
42871
+ onPointerEnter: (e) => handleTileHover(tile, e),
42872
+ onPointerLeave: (e) => handleTileHover(null, e),
42873
+ userData: { type: "tile", tileId: tile.id, gridX: tile.x, gridZ: tile.z ?? tile.y },
42874
+ children: /* @__PURE__ */ jsx(
42875
+ ModelLoader,
42876
+ {
42877
+ url: tile.modelUrl,
42878
+ scale: 0.95,
42879
+ fallbackGeometry: "box",
42880
+ castShadow: true,
42881
+ receiveShadow: true
42882
+ }
42883
+ )
42884
+ }
42885
+ );
42886
+ }
42887
+ return /* @__PURE__ */ jsxs(
42888
+ "mesh",
42889
+ {
42890
+ position,
42891
+ onClick: (e) => handleTileClick(tile, e),
42892
+ onPointerEnter: (e) => handleTileHover(tile, e),
42893
+ onPointerLeave: (e) => handleTileHover(null, e),
42894
+ userData: { type: "tile", tileId: tile.id, gridX: tile.x, gridZ: tile.z ?? tile.y },
42895
+ children: [
42896
+ /* @__PURE__ */ jsx("boxGeometry", { args: [0.95, 0.2, 0.95] }),
42897
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color, emissive })
42898
+ ]
42899
+ }
42900
+ );
42901
+ },
42902
+ [selectedTileIds, hoveredTile, validMoves, attackTargets, handleTileClick, handleTileHover]
42903
+ );
42904
+ const DefaultUnitRenderer = useCallback(
42905
+ ({ unit, position }) => {
42906
+ const isSelected = selectedUnitId === unit.id;
42907
+ const color = unit.faction === "player" ? 4491519 : unit.faction === "enemy" ? 16729156 : 16777028;
42908
+ return /* @__PURE__ */ jsxs(
42909
+ "group",
42910
+ {
42911
+ position,
42912
+ onClick: (e) => handleUnitClick(unit, e),
42913
+ userData: { type: "unit", unitId: unit.id },
42914
+ children: [
42915
+ isSelected && /* @__PURE__ */ jsxs("mesh", { position: [0, 0.05, 0], rotation: [-Math.PI / 2, 0, 0], children: [
42916
+ /* @__PURE__ */ jsx("ringGeometry", { args: [0.4, 0.5, 32] }),
42917
+ /* @__PURE__ */ jsx("meshBasicMaterial", { color: "#ffff00", transparent: true, opacity: 0.8 })
42918
+ ] }),
42919
+ unit.modelUrl ? (
42920
+ /* GLB unit model (box fallback while loading / on error) */
42921
+ /* @__PURE__ */ jsx(
42922
+ ModelLoader,
42923
+ {
42924
+ url: unit.modelUrl,
42925
+ scale: 0.5,
42926
+ fallbackGeometry: "box",
42927
+ castShadow: true
42928
+ }
42929
+ )
42930
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
42931
+ /* @__PURE__ */ jsxs("mesh", { position: [0, 0.3, 0], children: [
42932
+ /* @__PURE__ */ jsx("cylinderGeometry", { args: [0.3, 0.3, 0.1, 8] }),
42933
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color })
42934
+ ] }),
42935
+ /* @__PURE__ */ jsxs("mesh", { position: [0, 0.6, 0], children: [
42936
+ /* @__PURE__ */ jsx("capsuleGeometry", { args: [0.2, 0.4, 4, 8] }),
42937
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color })
42938
+ ] }),
42939
+ /* @__PURE__ */ jsxs("mesh", { position: [0, 0.9, 0], children: [
42940
+ /* @__PURE__ */ jsx("sphereGeometry", { args: [0.12, 8, 8] }),
42941
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color })
42942
+ ] })
42943
+ ] }),
42944
+ unit.health !== void 0 && unit.maxHealth !== void 0 && /* @__PURE__ */ jsxs("group", { position: [0, 1.2, 0], children: [
42945
+ /* @__PURE__ */ jsxs("mesh", { position: [-0.25, 0, 0], children: [
42946
+ /* @__PURE__ */ jsx("planeGeometry", { args: [0.5, 0.05] }),
42947
+ /* @__PURE__ */ jsx("meshBasicMaterial", { color: 3355443 })
42948
+ ] }),
42949
+ /* @__PURE__ */ jsxs(
42950
+ "mesh",
42951
+ {
42952
+ position: [
42953
+ -0.25 + 0.5 * (unit.health / unit.maxHealth) / 2,
42954
+ 0,
42955
+ 0.01
42956
+ ],
42957
+ children: [
42958
+ /* @__PURE__ */ jsx("planeGeometry", { args: [0.5 * (unit.health / unit.maxHealth), 0.05] }),
42959
+ /* @__PURE__ */ jsx(
42960
+ "meshBasicMaterial",
42961
+ {
42962
+ color: unit.health / unit.maxHealth > 0.5 ? 4500036 : unit.health / unit.maxHealth > 0.25 ? 11184708 : 16729156
42963
+ }
42964
+ )
42965
+ ]
42966
+ }
42967
+ )
42968
+ ] })
42969
+ ]
42970
+ }
42971
+ );
42972
+ },
42973
+ [selectedUnitId, handleUnitClick]
42974
+ );
42975
+ const DefaultFeatureRenderer = useCallback(
42976
+ ({
42977
+ feature,
42978
+ position
42979
+ }) => {
42980
+ if (feature.assetUrl) {
42981
+ return /* @__PURE__ */ jsx(
42982
+ ModelLoader,
42983
+ {
42984
+ url: feature.assetUrl,
42985
+ position,
42986
+ scale: 0.5,
42987
+ rotation: [0, feature.rotation ?? 0, 0],
42988
+ onClick: () => handleFeatureClick(feature, null),
42989
+ fallbackGeometry: "box"
42990
+ },
42991
+ feature.id
42992
+ );
42993
+ }
42994
+ if (feature.type === "tree") {
42995
+ return /* @__PURE__ */ jsxs(
42996
+ "group",
42997
+ {
42998
+ position,
42999
+ onClick: (e) => handleFeatureClick(feature, e),
43000
+ userData: { type: "feature", featureId: feature.id },
43001
+ children: [
43002
+ /* @__PURE__ */ jsxs("mesh", { position: [0, 0.4, 0], children: [
43003
+ /* @__PURE__ */ jsx("cylinderGeometry", { args: [0.1, 0.15, 0.8, 6] }),
43004
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color: 9127187 })
43005
+ ] }),
43006
+ /* @__PURE__ */ jsxs("mesh", { position: [0, 0.9, 0], children: [
43007
+ /* @__PURE__ */ jsx("coneGeometry", { args: [0.5, 0.8, 8] }),
43008
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color: 2263842 })
43009
+ ] })
43010
+ ]
43011
+ }
43012
+ );
43013
+ }
43014
+ if (feature.type === "rock") {
43015
+ return /* @__PURE__ */ jsxs(
43016
+ "mesh",
43017
+ {
43018
+ position: [position[0], position[1] + 0.3, position[2]],
43019
+ onClick: (e) => handleFeatureClick(feature, e),
43020
+ userData: { type: "feature", featureId: feature.id },
43021
+ children: [
43022
+ /* @__PURE__ */ jsx("dodecahedronGeometry", { args: [0.3, 0] }),
43023
+ /* @__PURE__ */ jsx("meshStandardMaterial", { color: 8421504 })
43024
+ ]
43025
+ }
43026
+ );
43027
+ }
43028
+ return null;
43029
+ },
43030
+ [handleFeatureClick]
43031
+ );
43032
+ if (externalLoading || assetsLoading && preloadAssets.length > 0) {
43033
+ return /* @__PURE__ */ jsx(
43034
+ Canvas3DLoadingState,
43035
+ {
43036
+ progress,
43037
+ loaded,
43038
+ total,
43039
+ message: loadingMessage,
43040
+ className
43041
+ }
43042
+ );
43043
+ }
43044
+ const displayError = externalError || internalError;
43045
+ if (displayError) {
43046
+ 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: [
43047
+ "Error: ",
43048
+ displayError
43049
+ ] }) }) });
43050
+ }
43051
+ return /* @__PURE__ */ jsx(
43052
+ Canvas3DErrorBoundary,
43053
+ {
43054
+ onError: (err) => setInternalError(err.message),
43055
+ onReset: () => setInternalError(null),
43056
+ children: /* @__PURE__ */ jsxs(
43057
+ "div",
43058
+ {
43059
+ ref: containerRef,
43060
+ className: cn("game-canvas-3d relative w-full h-full min-h-[85vh] overflow-hidden", className),
43061
+ "data-orientation": orientation,
43062
+ "data-camera-mode": cameraMode,
43063
+ "data-overlay": overlay,
43064
+ children: [
43065
+ /* @__PURE__ */ jsxs(
43066
+ Canvas,
43067
+ {
43068
+ shadows,
43069
+ camera: {
43070
+ position: cameraConfig.position,
43071
+ fov: cameraConfig.fov,
43072
+ near: 0.1,
43073
+ far: 1e3
43074
+ },
43075
+ style: { background: backgroundColor },
43076
+ onClick: (e) => {
43077
+ if (e.target === e.currentTarget) {
43078
+ eventHandlers.handleCanvasClick(e);
43079
+ }
43080
+ },
43081
+ children: [
43082
+ /* @__PURE__ */ jsx(CameraController, { onCameraChange: eventHandlers.handleCameraChange }),
43083
+ /* @__PURE__ */ jsx("ambientLight", { intensity: 0.6 }),
43084
+ /* @__PURE__ */ jsx(
43085
+ "directionalLight",
43086
+ {
43087
+ position: [10, 20, 10],
43088
+ intensity: 0.8,
43089
+ castShadow: shadows,
43090
+ "shadow-mapSize": [2048, 2048]
43091
+ }
43092
+ ),
43093
+ /* @__PURE__ */ jsx("hemisphereLight", { intensity: 0.3, color: "#87ceeb", groundColor: "#362d1d" }),
43094
+ showGrid && /* @__PURE__ */ jsx(
43095
+ Grid$1,
43096
+ {
43097
+ args: [
43098
+ Math.max(gridBounds.maxX - gridBounds.minX + 2, 10),
43099
+ Math.max(gridBounds.maxZ - gridBounds.minZ + 2, 10)
43100
+ ],
43101
+ position: [
43102
+ (gridBounds.maxX - gridBounds.minX) / 2 - 0.5,
43103
+ 0,
43104
+ (gridBounds.maxZ - gridBounds.minZ) / 2 - 0.5
43105
+ ],
43106
+ cellSize: 1,
43107
+ cellThickness: 1,
43108
+ cellColor: "#444444",
43109
+ sectionSize: 5,
43110
+ sectionThickness: 1.5,
43111
+ sectionColor: "#666666",
43112
+ fadeDistance: 50,
43113
+ fadeStrength: 1
43114
+ }
43115
+ ),
43116
+ tiles.map((tile, index) => {
43117
+ const position = gridToWorld(
43118
+ tile.x,
43119
+ tile.z ?? tile.y ?? 0,
43120
+ tile.elevation ?? 0
43121
+ );
43122
+ const Renderer = CustomTileRenderer || DefaultTileRenderer;
43123
+ return /* @__PURE__ */ jsx(Renderer, { tile, position }, tile.id ?? `tile-${index}`);
43124
+ }),
43125
+ features.map((feature, index) => {
43126
+ const position = gridToWorld(
43127
+ feature.x,
43128
+ feature.z ?? feature.y ?? 0,
43129
+ (feature.elevation ?? 0) + 0.5
43130
+ );
43131
+ const Renderer = CustomFeatureRenderer || DefaultFeatureRenderer;
43132
+ return /* @__PURE__ */ jsx(Renderer, { feature, position }, feature.id ?? `feature-${index}`);
43133
+ }),
43134
+ units.map((unit) => {
43135
+ const position = gridToWorld(
43136
+ unit.x ?? 0,
43137
+ unit.z ?? unit.y ?? 0,
43138
+ (unit.elevation ?? 0) + 0.5
43139
+ );
43140
+ const Renderer = CustomUnitRenderer || DefaultUnitRenderer;
43141
+ return /* @__PURE__ */ jsx(Renderer, { unit, position }, unit.id);
43142
+ }),
43143
+ children,
43144
+ /* @__PURE__ */ jsx(
43145
+ OrbitControls,
43146
+ {
43147
+ ref: controlsRef,
43148
+ target: cameraTarget,
43149
+ enableDamping: true,
43150
+ dampingFactor: 0.05,
43151
+ minDistance: 2,
43152
+ maxDistance: 100,
43153
+ maxPolarAngle: Math.PI / 2 - 0.1
43154
+ }
43155
+ )
43156
+ ]
43157
+ }
43158
+ ),
43159
+ showCoordinates && hoveredTile && /* @__PURE__ */ jsxs("div", { className: "game-canvas-3d__coordinates", children: [
43160
+ "X: ",
43161
+ hoveredTile.x,
43162
+ ", Z: ",
43163
+ hoveredTile.z ?? hoveredTile.y ?? 0
43164
+ ] }),
43165
+ showTileInfo && hoveredTile && /* @__PURE__ */ jsxs("div", { className: "game-canvas-3d__tile-info", children: [
43166
+ /* @__PURE__ */ jsx("div", { className: "tile-info__type", children: hoveredTile.type }),
43167
+ hoveredTile.terrain && /* @__PURE__ */ jsx("div", { className: "tile-info__terrain", children: hoveredTile.terrain })
43168
+ ] })
43169
+ ]
43170
+ }
43171
+ )
43172
+ }
43173
+ );
43174
+ }
43175
+ );
43176
+ GameCanvas3D.displayName = "GameCanvas3D";
43177
+ }
43178
+ });
43179
+ function GameBoard3D({
43180
+ entity,
43181
+ tiles = [],
43182
+ features = [],
43183
+ cameraMode = "perspective",
43184
+ backgroundColor = "#2a1a1a",
43185
+ tileClickEvent,
43186
+ unitClickEvent,
43187
+ attackEvent,
43188
+ endTurnEvent,
43189
+ cancelEvent,
43190
+ playAgainEvent,
43191
+ gameEndEvent,
43192
+ className
43193
+ }) {
43194
+ const row = boardEntity(entity);
43195
+ const eventBus = useEventBus();
43196
+ const entityUnits = row ? rows(row.units) : [];
43197
+ const units = entityUnits;
43198
+ const selectedUnitId = row ? str(row.selectedUnitId) || null : null;
43199
+ const phase = row ? str(row.phase) : "observation";
43200
+ const result = row ? str(row.result) : "none";
43201
+ const validMoves = row && Array.isArray(row.validMoves) ? row.validMoves : [];
43202
+ const attackTargets = row && Array.isArray(row.attackTargets) ? row.attackTargets : [];
43203
+ const turn = row ? num(row.turn) : 0;
43204
+ const currentTeam = row ? str(row.currentTeam) : "player";
43205
+ const isGameOver = result !== "none";
43206
+ const gameEndEmittedRef = useRef(false);
43207
+ useEffect(() => {
43208
+ if ((result === "victory" || result === "defeat") && gameEndEvent) {
43209
+ if (!gameEndEmittedRef.current) {
43210
+ gameEndEmittedRef.current = true;
43211
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
43212
+ }
43213
+ } else {
43214
+ gameEndEmittedRef.current = false;
43215
+ }
43216
+ }, [result, gameEndEvent, eventBus]);
43217
+ const checkGameEnd = useCallback(() => {
43218
+ const alivePlayer = entityUnits.filter((u) => unitTeam(u) === "player" && unitHealth(u) > 0);
43219
+ const aliveEnemy = entityUnits.filter((u) => unitTeam(u) === "enemy" && unitHealth(u) > 0);
43220
+ if (alivePlayer.length === 0 && gameEndEvent) {
43221
+ eventBus.emit(`UI:${gameEndEvent}`, { result: "defeat" });
43222
+ } else if (aliveEnemy.length === 0 && gameEndEvent) {
43223
+ eventBus.emit(`UI:${gameEndEvent}`, { result: "victory" });
43224
+ }
43225
+ }, [entityUnits, gameEndEvent, eventBus]);
43226
+ const handleUnitClickCallback = useCallback((isoUnit) => {
43227
+ if (phase !== "action" || !selectedUnitId || !attackEvent) return;
43228
+ const attackerRow = entityUnits.find((u) => str(u.id) === selectedUnitId);
43229
+ const targetRow = entityUnits.find((u) => str(u.id) === isoUnit.id);
43230
+ if (!attackerRow || !targetRow) return;
43231
+ if (unitTeam(targetRow) !== "enemy" || unitHealth(targetRow) <= 0) return;
43232
+ const ap = unitPosition(attackerRow);
43233
+ const tp = unitPosition(targetRow);
43234
+ const dx = Math.abs(ap.x - tp.x);
43235
+ const dy = Math.abs(ap.y - tp.y);
43236
+ if (dx <= 1 && dy <= 1 && dx + dy > 0) {
43237
+ const damage = Math.max(1, num(attackerRow.attack) - num(targetRow.defense));
43238
+ eventBus.emit(`UI:${attackEvent}`, {
43239
+ attackerId: str(attackerRow.id),
43240
+ targetId: str(targetRow.id),
43241
+ damage
43242
+ });
43243
+ setTimeout(checkGameEnd, 100);
43244
+ }
43245
+ }, [phase, selectedUnitId, entityUnits, attackEvent, eventBus, checkGameEnd]);
43246
+ const handleEndTurn = useCallback(() => {
43247
+ if (endTurnEvent) eventBus.emit(`UI:${endTurnEvent}`, {});
43248
+ }, [endTurnEvent, eventBus]);
43249
+ const handleCancel = useCallback(() => {
43250
+ if (cancelEvent) eventBus.emit(`UI:${cancelEvent}`, {});
43251
+ }, [cancelEvent, eventBus]);
43252
+ const handlePlayAgain = useCallback(() => {
43253
+ if (playAgainEvent) eventBus.emit(`UI:${playAgainEvent}`, {});
43254
+ }, [playAgainEvent, eventBus]);
43255
+ return /* @__PURE__ */ jsxs(VStack, { className: cn("game-board-3d block w-full min-h-[85vh] relative", className), children: [
43256
+ /* @__PURE__ */ jsxs("div", { className: "game-board-3d__status absolute top-3 left-3 z-10 flex gap-2 items-center", children: [
43257
+ /* @__PURE__ */ jsx(Typography, { variant: "small", className: "status__phase capitalize", children: phase.replace("_", " ") }),
43258
+ currentTeam && !isGameOver && /* @__PURE__ */ jsxs(Typography, { variant: "small", color: "muted", className: "status__team", children: [
43259
+ "\u2014 ",
43260
+ currentTeam === "player" ? "Your Turn" : "Enemy's Turn"
43261
+ ] }),
43262
+ /* @__PURE__ */ jsxs(Typography, { variant: "small", color: "muted", className: "status__turn", children: [
43263
+ "Turn ",
43264
+ turn
43265
+ ] })
43266
+ ] }),
43267
+ /* @__PURE__ */ jsx(
43268
+ GameCanvas3D,
43269
+ {
43270
+ tiles,
43271
+ units,
43272
+ features,
43273
+ cameraMode,
43274
+ showGrid: true,
43275
+ showCoordinates: false,
43276
+ showTileInfo: false,
43277
+ shadows: true,
43278
+ backgroundColor,
43279
+ tileClickEvent,
43280
+ unitClickEvent,
43281
+ onUnitClick: handleUnitClickCallback,
43282
+ selectedUnitId,
43283
+ validMoves,
43284
+ attackTargets,
43285
+ className: "game-board-3d__canvas w-full min-h-[85vh]"
43286
+ }
43287
+ ),
43288
+ !isGameOver && /* @__PURE__ */ jsxs(HStack, { className: "fixed bottom-6 right-6 z-50", gap: "sm", children: [
43289
+ (phase === "selection" || phase === "movement" || phase === "action") && /* @__PURE__ */ jsx(
43290
+ Button,
43291
+ {
43292
+ variant: "secondary",
43293
+ className: "shadow-xl",
43294
+ onClick: handleCancel,
43295
+ children: "Cancel"
43296
+ }
43297
+ ),
43298
+ phase !== "enemy_turn" && /* @__PURE__ */ jsx(
43299
+ Button,
43300
+ {
43301
+ variant: "primary",
43302
+ className: "shadow-xl",
43303
+ onClick: handleEndTurn,
43304
+ children: "End Turn"
43305
+ }
43306
+ )
43307
+ ] }),
43308
+ 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: [
43309
+ /* @__PURE__ */ jsx(
43310
+ Typography,
43311
+ {
43312
+ variant: "h2",
43313
+ className: cn(
43314
+ "overlay__result",
43315
+ result === "victory" ? "text-yellow-400" : "text-red-500"
43316
+ ),
43317
+ children: result === "victory" ? "Victory!" : "Defeat"
43318
+ }
43319
+ ),
43320
+ /* @__PURE__ */ jsxs(Typography, { variant: "body", color: "muted", className: "overlay__turn-count", children: [
43321
+ "Completed in ",
43322
+ turn,
43323
+ " turn",
43324
+ turn !== 1 ? "s" : ""
43325
+ ] }),
43326
+ /* @__PURE__ */ jsx(
43327
+ Button,
43328
+ {
43329
+ variant: "primary",
43330
+ className: "px-8 py-3 font-semibold",
43331
+ onClick: handlePlayAgain,
43332
+ children: "Play Again"
43333
+ }
43334
+ )
43335
+ ] }) })
43336
+ ] });
43337
+ }
43338
+ var init_GameBoard3D = __esm({
43339
+ "components/game/organisms/GameBoard3D.tsx"() {
43340
+ "use client";
43341
+ init_cn();
43342
+ init_Button();
43343
+ init_Typography();
43344
+ init_Stack();
43345
+ init_useEventBus();
43346
+ init_GameCanvas3D2();
43347
+ init_boardEntity();
43348
+ GameBoard3D.displayName = "GameBoard3D";
43349
+ }
43350
+ });
41672
43351
  var GameShell;
41673
43352
  var init_GameShell = __esm({
41674
43353
  "components/game/templates/GameShell.tsx"() {
@@ -43145,21 +44824,6 @@ var init_ModalSlot = __esm({
43145
44824
  ModalSlot.displayName = "ModalSlot";
43146
44825
  }
43147
44826
  });
43148
- function getOpponentAction(strategy, actions, history) {
43149
- const actionIds = actions.map((a) => a.id);
43150
- switch (strategy) {
43151
- case "always-cooperate":
43152
- return actionIds[0];
43153
- case "always-defect":
43154
- return actionIds[actionIds.length - 1];
43155
- case "tit-for-tat":
43156
- if (history.length === 0) return actionIds[0];
43157
- return history[history.length - 1].playerAction;
43158
- case "random":
43159
- default:
43160
- return actionIds[Math.floor(Math.random() * actionIds.length)];
43161
- }
43162
- }
43163
44827
  function NegotiatorBoard({
43164
44828
  entity,
43165
44829
  completeEvent = "PUZZLE_COMPLETE",
@@ -43181,28 +44845,35 @@ function NegotiatorBoard({
43181
44845
  const playerTotal = num(resolved?.score);
43182
44846
  const isComplete = result !== "none" || totalRounds > 0 && currentRound >= totalRounds;
43183
44847
  const won = result === "win";
43184
- const opponentTotal = history.reduce((s, r) => s + r.opponentPayoff, 0);
44848
+ const lastPlayerAction = str(resolved?.lastPlayerAction);
44849
+ const lastOpponentAction = str(resolved?.lastOpponentAction);
44850
+ const lastPayoff = num(resolved?.lastPayoff);
43185
44851
  const actions = Array.isArray(resolved?.actions) ? resolved.actions : [];
43186
44852
  const payoffMatrix = Array.isArray(resolved?.payoffMatrix) ? resolved.payoffMatrix : [];
44853
+ const prevRoundRef = useRef(currentRound);
44854
+ useEffect(() => {
44855
+ if (currentRound > prevRoundRef.current && lastPlayerAction) {
44856
+ const opponentPayoffEntry = payoffMatrix.find(
44857
+ (p2) => p2.playerAction === lastPlayerAction && p2.opponentAction === lastOpponentAction
44858
+ );
44859
+ setHistory((prev) => [
44860
+ ...prev,
44861
+ {
44862
+ round: currentRound,
44863
+ playerAction: lastPlayerAction,
44864
+ opponentAction: lastOpponentAction,
44865
+ playerPayoff: lastPayoff,
44866
+ opponentPayoff: opponentPayoffEntry?.opponentPayoff ?? 0
44867
+ }
44868
+ ]);
44869
+ }
44870
+ prevRoundRef.current = currentRound;
44871
+ }, [currentRound, lastPlayerAction, lastOpponentAction, lastPayoff, payoffMatrix]);
44872
+ const opponentTotal = useMemo(() => history.reduce((s, r) => s + r.opponentPayoff, 0), [history]);
43187
44873
  const handleAction = useCallback((actionId) => {
43188
44874
  if (isComplete) return;
43189
- const opponentAction = getOpponentAction(str(resolved?.opponentStrategy) || "random", actions, history);
43190
- const payoff = payoffMatrix.find(
43191
- (p2) => p2.playerAction === actionId && p2.opponentAction === opponentAction
43192
- );
43193
- const playerPayoff = payoff?.playerPayoff ?? 0;
43194
- setHistory((prev) => [
43195
- ...prev,
43196
- {
43197
- round: prev.length + 1,
43198
- playerAction: actionId,
43199
- opponentAction,
43200
- playerPayoff,
43201
- opponentPayoff: payoff?.opponentPayoff ?? 0
43202
- }
43203
- ]);
43204
44875
  if (playRoundEvent) {
43205
- emit(`UI:${playRoundEvent}`, { playerAction: actionId, payoff: playerPayoff });
44876
+ emit(`UI:${playRoundEvent}`, { playerAction: actionId, payoff: 0 });
43206
44877
  }
43207
44878
  if (totalRounds > 0 && currentRound + 1 >= totalRounds) {
43208
44879
  if (finishEvent) {
@@ -43212,10 +44883,11 @@ function NegotiatorBoard({
43212
44883
  setShowHint(true);
43213
44884
  }
43214
44885
  }
43215
- }, [isComplete, resolved, totalRounds, currentRound, actions, payoffMatrix, history, playRoundEvent, finishEvent, emit]);
44886
+ }, [isComplete, resolved, totalRounds, currentRound, playRoundEvent, finishEvent, emit]);
43216
44887
  const handleReset = useCallback(() => {
43217
44888
  setHistory([]);
43218
44889
  setShowHint(false);
44890
+ prevRoundRef.current = 0;
43219
44891
  if (playAgainEvent) {
43220
44892
  emit(`UI:${playAgainEvent}`, {});
43221
44893
  }
@@ -43326,6 +44998,236 @@ var init_NegotiatorBoard = __esm({
43326
44998
  NegotiatorBoard.displayName = "NegotiatorBoard";
43327
44999
  }
43328
45000
  });
45001
+ function resolveEntity(entity) {
45002
+ if (!entity) return {};
45003
+ if (Array.isArray(entity)) return entity[0] ?? {};
45004
+ return entity;
45005
+ }
45006
+ function numField(row, key, fallback) {
45007
+ const v = row[key];
45008
+ return typeof v === "number" ? v : fallback;
45009
+ }
45010
+ function strField(row, key, fallback) {
45011
+ const v = row[key];
45012
+ return typeof v === "string" ? v : fallback;
45013
+ }
45014
+ function PlatformerBoard({
45015
+ entity,
45016
+ player: propPlayer,
45017
+ platforms: propPlatforms,
45018
+ result: propResult,
45019
+ lives: propLives,
45020
+ score: propScore,
45021
+ level: propLevel,
45022
+ worldWidth = 800,
45023
+ worldHeight = 400,
45024
+ canvasWidth = 800,
45025
+ canvasHeight = 400,
45026
+ playerSprite,
45027
+ tileSprites,
45028
+ bgColor,
45029
+ leftEvent = "LEFT",
45030
+ rightEvent = "RIGHT",
45031
+ jumpEvent = "JUMP",
45032
+ stopEvent = "STOP",
45033
+ playAgainEvent = "PLAY_AGAIN",
45034
+ gameEndEvent = "GAME_END",
45035
+ className
45036
+ }) {
45037
+ const row = resolveEntity(entity);
45038
+ const eventBus = useEventBus();
45039
+ const { t } = useTranslate();
45040
+ const result = propResult ?? strField(row, "result", "none");
45041
+ propLives ?? numField(row, "lives", 3);
45042
+ const score = propScore ?? numField(row, "score", 0);
45043
+ const level = propLevel ?? numField(row, "level", 1);
45044
+ const entityPlayer = row["player"];
45045
+ const player = propPlayer ?? entityPlayer ?? {
45046
+ x: 80,
45047
+ y: 320,
45048
+ width: 32,
45049
+ height: 48,
45050
+ vx: 0,
45051
+ vy: 0,
45052
+ grounded: false,
45053
+ facingRight: true
45054
+ };
45055
+ const entityPlatforms = Array.isArray(row["platforms"]) ? row["platforms"] : void 0;
45056
+ const platforms = propPlatforms ?? entityPlatforms ?? [
45057
+ { x: 0, y: 368, width: 800, height: 32, type: "ground" },
45058
+ { x: 150, y: 280, width: 160, height: 16, type: "platform" },
45059
+ { x: 420, y: 220, width: 160, height: 16, type: "platform" },
45060
+ { x: 580, y: 300, width: 80, height: 16, type: "hazard" },
45061
+ { x: 700, y: 340, width: 64, height: 28, type: "goal" }
45062
+ ];
45063
+ const handleRestart = useCallback(() => {
45064
+ if (playAgainEvent) {
45065
+ eventBus.emit(`UI:${playAgainEvent}`, {});
45066
+ }
45067
+ }, [playAgainEvent, eventBus]);
45068
+ const isGameOver = result === "won" || result === "lost";
45069
+ const gameEndEmittedRef = useRef(false);
45070
+ useEffect(() => {
45071
+ if ((result === "won" || result === "lost") && gameEndEvent) {
45072
+ if (!gameEndEmittedRef.current) {
45073
+ gameEndEmittedRef.current = true;
45074
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
45075
+ }
45076
+ } else {
45077
+ gameEndEmittedRef.current = false;
45078
+ }
45079
+ }, [result, gameEndEvent, eventBus]);
45080
+ return /* @__PURE__ */ jsx(
45081
+ VStack,
45082
+ {
45083
+ className: cn("platformer-board relative bg-background", className),
45084
+ gap: "none",
45085
+ children: /* @__PURE__ */ jsxs(Box, { className: "relative", children: [
45086
+ /* @__PURE__ */ jsx(
45087
+ PlatformerCanvas,
45088
+ {
45089
+ player,
45090
+ platforms,
45091
+ worldWidth,
45092
+ worldHeight,
45093
+ canvasWidth,
45094
+ canvasHeight,
45095
+ followCamera: true,
45096
+ bgColor,
45097
+ playerSprite,
45098
+ tileSprites,
45099
+ leftEvent,
45100
+ rightEvent,
45101
+ jumpEvent,
45102
+ stopEvent
45103
+ }
45104
+ ),
45105
+ 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: [
45106
+ /* @__PURE__ */ jsx(
45107
+ Typography,
45108
+ {
45109
+ variant: "h2",
45110
+ className: cn(
45111
+ "text-4xl font-black tracking-widest uppercase",
45112
+ result === "won" ? "text-warning" : "text-error"
45113
+ ),
45114
+ children: result === "won" ? t("platformer.won") : t("platformer.lost")
45115
+ }
45116
+ ),
45117
+ result === "won" && /* @__PURE__ */ jsxs(Typography, { variant: "body1", className: "text-muted-foreground", children: [
45118
+ t("platformer.level"),
45119
+ " ",
45120
+ level,
45121
+ " \u2014 ",
45122
+ t("platformer.score"),
45123
+ ": ",
45124
+ score
45125
+ ] }),
45126
+ result === "lost" && /* @__PURE__ */ jsx(Typography, { variant: "body1", className: "text-muted-foreground", children: t("platformer.noLives") }),
45127
+ /* @__PURE__ */ jsx(
45128
+ Button,
45129
+ {
45130
+ variant: "primary",
45131
+ className: "px-8 py-3 font-semibold",
45132
+ onClick: handleRestart,
45133
+ children: result === "won" ? t("platformer.nextLevel") : t("platformer.playAgain")
45134
+ }
45135
+ )
45136
+ ] }) })
45137
+ ] })
45138
+ }
45139
+ );
45140
+ }
45141
+ var init_PlatformerBoard = __esm({
45142
+ "components/game/organisms/PlatformerBoard.tsx"() {
45143
+ "use client";
45144
+ init_cn();
45145
+ init_useEventBus();
45146
+ init_Box();
45147
+ init_Button();
45148
+ init_Typography();
45149
+ init_Stack();
45150
+ init_PlatformerCanvas();
45151
+ PlatformerBoard.displayName = "PlatformerBoard";
45152
+ }
45153
+ });
45154
+ function PlatformerTemplate({
45155
+ entity,
45156
+ platforms = DEFAULT_PLATFORMS,
45157
+ worldWidth = 800,
45158
+ worldHeight = 400,
45159
+ canvasWidth = 800,
45160
+ canvasHeight = 400,
45161
+ playerSprite = `${CDN3}/characters/platformChar_idle.png`,
45162
+ tileSprites = DEFAULT_TILE_SPRITES,
45163
+ bgColor = "#5c94fc",
45164
+ gameEndEvent = "GAME_END",
45165
+ className
45166
+ }) {
45167
+ const row = entity && !Array.isArray(entity) ? entity : void 0;
45168
+ const lives = typeof row?.["lives"] === "number" ? row["lives"] : 3;
45169
+ const score = typeof row?.["score"] === "number" ? row["score"] : 0;
45170
+ const level = typeof row?.["level"] === "number" ? row["level"] : 1;
45171
+ return /* @__PURE__ */ jsxs(VStack, { className: cn("platformer-template w-full", className), gap: "none", children: [
45172
+ /* @__PURE__ */ jsx(Box, { className: "relative", children: /* @__PURE__ */ jsx(
45173
+ GameHud,
45174
+ {
45175
+ position: "top",
45176
+ stats: [
45177
+ { label: "Lives", value: lives, format: "number", variant: "danger" },
45178
+ { label: "Level", value: level, format: "number", variant: "default" }
45179
+ ]
45180
+ }
45181
+ ) }),
45182
+ /* @__PURE__ */ jsx(
45183
+ PlatformerBoard,
45184
+ {
45185
+ entity,
45186
+ platforms,
45187
+ worldWidth,
45188
+ worldHeight,
45189
+ canvasWidth,
45190
+ canvasHeight,
45191
+ playerSprite,
45192
+ tileSprites,
45193
+ bgColor,
45194
+ leftEvent: "LEFT",
45195
+ rightEvent: "RIGHT",
45196
+ jumpEvent: "JUMP",
45197
+ stopEvent: "STOP",
45198
+ playAgainEvent: "PLAY_AGAIN",
45199
+ gameEndEvent
45200
+ }
45201
+ ),
45202
+ /* @__PURE__ */ jsx(Box, { className: "px-4 py-2", children: /* @__PURE__ */ jsx(ScoreBoard, { score, level }) })
45203
+ ] });
45204
+ }
45205
+ var CDN3, DEFAULT_TILE_SPRITES, DEFAULT_PLATFORMS;
45206
+ var init_PlatformerTemplate = __esm({
45207
+ "components/game/templates/PlatformerTemplate.tsx"() {
45208
+ init_cn();
45209
+ init_Box();
45210
+ init_Stack();
45211
+ init_PlatformerBoard();
45212
+ init_GameHud();
45213
+ init_ScoreBoard();
45214
+ CDN3 = "https://almadar-kflow-assets.web.app/shared/platformer";
45215
+ DEFAULT_TILE_SPRITES = {
45216
+ ground: `${CDN3}/tiles/platformPack_tile001.png`,
45217
+ platform: `${CDN3}/tiles/platformPack_tile004.png`,
45218
+ hazard: `${CDN3}/tiles/platformPack_tile017.png`,
45219
+ goal: `${CDN3}/tiles/platformPack_tile007.png`
45220
+ };
45221
+ DEFAULT_PLATFORMS = [
45222
+ { x: 0, y: 368, width: 800, height: 32, type: "ground" },
45223
+ { x: 150, y: 280, width: 160, height: 16, type: "platform" },
45224
+ { x: 420, y: 220, width: 160, height: 16, type: "platform" },
45225
+ { x: 580, y: 300, width: 80, height: 16, type: "hazard" },
45226
+ { x: 700, y: 340, width: 64, height: 28, type: "goal" }
45227
+ ];
45228
+ PlatformerTemplate.displayName = "PlatformerTemplate";
45229
+ }
45230
+ });
43329
45231
  var PricingOrganism;
43330
45232
  var init_PricingOrganism = __esm({
43331
45233
  "components/marketing/organisms/PricingOrganism.tsx"() {
@@ -43449,6 +45351,7 @@ var init_PricingPageTemplate = __esm({
43449
45351
  }
43450
45352
  });
43451
45353
  function ResourceCounter({
45354
+ assetUrl = DEFAULT_ASSET_URL12,
43452
45355
  icon,
43453
45356
  label = "Gold",
43454
45357
  value = 250,
@@ -43468,7 +45371,17 @@ function ResourceCounter({
43468
45371
  className
43469
45372
  ),
43470
45373
  children: [
43471
- icon && /* @__PURE__ */ jsx("span", { className: cn("flex-shrink-0", sizes.icon), children: typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon }) : /* @__PURE__ */ jsx(Icon, { icon }) }),
45374
+ assetUrl ? /* @__PURE__ */ jsx(
45375
+ "img",
45376
+ {
45377
+ src: assetUrl,
45378
+ alt: label,
45379
+ width: sizes.img,
45380
+ height: sizes.img,
45381
+ style: { imageRendering: "pixelated", objectFit: "contain" },
45382
+ className: "flex-shrink-0"
45383
+ }
45384
+ ) : 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,
43472
45385
  /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: label }),
43473
45386
  /* @__PURE__ */ jsxs("span", { className: cn("font-bold tabular-nums", color && (color in colorTokenClasses2 ? colorTokenClasses2[color] : color)), children: [
43474
45387
  value,
@@ -43481,7 +45394,7 @@ function ResourceCounter({
43481
45394
  }
43482
45395
  );
43483
45396
  }
43484
- var colorTokenClasses2, sizeMap15;
45397
+ var colorTokenClasses2, DEFAULT_ASSET_URL12, sizeMap15;
43485
45398
  var init_ResourceCounter = __esm({
43486
45399
  "components/game/atoms/ResourceCounter.tsx"() {
43487
45400
  init_cn();
@@ -43494,14 +45407,365 @@ var init_ResourceCounter = __esm({
43494
45407
  error: "text-error",
43495
45408
  muted: "text-muted-foreground"
43496
45409
  };
45410
+ DEFAULT_ASSET_URL12 = "https://almadar-kflow-assets.web.app/shared/world-map/gold_mine.png";
43497
45411
  sizeMap15 = {
43498
- sm: { wrapper: "text-xs gap-1 px-1.5 py-0.5", icon: "text-sm" },
43499
- md: { wrapper: "text-sm gap-1.5 px-2 py-1", icon: "text-base" },
43500
- lg: { wrapper: "text-base gap-2 px-3 py-1.5", icon: "text-lg" }
45412
+ sm: { wrapper: "text-xs gap-1 px-1.5 py-0.5", icon: "text-sm", img: 16 },
45413
+ md: { wrapper: "text-sm gap-1.5 px-2 py-1", icon: "text-base", img: 20 },
45414
+ lg: { wrapper: "text-base gap-2 px-3 py-1.5", icon: "text-lg", img: 28 }
43501
45415
  };
43502
45416
  ResourceCounter.displayName = "ResourceCounter";
43503
45417
  }
43504
45418
  });
45419
+ function RoguelikeBoard({
45420
+ entity,
45421
+ tiles: propTiles,
45422
+ enemies: propEnemies,
45423
+ items: propItems,
45424
+ player: propPlayer,
45425
+ playerHp: propPlayerHp,
45426
+ playerMaxHp: propPlayerMaxHp,
45427
+ playerAttack: propPlayerAttack,
45428
+ depth: propDepth,
45429
+ result: propResult,
45430
+ phase: propPhase,
45431
+ stairsX: propStairsX,
45432
+ stairsY: propStairsY,
45433
+ assetManifest: propAssetManifest,
45434
+ scale = 0.45,
45435
+ unitScale = 1,
45436
+ moveEvent,
45437
+ playAgainEvent,
45438
+ gameEndEvent,
45439
+ className
45440
+ }) {
45441
+ const board = boardEntity(entity) ?? {};
45442
+ const tiles = propTiles ?? (Array.isArray(board.tiles) ? board.tiles : DEFAULT_TILES2);
45443
+ const enemies = propEnemies ?? (Array.isArray(board.enemies) ? board.enemies : DEFAULT_ENEMIES);
45444
+ const items = propItems ?? (Array.isArray(board.items) ? board.items : DEFAULT_ITEMS);
45445
+ const player = propPlayer ?? board.player ?? { x: 1, y: 1 };
45446
+ const playerHp = propPlayerHp ?? num(board.playerHp, 10);
45447
+ const playerMaxHp = propPlayerMaxHp ?? num(board.playerMaxHp, 10);
45448
+ const playerAttack = propPlayerAttack ?? num(board.playerAttack, 3);
45449
+ const depth = propDepth ?? num(board.depth, 1);
45450
+ const result = propResult ?? (str(board.result) || "none");
45451
+ const phase = propPhase ?? (str(board.phase) || "player_turn");
45452
+ propStairsX ?? num(board.stairsX, 3);
45453
+ propStairsY ?? num(board.stairsY, 3);
45454
+ const eventBus = useEventBus();
45455
+ const { t } = useTranslate();
45456
+ const [hoveredTile, setHoveredTile] = useState(null);
45457
+ const emitMove = useCallback((dx, dy) => {
45458
+ if (!moveEvent) return;
45459
+ eventBus.emit(`UI:${moveEvent}`, { dx, dy });
45460
+ }, [moveEvent, eventBus]);
45461
+ useEffect(() => {
45462
+ const handler = (e) => {
45463
+ if (result !== "none" || phase !== "player_turn") return;
45464
+ if (e.key === "ArrowUp" || e.key === "w") emitMove(0, -1);
45465
+ if (e.key === "ArrowDown" || e.key === "s") emitMove(0, 1);
45466
+ if (e.key === "ArrowLeft" || e.key === "a") emitMove(-1, 0);
45467
+ if (e.key === "ArrowRight" || e.key === "d") emitMove(1, 0);
45468
+ };
45469
+ window.addEventListener("keydown", handler);
45470
+ return () => window.removeEventListener("keydown", handler);
45471
+ }, [result, phase, emitMove]);
45472
+ useEffect(() => {
45473
+ if ((result === "won" || result === "lost") && gameEndEvent) {
45474
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
45475
+ }
45476
+ }, [result, gameEndEvent, eventBus]);
45477
+ const handleTileClick = useCallback((x, y) => {
45478
+ if (result !== "none" || phase !== "player_turn") return;
45479
+ const dx = x - player.x;
45480
+ const dy = y - player.y;
45481
+ if (Math.abs(dx) + Math.abs(dy) === 1) {
45482
+ emitMove(dx, dy);
45483
+ }
45484
+ }, [result, phase, player, emitMove]);
45485
+ const handleReset = useCallback(() => {
45486
+ if (!playAgainEvent) return;
45487
+ eventBus.emit(`UI:${playAgainEvent}`, {});
45488
+ }, [playAgainEvent, eventBus]);
45489
+ const isoUnits = useMemo(() => {
45490
+ const liveEnemies = enemies.filter((e) => e.hp > 0);
45491
+ return [
45492
+ {
45493
+ id: "player",
45494
+ position: player,
45495
+ name: t("roguelike.player") || "Hero",
45496
+ team: "player",
45497
+ health: playerHp,
45498
+ maxHealth: playerMaxHp,
45499
+ unitType: "player",
45500
+ sprite: `${CDN4}/isometric-dungeon/Characters/Male/Male_0_Idle0.png`
45501
+ },
45502
+ ...liveEnemies.map((e) => ({
45503
+ id: e.id,
45504
+ position: { x: e.x, y: e.y },
45505
+ name: t("roguelike.enemy") || "Enemy",
45506
+ team: "enemy",
45507
+ health: e.hp,
45508
+ maxHealth: 6,
45509
+ unitType: "enemy",
45510
+ sprite: `${CDN4}/sprite-sheets/shadow-legion-sprite-sheet-sw.png`
45511
+ }))
45512
+ ];
45513
+ }, [player, enemies, playerHp, playerMaxHp, t]);
45514
+ const isoFeatures = useMemo(
45515
+ () => items.map((it) => ({
45516
+ id: it.id,
45517
+ x: it.x,
45518
+ y: it.y,
45519
+ type: it.kind,
45520
+ sprite: FEATURE_SPRITE[it.kind] ?? FEATURE_SPRITE.health_potion
45521
+ })),
45522
+ [items]
45523
+ );
45524
+ const validMoves = useMemo(() => {
45525
+ if (result !== "none" || phase !== "player_turn") return [];
45526
+ const dirs = [{ dx: 0, dy: -1 }, { dx: 0, dy: 1 }, { dx: -1, dy: 0 }, { dx: 1, dy: 0 }];
45527
+ return dirs.map(({ dx, dy }) => ({ x: player.x + dx, y: player.y + dy })).filter(({ x, y }) => {
45528
+ const tile = tiles.find((t2) => t2.x === x && t2.y === y);
45529
+ return tile?.passable ?? false;
45530
+ });
45531
+ }, [result, phase, player, tiles]);
45532
+ const isGameOver = result === "won" || result === "lost";
45533
+ return /* @__PURE__ */ jsxs(VStack, { className: cn("roguelike-board relative min-h-[600px] bg-background", className), gap: "none", children: [
45534
+ /* @__PURE__ */ jsxs(HStack, { className: "items-center justify-between px-4 py-2 border-b border-border", gap: "md", children: [
45535
+ /* @__PURE__ */ jsxs(HStack, { gap: "sm", className: "items-center", children: [
45536
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: t("roguelike.depth") || "Depth" }),
45537
+ /* @__PURE__ */ jsx(Typography, { variant: "body1", className: "font-bold tabular-nums", children: depth })
45538
+ ] }),
45539
+ /* @__PURE__ */ jsxs(HStack, { gap: "sm", className: "items-center", children: [
45540
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: t("roguelike.hp") || "HP" }),
45541
+ /* @__PURE__ */ jsxs(
45542
+ Typography,
45543
+ {
45544
+ variant: "body1",
45545
+ className: cn(
45546
+ "font-bold tabular-nums",
45547
+ playerHp <= Math.floor(playerMaxHp * 0.25) ? "text-error" : "text-success"
45548
+ ),
45549
+ children: [
45550
+ playerHp,
45551
+ " / ",
45552
+ playerMaxHp
45553
+ ]
45554
+ }
45555
+ )
45556
+ ] }),
45557
+ /* @__PURE__ */ jsxs(HStack, { gap: "sm", className: "items-center", children: [
45558
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: t("roguelike.atk") || "ATK" }),
45559
+ /* @__PURE__ */ jsx(Typography, { variant: "body1", className: "font-bold tabular-nums text-foreground", children: playerAttack })
45560
+ ] }),
45561
+ /* @__PURE__ */ jsxs(HStack, { gap: "sm", className: "items-center", children: [
45562
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground", children: t("roguelike.phase") || "Phase" }),
45563
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "capitalize text-foreground", children: phase.replace("_", " ") })
45564
+ ] })
45565
+ ] }),
45566
+ /* @__PURE__ */ jsx(Box, { className: "relative flex-1", children: /* @__PURE__ */ jsx(
45567
+ IsometricCanvas_default,
45568
+ {
45569
+ tiles,
45570
+ units: isoUnits,
45571
+ features: isoFeatures,
45572
+ selectedUnitId: null,
45573
+ validMoves,
45574
+ attackTargets: [],
45575
+ hoveredTile,
45576
+ onTileClick: handleTileClick,
45577
+ onUnitClick: () => void 0,
45578
+ onTileHover: (x, y) => setHoveredTile({ x, y }),
45579
+ onTileLeave: () => setHoveredTile(null),
45580
+ scale,
45581
+ assetBaseUrl: propAssetManifest?.baseUrl ?? CDN4,
45582
+ assetManifest: propAssetManifest,
45583
+ unitScale
45584
+ }
45585
+ ) }),
45586
+ !isGameOver && /* @__PURE__ */ jsx(HStack, { className: "justify-center gap-2 p-3", gap: "none", children: [
45587
+ { label: "\u2191", dx: 0, dy: -1 },
45588
+ { label: "\u2193", dx: 0, dy: 1 },
45589
+ { label: "\u2190", dx: -1, dy: 0 },
45590
+ { label: "\u2192", dx: 1, dy: 0 }
45591
+ ].map(({ label, dx, dy }) => /* @__PURE__ */ jsx(
45592
+ Button,
45593
+ {
45594
+ variant: "secondary",
45595
+ className: "w-9 h-9 p-0 text-base font-mono",
45596
+ onClick: () => emitMove(dx, dy),
45597
+ disabled: phase !== "player_turn",
45598
+ children: label
45599
+ },
45600
+ label
45601
+ )) }),
45602
+ 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: [
45603
+ /* @__PURE__ */ jsx(
45604
+ Typography,
45605
+ {
45606
+ variant: "h2",
45607
+ className: cn(
45608
+ "text-4xl font-black tracking-widest uppercase",
45609
+ result === "won" ? "text-warning" : "text-error"
45610
+ ),
45611
+ children: result === "won" ? t("roguelike.victory") || "Victory!" : t("roguelike.defeat") || "Defeated!"
45612
+ }
45613
+ ),
45614
+ result === "won" && /* @__PURE__ */ jsx(Typography, { variant: "body1", className: "text-muted-foreground", children: t("roguelike.cleared") || `Dungeon cleared! Depth reached: ${depth}` }),
45615
+ /* @__PURE__ */ jsx(
45616
+ Button,
45617
+ {
45618
+ variant: "primary",
45619
+ className: "px-8 py-3 font-semibold",
45620
+ onClick: handleReset,
45621
+ children: t("roguelike.playAgain") || "Play Again"
45622
+ }
45623
+ )
45624
+ ] }) })
45625
+ ] });
45626
+ }
45627
+ var CDN4, DEFAULT_TILES2, DEFAULT_ENEMIES, DEFAULT_ITEMS, FEATURE_SPRITE;
45628
+ var init_RoguelikeBoard = __esm({
45629
+ "components/game/organisms/RoguelikeBoard.tsx"() {
45630
+ "use client";
45631
+ init_cn();
45632
+ init_useEventBus();
45633
+ init_Box();
45634
+ init_Button();
45635
+ init_Typography();
45636
+ init_Stack();
45637
+ init_IsometricCanvas();
45638
+ init_boardEntity();
45639
+ CDN4 = "https://almadar-kflow-assets.web.app/shared";
45640
+ DEFAULT_TILES2 = [
45641
+ { x: 0, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
45642
+ { x: 1, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
45643
+ { x: 2, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
45644
+ { x: 3, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
45645
+ { x: 4, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
45646
+ { x: 0, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
45647
+ { x: 1, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
45648
+ { x: 2, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
45649
+ { x: 3, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
45650
+ { x: 4, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
45651
+ { x: 0, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
45652
+ { x: 1, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirtTiles_E.png` },
45653
+ { x: 2, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirtTiles_E.png` },
45654
+ { x: 3, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
45655
+ { x: 4, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
45656
+ { x: 0, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
45657
+ { x: 1, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirt_E.png` },
45658
+ { x: 2, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/dirtTiles_E.png` },
45659
+ { x: 3, y: 3, terrain: "stairs", passable: true, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stairs_E.png` },
45660
+ { x: 4, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
45661
+ { x: 0, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
45662
+ { x: 1, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
45663
+ { x: 2, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
45664
+ { x: 3, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` },
45665
+ { x: 4, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN4}/isometric-dungeon/Isometric/stoneInset_E.png` }
45666
+ ];
45667
+ DEFAULT_ENEMIES = [
45668
+ { id: "e1", x: 3, y: 1, hp: 5, attack: 2 },
45669
+ { id: "e2", x: 1, y: 3, hp: 4, attack: 1 }
45670
+ ];
45671
+ DEFAULT_ITEMS = [
45672
+ { id: "i1", x: 2, y: 2, kind: "health_potion" }
45673
+ ];
45674
+ FEATURE_SPRITE = {
45675
+ health_potion: `${CDN4}/isometric-dungeon/Isometric/chestClosed_E.png`,
45676
+ sword: `${CDN4}/isometric-dungeon/Isometric/barrel_E.png`,
45677
+ shield: `${CDN4}/isometric-dungeon/Isometric/barrel_E.png`
45678
+ };
45679
+ RoguelikeBoard.displayName = "RoguelikeBoard";
45680
+ }
45681
+ });
45682
+ function RoguelikeTemplate({
45683
+ entity,
45684
+ scale = 0.45,
45685
+ unitScale = 1,
45686
+ tiles = DEFAULT_ROGUELIKE_TILES,
45687
+ enemies,
45688
+ items,
45689
+ stairsX,
45690
+ stairsY,
45691
+ assetManifest = DEFAULT_ROGUELIKE_MANIFEST,
45692
+ gameEndEvent,
45693
+ className
45694
+ }) {
45695
+ const resolved = entity && typeof entity === "object" && !Array.isArray(entity) ? entity : void 0;
45696
+ if (!resolved && !tiles && !assetManifest) return null;
45697
+ return /* @__PURE__ */ jsx(
45698
+ RoguelikeBoard,
45699
+ {
45700
+ entity: resolved,
45701
+ tiles,
45702
+ enemies,
45703
+ items,
45704
+ stairsX,
45705
+ stairsY,
45706
+ assetManifest,
45707
+ scale,
45708
+ unitScale,
45709
+ moveEvent: "MOVE",
45710
+ playAgainEvent: "PLAY_AGAIN",
45711
+ gameEndEvent: gameEndEvent ?? "GAME_END",
45712
+ className
45713
+ }
45714
+ );
45715
+ }
45716
+ var CDN5, DEFAULT_ROGUELIKE_TILES, DEFAULT_ROGUELIKE_MANIFEST;
45717
+ var init_RoguelikeTemplate = __esm({
45718
+ "components/game/templates/RoguelikeTemplate.tsx"() {
45719
+ init_RoguelikeBoard();
45720
+ CDN5 = "https://almadar-kflow-assets.web.app/shared";
45721
+ DEFAULT_ROGUELIKE_TILES = [
45722
+ { x: 0, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45723
+ { x: 1, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45724
+ { x: 2, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45725
+ { x: 3, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45726
+ { x: 4, y: 0, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45727
+ { x: 0, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45728
+ { x: 1, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
45729
+ { x: 2, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
45730
+ { x: 3, y: 1, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
45731
+ { x: 4, y: 1, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45732
+ { x: 0, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45733
+ { x: 1, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirtTiles_E.png` },
45734
+ { x: 2, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirtTiles_E.png` },
45735
+ { x: 3, y: 2, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
45736
+ { x: 4, y: 2, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45737
+ { x: 0, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45738
+ { x: 1, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirt_E.png` },
45739
+ { x: 2, y: 3, terrain: "floor", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/dirtTiles_E.png` },
45740
+ { x: 3, y: 3, terrain: "stairs", passable: true, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stairs_E.png` },
45741
+ { x: 4, y: 3, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45742
+ { x: 0, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45743
+ { x: 1, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45744
+ { x: 2, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45745
+ { x: 3, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` },
45746
+ { x: 4, y: 4, terrain: "wall", passable: false, terrainSprite: `${CDN5}/isometric-dungeon/Isometric/stoneInset_E.png` }
45747
+ ];
45748
+ DEFAULT_ROGUELIKE_MANIFEST = {
45749
+ baseUrl: CDN5,
45750
+ terrains: {
45751
+ floor: "/isometric-dungeon/Isometric/dirt_E.png",
45752
+ wall: "/isometric-dungeon/Isometric/stoneInset_E.png",
45753
+ stairs: "/isometric-dungeon/Isometric/stairs_E.png",
45754
+ planks: "/isometric-dungeon/Isometric/planks_E.png"
45755
+ },
45756
+ units: {
45757
+ player: "/isometric-dungeon/Characters/Male/Male_0_Idle0.png",
45758
+ enemy: "/sprite-sheets/shadow-legion-sprite-sheet-sw.png"
45759
+ },
45760
+ features: {
45761
+ health_potion: "/isometric-dungeon/Isometric/chestClosed_E.png",
45762
+ sword: "/isometric-dungeon/Isometric/barrel_E.png",
45763
+ shield: "/isometric-dungeon/Isometric/barrel_E.png"
45764
+ }
45765
+ };
45766
+ RoguelikeTemplate.displayName = "RoguelikeTemplate";
45767
+ }
45768
+ });
43505
45769
 
43506
45770
  // lib/traitRegistry.ts
43507
45771
  function getAllTraits() {
@@ -44736,7 +47000,7 @@ function getAllEvents(traits2) {
44736
47000
  function EventDispatcherTab({ traits: traits2, schema }) {
44737
47001
  const eventBus = useEventBus();
44738
47002
  const { t } = useTranslate();
44739
- const [log9, setLog] = React77.useState([]);
47003
+ const [log11, setLog] = React77.useState([]);
44740
47004
  const prevStatesRef = React77.useRef(/* @__PURE__ */ new Map());
44741
47005
  React77.useEffect(() => {
44742
47006
  for (const trait of traits2) {
@@ -44800,9 +47064,9 @@ function EventDispatcherTab({ traits: traits2, schema }) {
44800
47064
  /* @__PURE__ */ jsx(Typography, { variant: "small", weight: "medium", className: "text-gray-500 mb-1", children: t("debug.otherEvents") }),
44801
47065
  /* @__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)) })
44802
47066
  ] }),
44803
- log9.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
47067
+ log11.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
44804
47068
  /* @__PURE__ */ jsx(Typography, { variant: "small", weight: "medium", className: "text-gray-500 mb-1", children: t("debug.recentTransitions") }),
44805
- /* @__PURE__ */ jsx(Stack, { gap: "xs", children: log9.map((entry, i) => /* @__PURE__ */ jsxs(Typography, { variant: "small", className: "font-mono text-xs", children: [
47069
+ /* @__PURE__ */ jsx(Stack, { gap: "xs", children: log11.map((entry, i) => /* @__PURE__ */ jsxs(Typography, { variant: "small", className: "font-mono text-xs", children: [
44806
47070
  /* @__PURE__ */ jsx("span", { className: "text-purple-400", children: entry.traitName }),
44807
47071
  " ",
44808
47072
  /* @__PURE__ */ jsx("span", { className: "text-gray-500", children: entry.from }),
@@ -45605,17 +47869,24 @@ function SequencerBoard({
45605
47869
  const { emit } = useEventBus();
45606
47870
  const { t } = useTranslate();
45607
47871
  const resolved = boardEntity(entity);
45608
- const maxSlots = num(resolved?.maxSlots);
47872
+ const maxSlots = num(resolved?.maxSlots) || 3;
45609
47873
  const solutions = Array.isArray(resolved?.solutions) ? resolved.solutions : [];
45610
47874
  const availableActions = Array.isArray(resolved?.availableActions) ? resolved.availableActions : [];
45611
47875
  const allowDuplicates = resolved?.allowDuplicates !== false;
47876
+ const entitySlots = Array.isArray(resolved?.slots) ? resolved.slots : [];
47877
+ const entityResult = str(resolved?.result);
47878
+ const entityAttempts = num(resolved?.attempts);
47879
+ const entityCurrentStep = typeof resolved?.currentStep === "number" ? resolved.currentStep : -1;
47880
+ const slots = Array.from({ length: maxSlots }, (_, i) => {
47881
+ const entitySlot = entitySlots.find((s) => s.index === i);
47882
+ if (!entitySlot?.placedActionId) return void 0;
47883
+ return availableActions.find((a) => a.id === entitySlot.placedActionId);
47884
+ });
47885
+ const isSuccess = entityResult === "win";
47886
+ const attempts = entityAttempts;
47887
+ const isPlayingBack = entityCurrentStep >= 0 && !isSuccess;
47888
+ const currentStep = entityCurrentStep;
45612
47889
  const [headerError, setHeaderError] = useState(false);
45613
- const [slots, setSlots] = useState(
45614
- () => Array.from({ length: maxSlots }, () => void 0)
45615
- );
45616
- const [playState, setPlayState] = useState("idle");
45617
- const [currentStep, setCurrentStep] = useState(-1);
45618
- const [attempts, setAttempts] = useState(0);
45619
47890
  const [slotFeedback, setSlotFeedback] = useState(
45620
47891
  () => Array.from({ length: maxSlots }, () => null)
45621
47892
  );
@@ -45624,11 +47895,6 @@ function SequencerBoard({
45624
47895
  if (timerRef.current) clearTimeout(timerRef.current);
45625
47896
  }, []);
45626
47897
  const handleSlotDrop = useCallback((index, item) => {
45627
- setSlots((prev) => {
45628
- const next = [...prev];
45629
- next[index] = item;
45630
- return next;
45631
- });
45632
47898
  setSlotFeedback((prev) => {
45633
47899
  const next = [...prev];
45634
47900
  next[index] = null;
@@ -45638,11 +47904,6 @@ function SequencerBoard({
45638
47904
  if (placeEvent) emit(`UI:${placeEvent}`, { slotIndex: index, actionId: item.id });
45639
47905
  }, [emit, placeEvent]);
45640
47906
  const handleSlotRemove = useCallback((index) => {
45641
- setSlots((prev) => {
45642
- const next = [...prev];
45643
- next[index] = void 0;
45644
- return next;
45645
- });
45646
47907
  setSlotFeedback((prev) => {
45647
47908
  const next = [...prev];
45648
47909
  next[index] = null;
@@ -45653,48 +47914,34 @@ function SequencerBoard({
45653
47914
  }, [emit, removeEvent]);
45654
47915
  const handleReset = useCallback(() => {
45655
47916
  if (timerRef.current) clearTimeout(timerRef.current);
45656
- setSlots(Array.from({ length: maxSlots }, () => void 0));
45657
- setPlayState("idle");
45658
- setCurrentStep(-1);
45659
- setAttempts(0);
45660
47917
  setSlotFeedback(Array.from({ length: maxSlots }, () => null));
45661
47918
  if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
45662
47919
  }, [maxSlots, playAgainEvent, emit]);
45663
47920
  const filledSlots = slots.filter((s) => !!s);
45664
- const canPlay = filledSlots.length > 0 && playState === "idle";
47921
+ const canPlay = filledSlots.length > 0 && !isPlayingBack && !isSuccess;
45665
47922
  const handlePlay = useCallback(() => {
45666
47923
  if (!canPlay) return;
45667
47924
  setSlotFeedback(Array.from({ length: maxSlots }, () => null));
45668
47925
  emit("UI:PLAY_SOUND", { key: "confirm" });
45669
47926
  const sequence = slots.map((s) => s?.id || "");
45670
- if (playEvent) {
45671
- emit(`UI:${playEvent}`, { sequence });
45672
- }
45673
- setPlayState("playing");
45674
- setCurrentStep(0);
47927
+ const playerIds = slots.filter(Boolean).map((s) => s?.id || "");
47928
+ if (playEvent) emit(`UI:${playEvent}`, { sequence });
45675
47929
  let step = 0;
45676
47930
  const advance = () => {
45677
47931
  step++;
47932
+ emit("UI:STEP", { step });
45678
47933
  if (step >= maxSlots) {
47934
+ if (checkEvent) emit(`UI:${checkEvent}`, { sequence: playerIds });
45679
47935
  const playerSeq = slots.map((s) => s?.id);
45680
- const playerIds = slots.filter(Boolean).map((s) => s?.id || "");
45681
47936
  const success = solutions.some(
45682
47937
  (sol) => sol.length === playerIds.length && sol.every((id, i) => id === playerIds[i])
45683
47938
  );
45684
- if (checkEvent) emit(`UI:${checkEvent}`, { sequence: playerIds });
45685
47939
  if (success) {
45686
- setPlayState("success");
45687
- setCurrentStep(-1);
45688
47940
  emit("UI:PLAY_SOUND", { key: "levelComplete" });
45689
- if (completeEvent) {
45690
- emit(`UI:${completeEvent}`, { success: true, sequence: playerIds });
45691
- }
47941
+ if (completeEvent) emit(`UI:${completeEvent}`, { success: true, sequence: playerIds });
45692
47942
  } else {
45693
- setAttempts((prev) => prev + 1);
45694
47943
  const feedback = computeSlotFeedback(playerSeq, solutions);
45695
47944
  setSlotFeedback(feedback);
45696
- setPlayState("idle");
45697
- setCurrentStep(-1);
45698
47945
  emit("UI:PLAY_SOUND", { key: "fail" });
45699
47946
  const correctCount2 = feedback.filter((f3) => f3 === "correct").length;
45700
47947
  for (let ci = 0; ci < correctCount2; ci++) {
@@ -45704,7 +47951,6 @@ function SequencerBoard({
45704
47951
  }
45705
47952
  }
45706
47953
  } else {
45707
- setCurrentStep(step);
45708
47954
  timerRef.current = setTimeout(advance, stepDurationMs);
45709
47955
  }
45710
47956
  };
@@ -45754,7 +48000,7 @@ function SequencerBoard({
45754
48000
  /* @__PURE__ */ jsxs(VStack, { gap: "xs", children: [
45755
48001
  /* @__PURE__ */ jsxs(HStack, { className: "items-center justify-between", children: [
45756
48002
  /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("sequencer.yourSequence") + ":" }),
45757
- hasFeedback && playState === "idle" && /* @__PURE__ */ jsxs(Typography, { variant: "caption", className: "text-muted-foreground", children: [
48003
+ hasFeedback && !isPlayingBack && !isSuccess && /* @__PURE__ */ jsxs(Typography, { variant: "caption", className: "text-muted-foreground", children: [
45758
48004
  `${correctCount}/${maxSlots} `,
45759
48005
  "\u2705"
45760
48006
  ] })
@@ -45766,7 +48012,7 @@ function SequencerBoard({
45766
48012
  maxSlots,
45767
48013
  onSlotDrop: handleSlotDrop,
45768
48014
  onSlotRemove: handleSlotRemove,
45769
- playing: playState === "playing",
48015
+ playing: isPlayingBack,
45770
48016
  currentStep,
45771
48017
  categoryColors,
45772
48018
  slotFeedback,
@@ -45774,7 +48020,7 @@ function SequencerBoard({
45774
48020
  }
45775
48021
  )
45776
48022
  ] }),
45777
- playState !== "playing" && /* @__PURE__ */ jsx(
48023
+ !isPlayingBack && /* @__PURE__ */ jsx(
45778
48024
  ActionPalette,
45779
48025
  {
45780
48026
  actions: availableActions,
@@ -45784,8 +48030,8 @@ function SequencerBoard({
45784
48030
  label: t("sequencer.dragActions")
45785
48031
  }
45786
48032
  ),
45787
- 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) }) }),
45788
- 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") }) }),
48033
+ 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) }) }),
48034
+ 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") }) }),
45789
48035
  /* @__PURE__ */ jsxs(HStack, { gap: "sm", children: [
45790
48036
  /* @__PURE__ */ jsx(
45791
48037
  Button,
@@ -46852,9 +49098,9 @@ function VariablePanel({
46852
49098
  /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("stateArchitect.variables", { name: entityName }) }),
46853
49099
  variables.map((v) => {
46854
49100
  const name = v.name == null ? "" : String(v.name);
46855
- const value = numField(v.value);
46856
- const max = numField(v.max, 100);
46857
- const min = numField(v.min, 0);
49101
+ const value = numField2(v.value);
49102
+ const max = numField2(v.max, 100);
49103
+ const min = numField2(v.min, 0);
46858
49104
  const unit = v.unit == null ? "" : String(v.unit);
46859
49105
  const pct = Math.round((value - min) / (max - min) * 100);
46860
49106
  const isHigh = pct > 80;
@@ -46884,12 +49130,12 @@ function VariablePanel({
46884
49130
  })
46885
49131
  ] });
46886
49132
  }
46887
- var numField;
49133
+ var numField2;
46888
49134
  var init_VariablePanel = __esm({
46889
49135
  "components/game/organisms/puzzles/state-architect/VariablePanel.tsx"() {
46890
49136
  init_atoms2();
46891
49137
  init_cn();
46892
- numField = (v, fallback = 0) => {
49138
+ numField2 = (v, fallback = 0) => {
46893
49139
  const n = Number(v);
46894
49140
  return Number.isFinite(n) ? n : fallback;
46895
49141
  };
@@ -46946,6 +49192,9 @@ function StateArchitectBoard({
46946
49192
  stepDurationMs = 600,
46947
49193
  testEvent,
46948
49194
  completeEvent,
49195
+ addTransitionEvent,
49196
+ removeTransitionEvent,
49197
+ playAgainEvent,
46949
49198
  className
46950
49199
  }) {
46951
49200
  const { emit } = useEventBus();
@@ -46958,14 +49207,16 @@ function StateArchitectBoard({
46958
49207
  const testCases = Array.isArray(resolved?.testCases) ? resolved.testCases : [];
46959
49208
  const entityTransitions = Array.isArray(resolved?.transitions) ? resolved.transitions : [];
46960
49209
  const entityVariables = rows(resolved?.variables);
46961
- const [transitions, setTransitions] = useState(entityTransitions);
49210
+ const transitions = entityTransitions;
49211
+ const attempts = typeof resolved?.attempts === "number" ? resolved.attempts : 0;
49212
+ const entityResult = str(resolved?.result) || "none";
49213
+ const isSuccess = entityResult === "win";
49214
+ const [isTesting, setIsTesting] = useState(false);
46962
49215
  const [headerError, setHeaderError] = useState(false);
46963
- const [playState, setPlayState] = useState("editing");
46964
49216
  const [currentState, setCurrentState] = useState(initialState);
46965
49217
  const [selectedState, setSelectedState] = useState(null);
46966
49218
  const [testResults, setTestResults] = useState([]);
46967
49219
  const [variables, setVariables] = useState(() => [...entityVariables]);
46968
- const [attempts, setAttempts] = useState(0);
46969
49220
  const timerRef = useRef(null);
46970
49221
  const [addingFrom, setAddingFrom] = useState(null);
46971
49222
  useEffect(() => () => {
@@ -46975,7 +49226,7 @@ function StateArchitectBoard({
46975
49226
  const GRAPH_H = 400;
46976
49227
  const positions = useMemo(() => layoutStates(entityStates, GRAPH_W, GRAPH_H), [entityStates]);
46977
49228
  const handleStateClick = useCallback((state) => {
46978
- if (playState !== "editing") return;
49229
+ if (isTesting) return;
46979
49230
  if (addingFrom) {
46980
49231
  if (addingFrom !== state) {
46981
49232
  const event = availableEvents[0] || "EVENT";
@@ -46985,20 +49236,20 @@ function StateArchitectBoard({
46985
49236
  to: state,
46986
49237
  event
46987
49238
  };
46988
- setTransitions((prev) => [...prev, newTrans]);
49239
+ if (addTransitionEvent) emit(`UI:${addTransitionEvent}`, { id: newTrans.id, from: newTrans.from, to: newTrans.to, event: newTrans.event });
46989
49240
  }
46990
49241
  setAddingFrom(null);
46991
49242
  } else {
46992
49243
  setSelectedState(state);
46993
49244
  }
46994
- }, [playState, addingFrom, availableEvents]);
49245
+ }, [isTesting, addingFrom, availableEvents, addTransitionEvent, emit]);
46995
49246
  const handleStartAddTransition = useCallback(() => {
46996
49247
  if (!selectedState) return;
46997
49248
  setAddingFrom(selectedState);
46998
49249
  }, [selectedState]);
46999
49250
  const handleRemoveTransition = useCallback((transId) => {
47000
- setTransitions((prev) => prev.filter((t2) => t2.id !== transId));
47001
- }, []);
49251
+ if (removeTransitionEvent) emit(`UI:${removeTransitionEvent}`, { id: transId });
49252
+ }, [removeTransitionEvent, emit]);
47002
49253
  const machine = useMemo(() => ({
47003
49254
  name: entityName,
47004
49255
  description: str(resolved?.description),
@@ -47012,16 +49263,16 @@ function StateArchitectBoard({
47012
49263
  }))
47013
49264
  }), [entityName, resolved, entityStates, currentState, transitions]);
47014
49265
  const handleTest = useCallback(() => {
47015
- if (playState !== "editing") return;
49266
+ if (isTesting) return;
47016
49267
  if (testEvent) emit(`UI:${testEvent}`, {});
47017
- setPlayState("testing");
49268
+ setIsTesting(true);
47018
49269
  setTestResults([]);
47019
49270
  const results = [];
47020
49271
  let testIdx = 0;
47021
49272
  const runNextTest = () => {
47022
49273
  if (testIdx >= testCases.length) {
47023
49274
  const allPassed = results.every((r) => r.passed);
47024
- setPlayState(allPassed ? "success" : "fail");
49275
+ setIsTesting(false);
47025
49276
  setTestResults(results);
47026
49277
  if (allPassed && completeEvent) {
47027
49278
  emit(`UI:${completeEvent}`, {
@@ -47029,9 +49280,6 @@ function StateArchitectBoard({
47029
49280
  passedTests: results.filter((r) => r.passed).length
47030
49281
  });
47031
49282
  }
47032
- if (!allPassed) {
47033
- setAttempts((prev) => prev + 1);
47034
- }
47035
49283
  return;
47036
49284
  }
47037
49285
  const testCase = testCases[testIdx];
@@ -47054,24 +49302,23 @@ function StateArchitectBoard({
47054
49302
  timerRef.current = setTimeout(runNextTest, stepDurationMs);
47055
49303
  };
47056
49304
  timerRef.current = setTimeout(runNextTest, stepDurationMs);
47057
- }, [playState, transitions, testCases, initialState, stepDurationMs, testEvent, completeEvent, emit]);
49305
+ }, [isTesting, transitions, testCases, initialState, stepDurationMs, testEvent, completeEvent, emit]);
47058
49306
  const handleTryAgain = useCallback(() => {
47059
49307
  if (timerRef.current) clearTimeout(timerRef.current);
47060
- setPlayState("editing");
49308
+ setIsTesting(false);
47061
49309
  setCurrentState(initialState);
47062
49310
  setTestResults([]);
47063
49311
  }, [initialState]);
47064
49312
  const handleReset = useCallback(() => {
47065
49313
  if (timerRef.current) clearTimeout(timerRef.current);
47066
- setTransitions(entityTransitions);
47067
- setPlayState("editing");
49314
+ setIsTesting(false);
47068
49315
  setCurrentState(initialState);
47069
49316
  setTestResults([]);
47070
49317
  setVariables([...entityVariables]);
47071
49318
  setSelectedState(null);
47072
49319
  setAddingFrom(null);
47073
- setAttempts(0);
47074
- }, [entityTransitions, initialState, entityVariables]);
49320
+ if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
49321
+ }, [initialState, entityVariables, playAgainEvent, emit]);
47075
49322
  const codeData = useMemo(() => ({
47076
49323
  name: entityName,
47077
49324
  states: entityStates,
@@ -47167,7 +49414,7 @@ function StateArchitectBoard({
47167
49414
  ]
47168
49415
  }
47169
49416
  ),
47170
- playState === "editing" && /* @__PURE__ */ jsx(HStack, { gap: "sm", children: /* @__PURE__ */ jsx(
49417
+ !isTesting && /* @__PURE__ */ jsx(HStack, { gap: "sm", children: /* @__PURE__ */ jsx(
47171
49418
  Button,
47172
49419
  {
47173
49420
  variant: "ghost",
@@ -47189,7 +49436,7 @@ function StateArchitectBoard({
47189
49436
  t2.guardHint,
47190
49437
  ")"
47191
49438
  ] }),
47192
- playState === "editing" && /* @__PURE__ */ jsx(
49439
+ !isTesting && /* @__PURE__ */ jsx(
47193
49440
  Button,
47194
49441
  {
47195
49442
  variant: "ghost",
@@ -47221,21 +49468,21 @@ function StateArchitectBoard({
47221
49468
  resolved.showCodeView !== false && /* @__PURE__ */ jsx(StateJsonView, { data: codeData, label: "View Code" })
47222
49469
  ] })
47223
49470
  ] }),
47224
- 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") }) }),
47225
- playState === "fail" && /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
49471
+ 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") }) }),
49472
+ !isTesting && !isSuccess && testResults.some((r) => !r.passed) && /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
47226
49473
  /* @__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]) }) }),
47227
- 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: [
49474
+ !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: [
47228
49475
  /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-accent font-bold shrink-0", children: "\u{1F4A1} " + t("game.hint") + ":" }),
47229
49476
  /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-foreground", children: hint })
47230
49477
  ] }) })
47231
49478
  ] }),
47232
49479
  /* @__PURE__ */ jsxs(HStack, { gap: "sm", children: [
47233
- playState === "fail" ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleTryAgain, children: "\u{1F504} " + t("puzzle.tryAgainButton") }) : /* @__PURE__ */ jsx(
49480
+ !isTesting && !isSuccess && testResults.some((r) => !r.passed) ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleTryAgain, children: "\u{1F504} " + t("puzzle.tryAgainButton") }) : /* @__PURE__ */ jsx(
47234
49481
  Button,
47235
49482
  {
47236
49483
  variant: "primary",
47237
49484
  onClick: handleTest,
47238
- disabled: playState !== "editing",
49485
+ disabled: isTesting,
47239
49486
  children: "\u25B6 " + t("game.runTests")
47240
49487
  }
47241
49488
  ),
@@ -47315,6 +49562,7 @@ function formatDuration(seconds) {
47315
49562
  return `${Math.round(seconds)}s`;
47316
49563
  }
47317
49564
  function StatusEffect({
49565
+ assetUrl = DEFAULT_ASSET_URL13,
47318
49566
  icon = "shield",
47319
49567
  label = "Shield",
47320
49568
  duration = 30,
@@ -47335,7 +49583,17 @@ function StatusEffect({
47335
49583
  ),
47336
49584
  title: label,
47337
49585
  children: [
47338
- /* @__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" }) }),
49586
+ /* @__PURE__ */ jsx("span", { className: cn("flex items-center justify-center", sizes.icon), children: assetUrl ? /* @__PURE__ */ jsx(
49587
+ "img",
49588
+ {
49589
+ src: assetUrl,
49590
+ alt: label,
49591
+ width: sizes.img,
49592
+ height: sizes.img,
49593
+ style: { imageRendering: "pixelated", objectFit: "contain" },
49594
+ className: "flex-shrink-0"
49595
+ }
49596
+ ) : typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon, size: "sm" }) : /* @__PURE__ */ jsx(Icon, { icon, size: "sm" }) }),
47339
49597
  duration !== void 0 && /* @__PURE__ */ jsx(
47340
49598
  "span",
47341
49599
  {
@@ -47362,15 +49620,16 @@ function StatusEffect({
47362
49620
  label && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground mt-0.5 text-center whitespace-nowrap", children: label })
47363
49621
  ] });
47364
49622
  }
47365
- var sizeMap16, variantStyles9;
49623
+ var DEFAULT_ASSET_URL13, sizeMap16, variantStyles9;
47366
49624
  var init_StatusEffect = __esm({
47367
49625
  "components/game/atoms/StatusEffect.tsx"() {
47368
49626
  init_cn();
47369
49627
  init_Icon();
49628
+ DEFAULT_ASSET_URL13 = "https://almadar-kflow-assets.web.app/shared/effects/particles/flame_01.png";
47370
49629
  sizeMap16 = {
47371
- sm: { container: "w-8 h-8", icon: "text-sm", badge: "text-xs -top-1 -right-1 w-4 h-4", timer: "text-[9px]" },
47372
- md: { container: "w-10 h-10", icon: "text-base", badge: "text-xs -top-1 -right-1 w-5 h-5", timer: "text-xs" },
47373
- 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" }
49630
+ 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 },
49631
+ 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 },
49632
+ 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 }
47374
49633
  };
47375
49634
  variantStyles9 = {
47376
49635
  buff: "border-success bg-success/20",
@@ -47863,6 +50122,289 @@ var init_ToastSlot = __esm({
47863
50122
  ToastSlot.displayName = "ToastSlot";
47864
50123
  }
47865
50124
  });
50125
+ function tilesToIso(tiles) {
50126
+ return tiles.map((t) => ({
50127
+ x: t.x,
50128
+ y: t.y,
50129
+ terrain: t.terrain,
50130
+ terrainSprite: t.terrainSprite ?? TERRAIN_SPRITES[t.terrain ?? "ground"],
50131
+ passable: t.passable
50132
+ }));
50133
+ }
50134
+ function towersToUnits(towers) {
50135
+ return towers.map((t) => ({
50136
+ id: t.id,
50137
+ position: { x: t.x, y: t.y },
50138
+ name: "Tower",
50139
+ team: "player",
50140
+ sprite: TOWER_SPRITE,
50141
+ unitType: "guardian",
50142
+ health: 1,
50143
+ maxHealth: 1
50144
+ }));
50145
+ }
50146
+ function creepsToUnits(creeps) {
50147
+ return creeps.map((c) => ({
50148
+ id: c.id,
50149
+ position: { x: c.x, y: c.y },
50150
+ name: "Creep",
50151
+ team: "enemy",
50152
+ sprite: CREEP_SPRITE,
50153
+ unitType: "scrapper",
50154
+ health: c.hp,
50155
+ maxHealth: c.maxHp
50156
+ }));
50157
+ }
50158
+ function pathToFeatures(path) {
50159
+ return path.map((p2, i) => ({
50160
+ id: `path-${i}`,
50161
+ x: p2.x,
50162
+ y: p2.y,
50163
+ type: "path-marker",
50164
+ sprite: `${CDN6}world-map/road_straight.png`
50165
+ }));
50166
+ }
50167
+ function TowerDefenseBoard({
50168
+ entity,
50169
+ tiles: propTiles,
50170
+ path: propPath,
50171
+ towers: propTowers,
50172
+ creeps: propCreeps,
50173
+ gold: propGold,
50174
+ lives: propLives,
50175
+ wave: propWave,
50176
+ maxWaves: propMaxWaves,
50177
+ result: propResult,
50178
+ waveActive: propWaveActive,
50179
+ towerCost = 25,
50180
+ scale = 0.45,
50181
+ placeTowerEvent,
50182
+ startWaveEvent,
50183
+ playAgainEvent,
50184
+ gameEndEvent,
50185
+ className
50186
+ }) {
50187
+ const board = boardEntity(entity) ?? {};
50188
+ const eventBus = useEventBus();
50189
+ const { t } = useTranslate();
50190
+ const tiles = propTiles ?? rows(board.tiles);
50191
+ const path = propPath ?? rows(board.path);
50192
+ const towers = propTowers ?? rows(board.towers);
50193
+ const creeps = propCreeps ?? rows(board.creeps);
50194
+ const gold = propGold ?? num(board.gold, 100);
50195
+ const lives = propLives ?? num(board.lives, 20);
50196
+ const wave = propWave ?? num(board.wave, 1);
50197
+ const maxWaves = propMaxWaves ?? num(board.maxWaves, 5);
50198
+ const result = propResult ?? (str(board.result) || "none");
50199
+ const waveActive = propWaveActive ?? Boolean(board.waveActive);
50200
+ const [hoveredTile, setHoveredTile] = useState(null);
50201
+ const emittedGameEnd = useRef(false);
50202
+ if (result !== "none" && !emittedGameEnd.current) {
50203
+ emittedGameEnd.current = true;
50204
+ if (gameEndEvent) {
50205
+ eventBus.emit(`UI:${gameEndEvent}`, { result });
50206
+ }
50207
+ }
50208
+ if (result === "none") {
50209
+ emittedGameEnd.current = false;
50210
+ }
50211
+ const towerPositions = useMemo(() => new Set(towers.map((t2) => `${t2.x},${t2.y}`)), [towers]);
50212
+ const pathPositions = useMemo(() => new Set(path.map((p2) => `${p2.x},${p2.y}`)), [path]);
50213
+ const validMoves = useMemo(() => {
50214
+ if (result !== "none" || gold < towerCost) return [];
50215
+ return tiles.filter(
50216
+ (t2) => t2.passable !== false && !towerPositions.has(`${t2.x},${t2.y}`) && !pathPositions.has(`${t2.x},${t2.y}`)
50217
+ ).map((t2) => ({ x: t2.x, y: t2.y }));
50218
+ }, [tiles, towerPositions, pathPositions, result, gold, towerCost]);
50219
+ const isoTiles = useMemo(() => tilesToIso(tiles), [tiles]);
50220
+ const towerUnits = useMemo(() => towersToUnits(towers), [towers]);
50221
+ const creepUnits = useMemo(() => creepsToUnits(creeps), [creeps]);
50222
+ const isoUnits = useMemo(() => [...towerUnits, ...creepUnits], [towerUnits, creepUnits]);
50223
+ const pathFeatures = useMemo(() => pathToFeatures(path), [path]);
50224
+ const handleTileClick = useCallback((x, y) => {
50225
+ if (result !== "none") return;
50226
+ if (pathPositions.has(`${x},${y}`)) return;
50227
+ if (towerPositions.has(`${x},${y}`)) return;
50228
+ if (gold < towerCost) return;
50229
+ if (placeTowerEvent) {
50230
+ eventBus.emit(`UI:${placeTowerEvent}`, { x, y });
50231
+ }
50232
+ }, [result, pathPositions, towerPositions, gold, towerCost, placeTowerEvent, eventBus]);
50233
+ const handleStartWave = useCallback(() => {
50234
+ if (startWaveEvent) {
50235
+ eventBus.emit(`UI:${startWaveEvent}`, { wave });
50236
+ }
50237
+ }, [startWaveEvent, wave, eventBus]);
50238
+ const handlePlayAgain = useCallback(() => {
50239
+ if (playAgainEvent) {
50240
+ eventBus.emit(`UI:${playAgainEvent}`, {});
50241
+ }
50242
+ }, [playAgainEvent, eventBus]);
50243
+ const canPlaceTower = gold >= towerCost && result === "none";
50244
+ const canStartWave = !waveActive && result === "none";
50245
+ return /* @__PURE__ */ jsxs(VStack, { className: cn("tower-defense-board relative min-h-[600px] bg-background", className), gap: "none", children: [
50246
+ /* @__PURE__ */ jsxs(
50247
+ HStack,
50248
+ {
50249
+ className: "px-4 py-2 border-b border-border bg-surface",
50250
+ gap: "lg",
50251
+ align: "center",
50252
+ children: [
50253
+ /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
50254
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", children: t("td.gold") ?? "Gold" }),
50255
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", weight: "bold", className: "text-warning", children: gold })
50256
+ ] }),
50257
+ /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
50258
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", children: t("td.lives") ?? "Lives" }),
50259
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", weight: "bold", className: lives <= 5 ? "text-error" : "text-success", children: lives })
50260
+ ] }),
50261
+ /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
50262
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", children: t("td.wave") ?? "Wave" }),
50263
+ /* @__PURE__ */ jsxs(Typography, { variant: "body2", weight: "bold", children: [
50264
+ wave,
50265
+ " / ",
50266
+ maxWaves
50267
+ ] })
50268
+ ] }),
50269
+ /* @__PURE__ */ jsxs(Box, { className: "ml-auto", children: [
50270
+ canStartWave && /* @__PURE__ */ jsx(Button, { variant: "primary", size: "sm", onClick: handleStartWave, children: t("td.startWave") ?? "Start Wave" }),
50271
+ waveActive && /* @__PURE__ */ jsx(Typography, { variant: "small", className: "text-warning animate-pulse", children: t("td.waveInProgress") ?? "Wave in progress\u2026" })
50272
+ ] })
50273
+ ]
50274
+ }
50275
+ ),
50276
+ /* @__PURE__ */ jsxs(Box, { className: "relative flex-1", children: [
50277
+ /* @__PURE__ */ jsx(
50278
+ IsometricCanvas_default,
50279
+ {
50280
+ tiles: isoTiles,
50281
+ units: isoUnits,
50282
+ features: pathFeatures,
50283
+ validMoves,
50284
+ hoveredTile,
50285
+ onTileClick: handleTileClick,
50286
+ onTileHover: (x, y) => setHoveredTile({ x, y }),
50287
+ onTileLeave: () => setHoveredTile(null),
50288
+ scale
50289
+ }
50290
+ ),
50291
+ 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)` }) })
50292
+ ] }),
50293
+ 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: [
50294
+ /* @__PURE__ */ jsx(
50295
+ Typography,
50296
+ {
50297
+ variant: "h2",
50298
+ className: cn(
50299
+ "text-4xl font-black tracking-widest uppercase",
50300
+ result === "won" ? "text-warning" : "text-error"
50301
+ ),
50302
+ children: result === "won" ? t("td.victory") ?? "Victory!" : t("td.defeat") ?? "Defeat!"
50303
+ }
50304
+ ),
50305
+ /* @__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." }),
50306
+ /* @__PURE__ */ jsx(
50307
+ Button,
50308
+ {
50309
+ variant: "primary",
50310
+ className: "px-8 py-3 font-semibold",
50311
+ onClick: handlePlayAgain,
50312
+ children: t("td.playAgain") ?? "Play Again"
50313
+ }
50314
+ )
50315
+ ] }) })
50316
+ ] });
50317
+ }
50318
+ var CDN6, TERRAIN_SPRITES, TOWER_SPRITE, CREEP_SPRITE;
50319
+ var init_TowerDefenseBoard = __esm({
50320
+ "components/game/organisms/TowerDefenseBoard.tsx"() {
50321
+ "use client";
50322
+ init_cn();
50323
+ init_useEventBus();
50324
+ init_Box();
50325
+ init_Button();
50326
+ init_Typography();
50327
+ init_Stack();
50328
+ init_IsometricCanvas();
50329
+ init_boardEntity();
50330
+ CDN6 = "https://almadar-kflow-assets.web.app/shared/";
50331
+ TERRAIN_SPRITES = {
50332
+ ground: `${CDN6}isometric-blocks/PNG/Platformer tiles/platformerTile_01.png`,
50333
+ path: `${CDN6}isometric-blocks/PNG/Platformer tiles/platformerTile_09.png`,
50334
+ wall: `${CDN6}isometric-blocks/PNG/Platformer tiles/platformerTile_05.png`
50335
+ };
50336
+ TOWER_SPRITE = `${CDN6}units/guardian.png`;
50337
+ CREEP_SPRITE = `${CDN6}units/scrapper.png`;
50338
+ TowerDefenseBoard.displayName = "TowerDefenseBoard";
50339
+ }
50340
+ });
50341
+ function TowerDefenseTemplate({
50342
+ entity,
50343
+ title = "Tower Defense",
50344
+ tiles,
50345
+ path,
50346
+ towers,
50347
+ gold,
50348
+ lives,
50349
+ maxWaves,
50350
+ towerCost,
50351
+ scale,
50352
+ placeTowerEvent,
50353
+ startWaveEvent,
50354
+ playAgainEvent,
50355
+ gameEndEvent,
50356
+ className
50357
+ }) {
50358
+ const resolved = entity && typeof entity === "object" && !Array.isArray(entity) ? entity : void 0;
50359
+ return /* @__PURE__ */ jsxs(
50360
+ Box,
50361
+ {
50362
+ display: "flex",
50363
+ fullHeight: true,
50364
+ className: cn("tower-defense-template flex-col", className),
50365
+ children: [
50366
+ /* @__PURE__ */ jsx(
50367
+ HStack,
50368
+ {
50369
+ gap: "sm",
50370
+ align: "center",
50371
+ className: "px-4 py-3 border-b-2 border-border bg-surface shrink-0",
50372
+ children: /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title })
50373
+ }
50374
+ ),
50375
+ /* @__PURE__ */ jsx(Box, { className: "flex-1 relative overflow-hidden", children: /* @__PURE__ */ jsx(
50376
+ TowerDefenseBoard,
50377
+ {
50378
+ entity: resolved,
50379
+ tiles,
50380
+ path,
50381
+ towers,
50382
+ gold,
50383
+ lives,
50384
+ maxWaves,
50385
+ towerCost,
50386
+ scale,
50387
+ placeTowerEvent,
50388
+ startWaveEvent,
50389
+ playAgainEvent,
50390
+ gameEndEvent,
50391
+ className: "h-full"
50392
+ }
50393
+ ) })
50394
+ ]
50395
+ }
50396
+ );
50397
+ }
50398
+ var init_TowerDefenseTemplate = __esm({
50399
+ "components/game/templates/TowerDefenseTemplate.tsx"() {
50400
+ init_cn();
50401
+ init_Box();
50402
+ init_Stack();
50403
+ init_Typography();
50404
+ init_TowerDefenseBoard();
50405
+ TowerDefenseTemplate.displayName = "TowerDefenseTemplate";
50406
+ }
50407
+ });
47866
50408
  function useBattleState(initialUnits, eventConfig = {}, callbacks = {}) {
47867
50409
  const eventBus = useEventBus();
47868
50410
  const {
@@ -48061,17 +50603,12 @@ var init_UncontrolledBattleBoard = __esm({
48061
50603
  }
48062
50604
  });
48063
50605
  function heroPosition(h) {
50606
+ if ("position" in h && h.position != null) return h.position;
48064
50607
  return vec2(h.position);
48065
50608
  }
48066
- function heroOwner(h) {
48067
- return str(h.owner);
48068
- }
48069
50609
  function heroMovement(h) {
48070
50610
  return num(h.movement);
48071
50611
  }
48072
- function hexPassable(h) {
48073
- return h.passable !== false;
48074
- }
48075
50612
  function defaultIsInRange(from, to, range) {
48076
50613
  return Math.abs(from.x - to.x) + Math.abs(from.y - to.y) <= range;
48077
50614
  }
@@ -48107,38 +50644,41 @@ function WorldMapBoard({
48107
50644
  }) {
48108
50645
  const eventBus = useEventBus();
48109
50646
  const resolved = boardEntity(entity);
48110
- const hexes = rows(resolved?.hexes);
48111
- const heroes = rows(resolved?.heroes);
50647
+ const entityUnits = rows(resolved?.units);
50648
+ const entityTiles = rows(resolved?.tiles);
48112
50649
  const features = propFeatures ?? (Array.isArray(resolved?.features) ? resolved.features : []);
48113
50650
  const selectedHeroId = resolved?.selectedHeroId ?? null;
48114
50651
  const assetManifest = propAssetManifest ?? resolved?.assetManifest;
48115
50652
  const backgroundImage = resolved?.backgroundImage;
48116
50653
  const [hoveredTile, setHoveredTile] = useState(null);
48117
- const selectedHero = useMemo(
48118
- () => heroes.find((h) => str(h.id) === selectedHeroId) ?? null,
48119
- [heroes, selectedHeroId]
48120
- );
48121
50654
  const derivedTiles = useMemo(
48122
- () => hexes.map((hex) => ({
48123
- x: num(hex.x),
48124
- y: num(hex.y),
48125
- terrain: str(hex.terrain),
48126
- terrainSprite: hex.terrainSprite == null ? void 0 : str(hex.terrainSprite)
50655
+ () => entityTiles.map((t) => ({
50656
+ x: num(t.x),
50657
+ y: num(t.y),
50658
+ terrain: str(t.terrain),
50659
+ terrainSprite: t.terrainSprite == null ? void 0 : str(t.terrainSprite),
50660
+ passable: t.passable !== false
48127
50661
  })),
48128
- [hexes]
50662
+ [entityTiles]
48129
50663
  );
48130
50664
  const tiles = propTiles ?? derivedTiles;
48131
50665
  const baseUnits = useMemo(
48132
- () => propUnits ?? heroes.map((hero) => ({
48133
- id: str(hero.id),
48134
- position: heroPosition(hero),
48135
- name: str(hero.name),
48136
- team: heroOwner(hero) === "enemy" ? "enemy" : "player",
48137
- health: 100,
48138
- maxHealth: 100,
48139
- sprite: hero.sprite == null ? void 0 : str(hero.sprite)
50666
+ () => propUnits ?? entityUnits.map((u) => ({
50667
+ id: str(u.id),
50668
+ position: heroPosition(u),
50669
+ name: str(u.name),
50670
+ // lolo uses `team` field (not `owner`)
50671
+ team: str(u.team) === "enemy" ? "enemy" : "player",
50672
+ health: num(u.health) || 100,
50673
+ maxHealth: num(u.maxHealth) || 100,
50674
+ sprite: u.sprite == null ? void 0 : str(u.sprite)
48140
50675
  })),
48141
- [heroes, propUnits]
50676
+ [entityUnits, propUnits]
50677
+ );
50678
+ const gameUnits = baseUnits;
50679
+ const selectedHero = useMemo(
50680
+ () => gameUnits.find((u) => u.id === selectedHeroId) ?? null,
50681
+ [gameUnits, selectedHeroId]
48142
50682
  );
48143
50683
  const MOVE_SPEED_MS_PER_TILE = 300;
48144
50684
  const movementAnimRef = useRef(null);
@@ -48184,51 +50724,50 @@ function WorldMapBoard({
48184
50724
  const validMoves = useMemo(() => {
48185
50725
  if (!selectedHero || heroMovement(selectedHero) <= 0) return [];
48186
50726
  const sp = heroPosition(selectedHero);
48187
- const sOwner = heroOwner(selectedHero);
50727
+ const sTeam = str(selectedHero.team);
48188
50728
  const range = heroMovement(selectedHero);
48189
50729
  const moves = [];
48190
- hexes.forEach((hex) => {
48191
- const hx = num(hex.x);
48192
- const hy = num(hex.y);
48193
- if (!hexPassable(hex)) return;
48194
- if (hx === sp.x && hy === sp.y) return;
48195
- if (!isInRange(sp, { x: hx, y: hy }, range)) return;
48196
- if (heroes.some((h) => {
48197
- const hp = heroPosition(h);
48198
- return hp.x === hx && hp.y === hy && heroOwner(h) === sOwner;
50730
+ tiles.forEach((t) => {
50731
+ const tx = t.x;
50732
+ const ty = t.y;
50733
+ if (t.passable === false) return;
50734
+ if (tx === sp.x && ty === sp.y) return;
50735
+ if (!isInRange(sp, { x: tx, y: ty }, range)) return;
50736
+ if (gameUnits.some((u) => {
50737
+ const up = u.position ?? { x: u.x ?? -1, y: u.y ?? -1 };
50738
+ return up.x === tx && up.y === ty && str(u.team) === sTeam;
48199
50739
  })) return;
48200
- moves.push({ x: hx, y: hy });
50740
+ moves.push({ x: tx, y: ty });
48201
50741
  });
48202
50742
  return moves;
48203
- }, [selectedHero, hexes, heroes, isInRange]);
50743
+ }, [selectedHero, tiles, gameUnits, isInRange]);
48204
50744
  const attackTargets = useMemo(() => {
48205
50745
  if (!selectedHero || heroMovement(selectedHero) <= 0) return [];
48206
50746
  const sp = heroPosition(selectedHero);
48207
- const sOwner = heroOwner(selectedHero);
50747
+ const sTeam = str(selectedHero.team);
48208
50748
  const range = heroMovement(selectedHero);
48209
- return heroes.filter((h) => heroOwner(h) !== sOwner).filter((h) => isInRange(sp, heroPosition(h), range)).map((h) => heroPosition(h));
48210
- }, [selectedHero, heroes, isInRange]);
48211
- const maxY = Math.max(...hexes.map((h) => num(h.y)), 0);
50749
+ 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 });
50750
+ }, [selectedHero, gameUnits, isInRange]);
50751
+ const maxY = Math.max(...tiles.map((t) => t.y), 0);
48212
50752
  const baseOffsetX = (maxY + 1) * (TILE_WIDTH * scale / 2);
48213
50753
  const tileToScreen = useCallback(
48214
50754
  (tx, ty) => isoToScreen(tx, ty, scale, baseOffsetX),
48215
50755
  [scale, baseOffsetX]
48216
50756
  );
48217
50757
  const hoveredHex = useMemo(
48218
- () => hoveredTile ? hexes.find((h) => num(h.x) === hoveredTile.x && num(h.y) === hoveredTile.y) ?? null : null,
48219
- [hoveredTile, hexes]
50758
+ () => hoveredTile ? tiles.find((t) => t.x === hoveredTile.x && t.y === hoveredTile.y) ?? null : null,
50759
+ [hoveredTile, tiles]
48220
50760
  );
48221
50761
  const hoveredHero = useMemo(
48222
- () => hoveredTile ? heroes.find((h) => {
48223
- const hp = heroPosition(h);
48224
- return hp.x === hoveredTile.x && hp.y === hoveredTile.y;
50762
+ () => hoveredTile ? gameUnits.find((u) => {
50763
+ const up = u.position ?? { x: u.x ?? -1, y: u.y ?? -1 };
50764
+ return up.x === hoveredTile.x && up.y === hoveredTile.y;
48225
50765
  }) ?? null : null,
48226
- [hoveredTile, heroes]
50766
+ [hoveredTile, gameUnits]
48227
50767
  );
48228
50768
  const handleTileClick = useCallback((x, y) => {
48229
50769
  if (movementAnimRef.current) return;
48230
- const hex = hexes.find((h) => num(h.x) === x && num(h.y) === y);
48231
- if (!hex) return;
50770
+ const tile = tiles.find((t) => t.x === x && t.y === y);
48232
50771
  if (tileClickEvent) {
48233
50772
  eventBus.emit(`UI:${tileClickEvent}`, { x, y });
48234
50773
  }
@@ -48239,38 +50778,39 @@ function WorldMapBoard({
48239
50778
  if (heroMoveEvent) {
48240
50779
  eventBus.emit(`UI:${heroMoveEvent}`, { heroId, toX: x, toY: y });
48241
50780
  }
48242
- const feature = str(hex.feature);
50781
+ const feature = tile ? str(tile.feature) : "";
48243
50782
  if (feature && feature !== "none") {
48244
- onFeatureEnter?.(heroId, hex);
50783
+ const tileRow = tile;
50784
+ onFeatureEnter?.(heroId, tileRow);
48245
50785
  if (featureEnterEvent) {
48246
- eventBus.emit(`UI:${featureEnterEvent}`, { heroId, feature, hex });
50786
+ eventBus.emit(`UI:${featureEnterEvent}`, { heroId, feature, hex: tileRow });
48247
50787
  }
48248
50788
  }
48249
50789
  });
48250
50790
  return;
48251
50791
  }
48252
- const enemy = heroes.find((h) => {
48253
- const hp = heroPosition(h);
48254
- return hp.x === x && hp.y === y && heroOwner(h) === "enemy";
50792
+ const enemy = gameUnits.find((u) => {
50793
+ const up = u.position ?? { x: u.x ?? -1, y: u.y ?? -1 };
50794
+ return up.x === x && up.y === y && str(u.team) === "enemy";
48255
50795
  });
48256
50796
  if (selectedHero && enemy && attackTargets.some((t) => t.x === x && t.y === y)) {
48257
50797
  const attackerId = str(selectedHero.id);
48258
- const defenderId = str(enemy.id);
50798
+ const defenderId = enemy.id;
48259
50799
  onBattleEncounter?.(attackerId, defenderId);
48260
50800
  if (battleEncounterEvent) {
48261
50801
  eventBus.emit(`UI:${battleEncounterEvent}`, { attackerId, defenderId });
48262
50802
  }
48263
50803
  }
48264
- }, [hexes, heroes, selectedHero, validMoves, attackTargets, startMoveAnimation, onHeroMove, onFeatureEnter, onBattleEncounter, eventBus, tileClickEvent, heroMoveEvent, featureEnterEvent, battleEncounterEvent]);
50804
+ }, [tiles, gameUnits, selectedHero, validMoves, attackTargets, startMoveAnimation, onHeroMove, onFeatureEnter, onBattleEncounter, eventBus, tileClickEvent, heroMoveEvent, featureEnterEvent, battleEncounterEvent]);
48265
50805
  const handleUnitClick = useCallback((unitId) => {
48266
- const hero = heroes.find((h) => str(h.id) === unitId);
48267
- if (hero && (heroOwner(hero) === "player" || allowMoveAllHeroes)) {
50806
+ const unit = gameUnits.find((u) => u.id === unitId);
50807
+ if (unit && (str(unit.team) === "player" || allowMoveAllHeroes)) {
48268
50808
  onHeroSelect?.(unitId);
48269
50809
  if (heroSelectEvent) {
48270
50810
  eventBus.emit(`UI:${heroSelectEvent}`, { heroId: unitId });
48271
50811
  }
48272
50812
  }
48273
- }, [heroes, onHeroSelect, allowMoveAllHeroes, eventBus, heroSelectEvent]);
50813
+ }, [gameUnits, onHeroSelect, allowMoveAllHeroes, eventBus, heroSelectEvent]);
48274
50814
  const selectHero = useCallback((id) => {
48275
50815
  onHeroSelect?.(id);
48276
50816
  if (heroSelectEvent) {
@@ -48374,48 +50914,48 @@ function WorldMapTemplate({
48374
50914
  }
48375
50915
  );
48376
50916
  }
48377
- var CDN3, DEFAULT_WORLDMAP_TILES, DEFAULT_WORLDMAP_UNITS, DEFAULT_WORLDMAP_FEATURES, DEFAULT_WORLDMAP_MANIFEST;
50917
+ var CDN7, DEFAULT_WORLDMAP_TILES, DEFAULT_WORLDMAP_UNITS, DEFAULT_WORLDMAP_FEATURES, DEFAULT_WORLDMAP_MANIFEST;
48378
50918
  var init_WorldMapTemplate = __esm({
48379
50919
  "components/game/templates/WorldMapTemplate.tsx"() {
48380
50920
  init_WorldMapBoard();
48381
- CDN3 = "https://almadar-kflow-assets.web.app/shared";
50921
+ CDN7 = "https://almadar-kflow-assets.web.app/shared";
48382
50922
  DEFAULT_WORLDMAP_TILES = [
48383
- { x: 0, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
48384
- { x: 1, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
48385
- { x: 2, y: 0, terrain: "water", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
48386
- { x: 3, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
48387
- { x: 4, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
48388
- { x: 0, y: 1, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
48389
- { x: 1, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
48390
- { x: 2, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
48391
- { x: 3, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
48392
- { x: 4, y: 1, terrain: "water", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
48393
- { x: 0, y: 2, terrain: "water", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
48394
- { x: 1, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
48395
- { x: 2, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
48396
- { x: 3, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
48397
- { x: 4, y: 2, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
48398
- { x: 0, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
48399
- { x: 1, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
48400
- { x: 2, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
48401
- { x: 3, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
48402
- { x: 4, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
48403
- { x: 0, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
48404
- { x: 1, y: 4, terrain: "water", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
48405
- { x: 2, y: 4, terrain: "grass", passable: true, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
48406
- { x: 3, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
48407
- { x: 4, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN3}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` }
50923
+ { x: 0, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50924
+ { x: 1, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50925
+ { x: 2, y: 0, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
50926
+ { x: 3, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50927
+ { x: 4, y: 0, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50928
+ { x: 0, y: 1, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50929
+ { x: 1, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
50930
+ { x: 2, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
50931
+ { x: 3, y: 1, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
50932
+ { x: 4, y: 1, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
50933
+ { x: 0, y: 2, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
50934
+ { x: 1, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
50935
+ { x: 2, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
50936
+ { x: 3, y: 2, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
50937
+ { x: 4, y: 2, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50938
+ { x: 0, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50939
+ { x: 1, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
50940
+ { x: 2, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
50941
+ { x: 3, y: 3, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_03.png` },
50942
+ { x: 4, y: 3, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50943
+ { x: 0, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50944
+ { x: 1, y: 4, terrain: "water", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png` },
50945
+ { x: 2, y: 4, terrain: "grass", passable: true, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png` },
50946
+ { x: 3, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` },
50947
+ { x: 4, y: 4, terrain: "mountain", passable: false, terrainSprite: `${CDN7}/isometric-blocks/PNG/Platformer tiles/platformerTile_13.png` }
48408
50948
  ];
48409
50949
  DEFAULT_WORLDMAP_UNITS = [
48410
- { 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` },
48411
- { 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` }
50950
+ { 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` },
50951
+ { 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` }
48412
50952
  ];
48413
50953
  DEFAULT_WORLDMAP_FEATURES = [
48414
- { id: "f1", x: 2, y: 2, type: "capital", sprite: `${CDN3}/scenes/world/capital.png` },
48415
- { id: "f2", x: 4, y: 2, type: "power_node", sprite: `${CDN3}/world-map/power_node.png` }
50954
+ { id: "f1", x: 2, y: 2, type: "capital", sprite: `${CDN7}/scenes/world/capital.png` },
50955
+ { id: "f2", x: 4, y: 2, type: "power_node", sprite: `${CDN7}/world-map/power_node.png` }
48416
50956
  ];
48417
50957
  DEFAULT_WORLDMAP_MANIFEST = {
48418
- baseUrl: CDN3,
50958
+ baseUrl: CDN7,
48419
50959
  terrains: {
48420
50960
  grass: "/isometric-blocks/PNG/Platformer tiles/platformerTile_01.png",
48421
50961
  water: "/isometric-blocks/PNG/Platformer tiles/platformerTile_11.png",
@@ -48544,7 +51084,7 @@ function lazyThree(name, loader) {
48544
51084
  ThreeWrapper.displayName = `Lazy(${name})`;
48545
51085
  return ThreeWrapper;
48546
51086
  }
48547
- var ThreeBoundary, FeatureRenderer, GameCanvas3D, GameCanvas3DBattleTemplate, GameCanvas3DCastleTemplate, GameCanvas3DWorldMapTemplate, COMPONENT_REGISTRY;
51087
+ var ThreeBoundary, FeatureRenderer, GameCanvas3D2, GameCanvas3DBattleTemplate, GameCanvas3DCastleTemplate, GameCanvas3DWorldMapTemplate, COMPONENT_REGISTRY;
48548
51088
  var init_component_registry_generated = __esm({
48549
51089
  "components/core/organisms/component-registry.generated.ts"() {
48550
51090
  init_AboutPageTemplate();
@@ -48654,6 +51194,7 @@ var init_component_registry_generated = __esm({
48654
51194
  init_FormSectionHeader();
48655
51195
  init_GameAudioProvider();
48656
51196
  init_GameAudioToggle();
51197
+ init_GameBoard3D();
48657
51198
  init_GameCanvas2D();
48658
51199
  init_GameHud();
48659
51200
  init_GameMenu();
@@ -48711,7 +51252,9 @@ var init_component_registry_generated = __esm({
48711
51252
  init_PageHeader();
48712
51253
  init_Pagination();
48713
51254
  init_PatternTile();
51255
+ init_PlatformerBoard();
48714
51256
  init_PlatformerCanvas();
51257
+ init_PlatformerTemplate();
48715
51258
  init_Popover();
48716
51259
  init_PositionedCanvas();
48717
51260
  init_PowerupSlots();
@@ -48735,6 +51278,8 @@ var init_component_registry_generated = __esm({
48735
51278
  init_ResourceBar();
48736
51279
  init_ResourceCounter();
48737
51280
  init_RichBlockEditor();
51281
+ init_RoguelikeBoard();
51282
+ init_RoguelikeTemplate();
48738
51283
  init_RuntimeDebugger2();
48739
51284
  init_ScaledDiagram();
48740
51285
  init_ScoreBoard();
@@ -48807,6 +51352,8 @@ var init_component_registry_generated = __esm({
48807
51352
  init_Toast();
48808
51353
  init_ToastSlot();
48809
51354
  init_Tooltip();
51355
+ init_TowerDefenseBoard();
51356
+ init_TowerDefenseTemplate();
48810
51357
  init_TraitFrame();
48811
51358
  init_TraitSlot();
48812
51359
  init_TrendIndicator();
@@ -48851,7 +51398,7 @@ var init_component_registry_generated = __esm({
48851
51398
  }
48852
51399
  };
48853
51400
  FeatureRenderer = lazyThree("FeatureRenderer", () => import('@almadar/ui/components/molecules/game/three'));
48854
- GameCanvas3D = lazyThree("GameCanvas3D", () => import('@almadar/ui/components/molecules/game/three'));
51401
+ GameCanvas3D2 = lazyThree("GameCanvas3D", () => import('@almadar/ui/components/molecules/game/three'));
48855
51402
  GameCanvas3DBattleTemplate = lazyThree("GameCanvas3DBattleTemplate", () => import('@almadar/ui/components/molecules/game/three'));
48856
51403
  GameCanvas3DCastleTemplate = lazyThree("GameCanvas3DCastleTemplate", () => import('@almadar/ui/components/molecules/game/three'));
48857
51404
  GameCanvas3DWorldMapTemplate = lazyThree("GameCanvas3DWorldMapTemplate", () => import('@almadar/ui/components/molecules/game/three'));
@@ -48970,8 +51517,9 @@ var init_component_registry_generated = __esm({
48970
51517
  "FormSectionHeader": FormSectionHeader,
48971
51518
  "GameAudioProvider": GameAudioProvider,
48972
51519
  "GameAudioToggle": GameAudioToggle,
51520
+ "GameBoard3D": GameBoard3D,
48973
51521
  "GameCanvas2D": GameCanvas2D,
48974
- "GameCanvas3D": GameCanvas3D,
51522
+ "GameCanvas3D": GameCanvas3D2,
48975
51523
  "GameCanvas3DBattleTemplate": GameCanvas3DBattleTemplate,
48976
51524
  "GameCanvas3DCastleTemplate": GameCanvas3DCastleTemplate,
48977
51525
  "GameCanvas3DWorldMapTemplate": GameCanvas3DWorldMapTemplate,
@@ -49033,7 +51581,9 @@ var init_component_registry_generated = __esm({
49033
51581
  "PageHeader": PageHeader,
49034
51582
  "Pagination": Pagination,
49035
51583
  "PatternTile": PatternTile,
51584
+ "PlatformerBoard": PlatformerBoard,
49036
51585
  "PlatformerCanvas": PlatformerCanvas,
51586
+ "PlatformerTemplate": PlatformerTemplate,
49037
51587
  "Popover": Popover,
49038
51588
  "PositionedCanvas": PositionedCanvas,
49039
51589
  "PowerupSlots": PowerupSlots,
@@ -49057,6 +51607,8 @@ var init_component_registry_generated = __esm({
49057
51607
  "ResourceBar": ResourceBar,
49058
51608
  "ResourceCounter": ResourceCounter,
49059
51609
  "RichBlockEditor": RichBlockEditor,
51610
+ "RoguelikeBoard": RoguelikeBoard,
51611
+ "RoguelikeTemplate": RoguelikeTemplate,
49060
51612
  "RuntimeDebugger": RuntimeDebugger,
49061
51613
  "ScaledDiagram": ScaledDiagram,
49062
51614
  "ScoreBoard": ScoreBoard,
@@ -49132,6 +51684,8 @@ var init_component_registry_generated = __esm({
49132
51684
  "Toast": Toast,
49133
51685
  "ToastSlot": ToastSlot,
49134
51686
  "Tooltip": Tooltip,
51687
+ "TowerDefenseBoard": TowerDefenseBoard,
51688
+ "TowerDefenseTemplate": TowerDefenseTemplate,
49135
51689
  "TraitFrame": TraitFrame,
49136
51690
  "TraitSlot": TraitSlot,
49137
51691
  "TrendIndicator": TrendIndicator,
@@ -50694,6 +53248,10 @@ init_UncontrolledBattleBoard();
50694
53248
  init_useBattleState();
50695
53249
  init_WorldMapBoard();
50696
53250
  init_CastleBoard();
53251
+ init_PlatformerBoard();
53252
+ init_TowerDefenseBoard();
53253
+ init_RoguelikeBoard();
53254
+ init_GameBoard3D();
50697
53255
  init_TraitStateViewer();
50698
53256
  init_TraitSlot();
50699
53257
 
@@ -51266,6 +53824,9 @@ init_GameShell();
51266
53824
  init_BattleTemplate();
51267
53825
  init_CastleTemplate();
51268
53826
  init_WorldMapTemplate();
53827
+ init_PlatformerTemplate();
53828
+ init_TowerDefenseTemplate();
53829
+ init_RoguelikeTemplate();
51269
53830
  init_LandingPageTemplate();
51270
53831
  init_PricingPageTemplate();
51271
53832
  init_FeatureDetailPageTemplate();
@@ -51274,4 +53835,4 @@ init_AboutPageTemplate();
51274
53835
  // components/index.ts
51275
53836
  init_cn();
51276
53837
 
51277
- export { ALL_PRESETS, AR_BOOK_FIELDS, AboutPageTemplate, Accordion, ActionButton, ActionButtons, Card2 as ActionCard, ActionPalette, ActionTile, ActivationBlock, Alert, AnimatedCounter, AnimatedGraphic, AnimatedReveal, ArticleSection, Aside, AssetPicker, AuthLayout, Avatar, Badge, BattleBoard, BattleTemplate, BehaviorView, BloomQuizBlock, BookChapterView, BookCoverPage, BookNavBar, BookTableOfContents, BookViewer, Box, BranchingLogicBuilder, Breadcrumb, BuilderBoard, Button, ButtonGroup, CTABanner, CalendarGrid, CanvasEffect, Card, CardBody, CardContent, CardFooter, CardGrid, CardHeader, CardTitle, Carousel, CaseStudyCard, CaseStudyOrganism, CastleBoard, CastleTemplate, Center, Chart, ChartLegend, Checkbox, ChoiceButton, ClassifierBoard, Coachmark, CodeBlock, CodeRunnerPanel, CollapsibleSection, CombatLog, ComboCounter, CommunityLinks, ConditionalWrapper, ConfettiEffect, ConfirmDialog, ConnectionBlock, Container, ContentRenderer, ContentSection, ControlButton, CounterTemplate, CraftingRecipe, DEFAULT_LIKERT_OPTIONS, DEFAULT_MATRIX_COLUMNS, DIAMOND_TOP_Y, DPad, DamageNumber, DashboardGrid, DashboardLayout, DataGrid, DataList, DataTable, DateRangePicker, DateRangeSelector, DayCell, DebuggerBoard, DetailPanel, Dialog, DialogueBox, DialogueBubble, Divider, DocBreadcrumb, DocPagination, DocSearch, DocSidebar, DocTOC, DocumentViewer, StateMachineView as DomStateMachineVisualizer, Drawer, DrawerSlot, EdgeDecoration, EditorCheckbox, EditorSelect, EditorSlider, EditorTextInput, EditorToolbar, EmptyState, EnemyPlate, EntityDisplayEvents, ErrorBoundary, ErrorState, EventHandlerBoard, EventLog, FEATURE_COLORS, FEATURE_TYPES, FLOOR_HEIGHT, FeatureCard, FeatureDetailPageTemplate, FeatureGrid, FeatureGridOrganism, FileTree, FilterGroup, FilterPill, Flex, FlipCard, FlipContainer, FloatingActionButton, Form, FormActions, FormField, FormLayout, FormSection, FormSectionHeader, GameAudioContext, GameAudioProvider, GameAudioToggle, GameCanvas2D, GameHud, GameMenu, GameOverScreen, GameShell, GameTemplate, GenericAppTemplate, GeometricPattern, GradientDivider, GraphCanvas, GraphView, Grid, GridPicker, HStack, Header, HealthBar, HealthPanel, HeroOrganism, HeroSection, IDENTITY_BOOK_FIELDS, Icon, IconPicker, InfiniteScrollSentinel, Input, InputGroup, InstallBox, InventoryGrid, InventoryPanel, IsometricCanvas, ItemSlot, JazariStateMachine, JsonTreeEditor, Label, LandingPageTemplate, LawReferenceTooltip, Lightbox, LikertScale, LineChart2 as LineChart, List3 as List, LoadingState, MapView, MarkdownContent, MarketingFooter, MarketingStatCard, MasterDetail, MasterDetailLayout, MatrixQuestion, MediaGallery, Menu, Meter, MiniMap, Modal, ModalSlot, ModuleCard, Navigation, NegotiatorBoard, NodeSlotEditor, NotifyListener, NumberStepper, ObjectRulePanel, OnboardingSpotlight, OptionConstraintGroup, StateMachineView as OrbitalStateMachineView, OrbitalVisualization, Overlay, PageHeader, Pagination, PatternTile, PhysicsManager, PlatformerCanvas, Popover, PositionedCanvas, PowerupSlots, PricingCard, PricingGrid, PricingOrganism, PricingPageTemplate, ProgressBar, ProgressDots, PropertyInspector, PullQuote, PullToRefresh, QrScanner, QuestTracker, QuizBlock, Radio, RangeSlider, ReflectionBlock, RelationSelect, RepeatableFormSection, ReplyTree, ResourceBar, ResourceCounter, RichBlockEditor, RuleEditor, RuntimeDebugger, SHEET_COLUMNS, SPRITE_SHEET_LAYOUT, ScaledDiagram, ScoreBoard, ScoreDisplay, SearchInput, Section, SectionHeader, SegmentRenderer, Select, SequenceBar, SequencerBoard, ServiceCatalog, ShowcaseCard, ShowcaseOrganism, SidePanel, Sidebar, SignaturePad, SimpleGrid, SimulationCanvas, SimulationControls, SimulationGraph, SimulatorBoard, Skeleton, SlotContentRenderer, SocialProof, SortableList, Spacer, Sparkline, Spinner, Split, SplitPane, SplitSection, Sprite, Stack, StarRating, StatBadge, StatCard, StatDisplay, StateArchitectBoard, StateIndicator, StateJsonView, StateMachineView, StateNode2 as StateNode, StatsGrid, StatsOrganism, StatusBar, StatusDot, StatusEffect, StepFlow, StepFlowOrganism, SvgBranch, SvgConnection, SvgFlow, SvgGrid, SvgLobe, SvgMesh, SvgMorph, SvgNode, SvgPulse, SvgRing, SvgShield, SvgStack, SwipeableRow, Switch, TERRAIN_COLORS, TILE_HEIGHT, TILE_WIDTH, TabbedContainer, TableView, Tabs, TagCloud, TagInput, TeamCard, TeamOrganism, TerrainPalette, TextHighlight, Textarea, ThemeSelector, ThemeToggle, TimeSlotCell, Timeline, TimerDisplay, Toast, ToastSlot, Tooltip, TraitFrame, TraitSlot, TraitStateViewer, TransitionArrow, TrendIndicator, TurnIndicator, TurnPanel, TypewriterText, Typography, UISlotComponent, UISlotRenderer, UncontrolledBattleBoard, UnitCommandBar, UploadDropZone, VStack, VariablePanel, VersionDiff, ViolationAlert, VoteStack, WaypointMarker, WizardContainer, WizardNavigation, WizardProgress, WorldMapBoard, WorldMapTemplate, XPBar, applyTemporaryEffect, calculateAttackTargets, calculateDamage, calculateValidMoves, cn, combatAnimations, combatClasses, combatEffects, createInitialGameState, createUnitAnimationState, drawSprite, generateCombatMessage, getCurrentFrame, getTileDimensions, inferDirection, isoToScreen, mapBookData, parseLessonSegments, parseMarkdownWithCodeBlocks, pendulum, projectileMotion, resolveFieldMap, resolveFrame, resolveSheetDirection, screenToIso, springOscillator, tickAnimationState, toCodeLanguage, transitionAnimation, useAnchorRect, useBattleState, useCamera, useGameAudio, useGameAudioContext, useImageCache, usePhysics2D, useSpriteAnimations };
53838
+ export { ALL_PRESETS, AR_BOOK_FIELDS, AboutPageTemplate, Accordion, ActionButton, ActionButtons, Card2 as ActionCard, ActionPalette, ActionTile, ActivationBlock, Alert, AnimatedCounter, AnimatedGraphic, AnimatedReveal, ArticleSection, Aside, AssetPicker, AuthLayout, Avatar, Badge, BattleBoard, BattleTemplate, BehaviorView, BloomQuizBlock, BookChapterView, BookCoverPage, BookNavBar, BookTableOfContents, BookViewer, Box, BranchingLogicBuilder, Breadcrumb, BuilderBoard, Button, ButtonGroup, CTABanner, CalendarGrid, CanvasEffect, Card, CardBody, CardContent, CardFooter, CardGrid, CardHeader, CardTitle, Carousel, CaseStudyCard, CaseStudyOrganism, CastleBoard, CastleTemplate, Center, Chart, ChartLegend, Checkbox, ChoiceButton, ClassifierBoard, Coachmark, CodeBlock, CodeRunnerPanel, CollapsibleSection, CombatLog, ComboCounter, CommunityLinks, ConditionalWrapper, ConfettiEffect, ConfirmDialog, ConnectionBlock, Container, ContentRenderer, ContentSection, ControlButton, CounterTemplate, CraftingRecipe, DEFAULT_LIKERT_OPTIONS, DEFAULT_MATRIX_COLUMNS, DIAMOND_TOP_Y, DPad, DamageNumber, DashboardGrid, DashboardLayout, DataGrid, DataList, DataTable, DateRangePicker, DateRangeSelector, DayCell, DebuggerBoard, DetailPanel, Dialog, DialogueBox, DialogueBubble, Divider, DocBreadcrumb, DocPagination, DocSearch, DocSidebar, DocTOC, DocumentViewer, StateMachineView as DomStateMachineVisualizer, Drawer, DrawerSlot, EdgeDecoration, EditorCheckbox, EditorSelect, EditorSlider, EditorTextInput, EditorToolbar, EmptyState, EnemyPlate, EntityDisplayEvents, ErrorBoundary, ErrorState, EventHandlerBoard, EventLog, FEATURE_COLORS, FEATURE_TYPES, FLOOR_HEIGHT, FeatureCard, FeatureDetailPageTemplate, FeatureGrid, FeatureGridOrganism, FileTree, FilterGroup, FilterPill, Flex, FlipCard, FlipContainer, FloatingActionButton, Form, FormActions, FormField, FormLayout, FormSection, FormSectionHeader, GameAudioContext, GameAudioProvider, GameAudioToggle, GameBoard3D, GameCanvas2D, GameHud, GameMenu, GameOverScreen, GameShell, GameTemplate, GenericAppTemplate, GeometricPattern, GradientDivider, GraphCanvas, GraphView, Grid, GridPicker, HStack, Header, HealthBar, HealthPanel, HeroOrganism, HeroSection, IDENTITY_BOOK_FIELDS, Icon, IconPicker, InfiniteScrollSentinel, Input, InputGroup, InstallBox, InventoryGrid, InventoryPanel, IsometricCanvas, ItemSlot, JazariStateMachine, JsonTreeEditor, Label, LandingPageTemplate, LawReferenceTooltip, Lightbox, LikertScale, LineChart2 as LineChart, List3 as List, LoadingState, MapView, MarkdownContent, MarketingFooter, MarketingStatCard, MasterDetail, MasterDetailLayout, MatrixQuestion, MediaGallery, Menu, Meter, MiniMap, Modal, ModalSlot, ModuleCard, Navigation, NegotiatorBoard, NodeSlotEditor, NotifyListener, NumberStepper, ObjectRulePanel, OnboardingSpotlight, OptionConstraintGroup, StateMachineView as OrbitalStateMachineView, OrbitalVisualization, Overlay, PageHeader, Pagination, PatternTile, PhysicsManager, PlatformerBoard, PlatformerCanvas, PlatformerTemplate, Popover, PositionedCanvas, PowerupSlots, PricingCard, PricingGrid, PricingOrganism, PricingPageTemplate, ProgressBar, ProgressDots, PropertyInspector, PullQuote, PullToRefresh, QrScanner, QuestTracker, QuizBlock, Radio, RangeSlider, ReflectionBlock, RelationSelect, RepeatableFormSection, ReplyTree, ResourceBar, ResourceCounter, RichBlockEditor, RoguelikeBoard, RoguelikeTemplate, RuleEditor, RuntimeDebugger, SHEET_COLUMNS, SPRITE_SHEET_LAYOUT, ScaledDiagram, ScoreBoard, ScoreDisplay, SearchInput, Section, SectionHeader, SegmentRenderer, Select, SequenceBar, SequencerBoard, ServiceCatalog, ShowcaseCard, ShowcaseOrganism, SidePanel, Sidebar, SignaturePad, SimpleGrid, SimulationCanvas, SimulationControls, SimulationGraph, SimulatorBoard, Skeleton, SlotContentRenderer, SocialProof, SortableList, Spacer, Sparkline, Spinner, Split, SplitPane, SplitSection, Sprite, Stack, StarRating, StatBadge, StatCard, StatDisplay, StateArchitectBoard, StateIndicator, StateJsonView, StateMachineView, StateNode2 as StateNode, StatsGrid, StatsOrganism, StatusBar, StatusDot, StatusEffect, StepFlow, StepFlowOrganism, SvgBranch, SvgConnection, SvgFlow, SvgGrid, SvgLobe, SvgMesh, SvgMorph, SvgNode, SvgPulse, SvgRing, SvgShield, SvgStack, SwipeableRow, Switch, TERRAIN_COLORS, TILE_HEIGHT, TILE_WIDTH, TabbedContainer, TableView, Tabs, TagCloud, TagInput, TeamCard, TeamOrganism, TerrainPalette, TextHighlight, Textarea, ThemeSelector, ThemeToggle, TimeSlotCell, Timeline, TimerDisplay, Toast, ToastSlot, Tooltip, TowerDefenseBoard, TowerDefenseTemplate, TraitFrame, TraitSlot, TraitStateViewer, TransitionArrow, TrendIndicator, TurnIndicator, TurnPanel, TypewriterText, Typography, UISlotComponent, UISlotRenderer, UncontrolledBattleBoard, UnitCommandBar, UploadDropZone, VStack, VariablePanel, VersionDiff, ViolationAlert, VoteStack, WaypointMarker, WizardContainer, WizardNavigation, WizardProgress, WorldMapBoard, WorldMapTemplate, XPBar, applyTemporaryEffect, calculateAttackTargets, calculateDamage, calculateValidMoves, cn, combatAnimations, combatClasses, combatEffects, createInitialGameState, createUnitAnimationState, drawSprite, generateCombatMessage, getCurrentFrame, getTileDimensions, inferDirection, isoToScreen, mapBookData, parseLessonSegments, parseMarkdownWithCodeBlocks, pendulum, projectileMotion, resolveFieldMap, resolveFrame, resolveSheetDirection, screenToIso, springOscillator, tickAnimationState, toCodeLanguage, transitionAnimation, useAnchorRect, useBattleState, useCamera, useGameAudio, useGameAudioContext, useImageCache, usePhysics2D, useSpriteAnimations };