@almadar/ui 5.47.0 → 5.48.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.
@@ -7413,6 +7413,7 @@ var init_Sprite = __esm({
7413
7413
  }
7414
7414
  });
7415
7415
  function StateIndicator({
7416
+ assetUrl = DEFAULT_ASSET_URL,
7416
7417
  state = "idle",
7417
7418
  label,
7418
7419
  size = "md",
@@ -7435,18 +7436,29 @@ function StateIndicator({
7435
7436
  className
7436
7437
  ),
7437
7438
  children: [
7438
- /* @__PURE__ */ jsxRuntime.jsx(Box, { as: "span", children: typeof config.icon === "string" ? /^[a-zA-Z0-9-]+$/.test(config.icon) ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: config.icon }) : config.icon : /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: config.icon }) }),
7439
+ /* @__PURE__ */ jsxRuntime.jsx(Box, { as: "span", children: assetUrl ? /* @__PURE__ */ jsxRuntime.jsx(
7440
+ "img",
7441
+ {
7442
+ src: assetUrl,
7443
+ alt: displayLabel,
7444
+ width: 16,
7445
+ height: 16,
7446
+ style: { imageRendering: "pixelated", objectFit: "contain" },
7447
+ className: "flex-shrink-0"
7448
+ }
7449
+ ) : typeof config.icon === "string" ? /^[a-zA-Z0-9-]+$/.test(config.icon) ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: config.icon }) : config.icon : /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: config.icon }) }),
7439
7450
  /* @__PURE__ */ jsxRuntime.jsx(Box, { as: "span", children: displayLabel })
7440
7451
  ]
7441
7452
  }
7442
7453
  );
7443
7454
  }
7444
- var DEFAULT_STATE_STYLES, DEFAULT_STYLE, SIZE_CLASSES;
7455
+ var DEFAULT_ASSET_URL, DEFAULT_STATE_STYLES, DEFAULT_STYLE, SIZE_CLASSES;
7445
7456
  var init_StateIndicator = __esm({
7446
7457
  "components/game/atoms/StateIndicator.tsx"() {
7447
7458
  init_Box();
7448
7459
  init_Icon();
7449
7460
  init_cn();
7461
+ DEFAULT_ASSET_URL = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
7450
7462
  DEFAULT_STATE_STYLES = {
7451
7463
  idle: { icon: "\u23F8", bgClass: "bg-muted" },
7452
7464
  active: { icon: "\u25B6", bgClass: "bg-success" },
@@ -7521,6 +7533,7 @@ var init_TimerDisplay = __esm({
7521
7533
  }
7522
7534
  });
7523
7535
  function ResourceCounter({
7536
+ assetUrl = DEFAULT_ASSET_URL2,
7524
7537
  icon,
7525
7538
  label = "Gold",
7526
7539
  value = 250,
@@ -7540,7 +7553,17 @@ function ResourceCounter({
7540
7553
  className
7541
7554
  ),
7542
7555
  children: [
7543
- icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("flex-shrink-0", sizes.icon), children: typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: icon }) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon }) }),
7556
+ assetUrl ? /* @__PURE__ */ jsxRuntime.jsx(
7557
+ "img",
7558
+ {
7559
+ src: assetUrl,
7560
+ alt: label,
7561
+ width: sizes.img,
7562
+ height: sizes.img,
7563
+ style: { imageRendering: "pixelated", objectFit: "contain" },
7564
+ className: "flex-shrink-0"
7565
+ }
7566
+ ) : icon ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("flex-shrink-0", sizes.icon), children: typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: icon }) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon }) }) : null,
7544
7567
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: label }),
7545
7568
  /* @__PURE__ */ jsxRuntime.jsxs("span", { className: cn("font-bold tabular-nums", color && (color in colorTokenClasses2 ? colorTokenClasses2[color] : color)), children: [
7546
7569
  value,
@@ -7553,7 +7576,7 @@ function ResourceCounter({
7553
7576
  }
7554
7577
  );
7555
7578
  }
7556
- var colorTokenClasses2, sizeMap5;
7579
+ var colorTokenClasses2, DEFAULT_ASSET_URL2, sizeMap5;
7557
7580
  var init_ResourceCounter = __esm({
7558
7581
  "components/game/atoms/ResourceCounter.tsx"() {
7559
7582
  init_cn();
@@ -7566,16 +7589,17 @@ var init_ResourceCounter = __esm({
7566
7589
  error: "text-error",
7567
7590
  muted: "text-muted-foreground"
7568
7591
  };
7592
+ DEFAULT_ASSET_URL2 = "https://almadar-kflow-assets.web.app/shared/world-map/gold_mine.png";
7569
7593
  sizeMap5 = {
7570
- sm: { wrapper: "text-xs gap-1 px-1.5 py-0.5", icon: "text-sm" },
7571
- md: { wrapper: "text-sm gap-1.5 px-2 py-1", icon: "text-base" },
7572
- lg: { wrapper: "text-base gap-2 px-3 py-1.5", icon: "text-lg" }
7594
+ sm: { wrapper: "text-xs gap-1 px-1.5 py-0.5", icon: "text-sm", img: 16 },
7595
+ md: { wrapper: "text-sm gap-1.5 px-2 py-1", icon: "text-base", img: 20 },
7596
+ lg: { wrapper: "text-base gap-2 px-3 py-1.5", icon: "text-lg", img: 28 }
7573
7597
  };
7574
7598
  ResourceCounter.displayName = "ResourceCounter";
7575
7599
  }
7576
7600
  });
7577
7601
  function ItemSlot({
7578
- assetUrl = DEFAULT_ASSET_URL,
7602
+ assetUrl = DEFAULT_ASSET_URL3,
7579
7603
  icon = "sword",
7580
7604
  label = "Iron Sword",
7581
7605
  quantity,
@@ -7634,7 +7658,7 @@ function ItemSlot({
7634
7658
  }
7635
7659
  );
7636
7660
  }
7637
- var sizeMap6, rarityBorderMap, rarityGlowMap, DEFAULT_ASSET_URL, assetSizeMap;
7661
+ var sizeMap6, rarityBorderMap, rarityGlowMap, DEFAULT_ASSET_URL3, assetSizeMap;
7638
7662
  var init_ItemSlot = __esm({
7639
7663
  "components/game/atoms/ItemSlot.tsx"() {
7640
7664
  "use client";
@@ -7659,7 +7683,7 @@ var init_ItemSlot = __esm({
7659
7683
  epic: "shadow-lg",
7660
7684
  legendary: "shadow-lg"
7661
7685
  };
7662
- DEFAULT_ASSET_URL = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
7686
+ DEFAULT_ASSET_URL3 = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
7663
7687
  assetSizeMap = {
7664
7688
  sm: 28,
7665
7689
  md: 40,
@@ -7732,6 +7756,7 @@ function getComboScale(combo) {
7732
7756
  return "";
7733
7757
  }
7734
7758
  function ComboCounter({
7759
+ assetUrl = DEFAULT_ASSET_URL4,
7735
7760
  combo = 5,
7736
7761
  multiplier,
7737
7762
  streak,
@@ -7751,6 +7776,17 @@ function ComboCounter({
7751
7776
  className
7752
7777
  ),
7753
7778
  children: [
7779
+ assetUrl && /* @__PURE__ */ jsxRuntime.jsx(
7780
+ "img",
7781
+ {
7782
+ src: assetUrl,
7783
+ alt: "combo",
7784
+ width: 24,
7785
+ height: 24,
7786
+ style: { imageRendering: "pixelated", objectFit: "contain" },
7787
+ className: "flex-shrink-0 mb-0.5"
7788
+ }
7789
+ ),
7754
7790
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("font-black tabular-nums leading-none", sizes.combo, getComboIntensity(combo)), children: combo }),
7755
7791
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("font-bold uppercase tracking-wider text-muted-foreground", sizes.label), children: "combo" }),
7756
7792
  multiplier != null && multiplier > 1 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: cn("font-bold text-warning tabular-nums", sizes.multiplier), children: [
@@ -7765,10 +7801,11 @@ function ComboCounter({
7765
7801
  }
7766
7802
  );
7767
7803
  }
7768
- var sizeMap8;
7804
+ var DEFAULT_ASSET_URL4, sizeMap8;
7769
7805
  var init_ComboCounter = __esm({
7770
7806
  "components/game/atoms/ComboCounter.tsx"() {
7771
7807
  init_cn();
7808
+ DEFAULT_ASSET_URL4 = "https://almadar-kflow-assets.web.app/shared/effects/flash/flash00.png";
7772
7809
  sizeMap8 = {
7773
7810
  sm: { combo: "text-lg", label: "text-xs", multiplier: "text-xs" },
7774
7811
  md: { combo: "text-2xl", label: "text-xs", multiplier: "text-sm" },
@@ -7862,6 +7899,7 @@ var init_XPBar = __esm({
7862
7899
  }
7863
7900
  });
7864
7901
  function WaypointMarker({
7902
+ assetUrl = DEFAULT_ASSET_URL5,
7865
7903
  label,
7866
7904
  icon,
7867
7905
  active = true,
@@ -7900,7 +7938,17 @@ function WaypointMarker({
7900
7938
  active && !completed && "bg-info text-foreground",
7901
7939
  !active && !completed && "bg-muted"
7902
7940
  ),
7903
- children: completed ? checkIcon : icon && (typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: icon }) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon }))
7941
+ children: completed ? checkIcon : assetUrl ? /* @__PURE__ */ jsxRuntime.jsx(
7942
+ "img",
7943
+ {
7944
+ src: assetUrl,
7945
+ alt: label,
7946
+ width: sizes.img,
7947
+ height: sizes.img,
7948
+ style: { imageRendering: "pixelated", objectFit: "contain" },
7949
+ className: "flex-shrink-0"
7950
+ }
7951
+ ) : icon ? typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: icon }) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon }) : null
7904
7952
  }
7905
7953
  )
7906
7954
  ] }),
@@ -7917,15 +7965,16 @@ function WaypointMarker({
7917
7965
  )
7918
7966
  ] });
7919
7967
  }
7920
- var sizeMap10, checkIcon;
7968
+ var DEFAULT_ASSET_URL5, sizeMap10, checkIcon;
7921
7969
  var init_WaypointMarker = __esm({
7922
7970
  "components/game/atoms/WaypointMarker.tsx"() {
7923
7971
  init_cn();
7924
7972
  init_Icon();
7973
+ DEFAULT_ASSET_URL5 = "https://almadar-kflow-assets.web.app/shared/world-map/battle_marker.png";
7925
7974
  sizeMap10 = {
7926
- sm: { dot: "w-4 h-4", ring: "w-6 h-6", label: "text-xs mt-1" },
7927
- md: { dot: "w-6 h-6", ring: "w-8 h-8", label: "text-sm mt-1.5" },
7928
- lg: { dot: "w-8 h-8", ring: "w-10 h-10", label: "text-base mt-2" }
7975
+ sm: { dot: "w-4 h-4", ring: "w-6 h-6", label: "text-xs mt-1", img: 12 },
7976
+ md: { dot: "w-6 h-6", ring: "w-8 h-8", label: "text-sm mt-1.5", img: 18 },
7977
+ lg: { dot: "w-8 h-8", ring: "w-10 h-10", label: "text-base mt-2", img: 24 }
7929
7978
  };
7930
7979
  checkIcon = /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 3, className: "w-3/5 h-3/5", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M5 13l4 4L19 7", strokeLinecap: "round", strokeLinejoin: "round" }) });
7931
7980
  WaypointMarker.displayName = "WaypointMarker";
@@ -7938,6 +7987,7 @@ function formatDuration(seconds) {
7938
7987
  return `${Math.round(seconds)}s`;
7939
7988
  }
7940
7989
  function StatusEffect({
7990
+ assetUrl = DEFAULT_ASSET_URL6,
7941
7991
  icon = "shield",
7942
7992
  label = "Shield",
7943
7993
  duration = 30,
@@ -7958,7 +8008,17 @@ function StatusEffect({
7958
8008
  ),
7959
8009
  title: label,
7960
8010
  children: [
7961
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("flex items-center justify-center", sizes.icon), children: typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: icon, size: "sm" }) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon, size: "sm" }) }),
8011
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("flex items-center justify-center", sizes.icon), children: assetUrl ? /* @__PURE__ */ jsxRuntime.jsx(
8012
+ "img",
8013
+ {
8014
+ src: assetUrl,
8015
+ alt: label,
8016
+ width: sizes.img,
8017
+ height: sizes.img,
8018
+ style: { imageRendering: "pixelated", objectFit: "contain" },
8019
+ className: "flex-shrink-0"
8020
+ }
8021
+ ) : typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: icon, size: "sm" }) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon, size: "sm" }) }),
7962
8022
  duration !== void 0 && /* @__PURE__ */ jsxRuntime.jsx(
7963
8023
  "span",
7964
8024
  {
@@ -7985,15 +8045,16 @@ function StatusEffect({
7985
8045
  label && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground mt-0.5 text-center whitespace-nowrap", children: label })
7986
8046
  ] });
7987
8047
  }
7988
- var sizeMap11, variantStyles7;
8048
+ var DEFAULT_ASSET_URL6, sizeMap11, variantStyles7;
7989
8049
  var init_StatusEffect = __esm({
7990
8050
  "components/game/atoms/StatusEffect.tsx"() {
7991
8051
  init_cn();
7992
8052
  init_Icon();
8053
+ DEFAULT_ASSET_URL6 = "https://almadar-kflow-assets.web.app/shared/effects/particles/flame_01.png";
7993
8054
  sizeMap11 = {
7994
- sm: { container: "w-8 h-8", icon: "text-sm", badge: "text-xs -top-1 -right-1 w-4 h-4", timer: "text-[9px]" },
7995
- md: { container: "w-10 h-10", icon: "text-base", badge: "text-xs -top-1 -right-1 w-5 h-5", timer: "text-xs" },
7996
- 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" }
8055
+ 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 },
8056
+ 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 },
8057
+ 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 }
7997
8058
  };
7998
8059
  variantStyles7 = {
7999
8060
  buff: "border-success bg-success/20",
@@ -11906,15 +11967,15 @@ function BattleBoard({
11906
11967
  const board = boardEntity(entity) ?? {};
11907
11968
  const tiles = propTiles ?? (Array.isArray(board.tiles) ? board.tiles : []);
11908
11969
  const features = propFeatures ?? (Array.isArray(board.features) ? board.features : []);
11909
- const boardWidth = num(board.boardWidth, 8);
11910
- const boardHeight = num(board.boardHeight, 6);
11970
+ const boardWidth = num(board.gridWidth ?? board.boardWidth, 8);
11971
+ const boardHeight = num(board.gridHeight ?? board.boardHeight, 6);
11911
11972
  const assetManifest = propAssetManifest ?? board.assetManifest;
11912
11973
  const backgroundImage = board.backgroundImage;
11913
11974
  const units = rows(board.units);
11914
11975
  const selectedUnitId = board.selectedUnitId ?? null;
11915
11976
  const currentPhase = str(board.phase) || "observation";
11916
11977
  const currentTurn = num(board.turn, 1);
11917
- const gameResult = board.gameResult ?? null;
11978
+ const gameResult = board.result ?? null;
11918
11979
  const eventBus = useEventBus();
11919
11980
  const { t } = hooks.useTranslate();
11920
11981
  const [hoveredTile, setHoveredTile] = React83.useState(null);
@@ -11935,7 +11996,7 @@ function BattleBoard({
11935
11996
  const validMoves = React83.useMemo(() => {
11936
11997
  if (!selectedUnit || currentPhase !== "movement") return [];
11937
11998
  const moves = [];
11938
- const range = num(selectedUnit.movement);
11999
+ const range = num(board.movementRange, 2);
11939
12000
  const origin = unitPosition(selectedUnit);
11940
12001
  for (let dy = -range; dy <= range; dy++) {
11941
12002
  for (let dx = -range; dx <= range; dx++) {
@@ -20220,15 +20281,24 @@ function CastleBoard({
20220
20281
  featureClickEvent,
20221
20282
  unitClickEvent,
20222
20283
  tileClickEvent,
20284
+ playAgainEvent,
20223
20285
  className
20224
20286
  }) {
20225
20287
  const eventBus = useEventBus();
20288
+ const { t } = hooks.useTranslate();
20226
20289
  const resolved = boardEntity(entity);
20227
20290
  const tiles = propTiles ?? (Array.isArray(resolved?.tiles) ? resolved.tiles : []);
20228
20291
  const features = propFeatures ?? (Array.isArray(resolved?.features) ? resolved.features : []);
20229
20292
  const units = propUnits ?? (Array.isArray(resolved?.units) ? resolved.units : []);
20230
20293
  const assetManifest = propAssetManifest ?? resolved?.assetManifest;
20231
20294
  const backgroundImage = resolved?.backgroundImage;
20295
+ const gold = num(resolved?.gold);
20296
+ const health = num(resolved?.health);
20297
+ const maxHealth = num(resolved?.maxHealth);
20298
+ const wave = num(resolved?.wave);
20299
+ const tickCount = num(resolved?.tickCount);
20300
+ const buildings = rows(resolved?.buildings);
20301
+ const result = str(resolved?.result) || "none";
20232
20302
  const [hoveredTile, setHoveredTile] = React83.useState(null);
20233
20303
  const [selectedFeature, setSelectedFeature] = React83.useState(null);
20234
20304
  const hoveredFeature = React83.useMemo(() => {
@@ -20241,7 +20311,7 @@ function CastleBoard({
20241
20311
  (u) => u.position?.x === hoveredTile.x && u.position?.y === hoveredTile.y
20242
20312
  ) ?? null;
20243
20313
  }, [hoveredTile, units]);
20244
- const maxY = Math.max(...tiles.map((t) => t.y), 0);
20314
+ const maxY = Math.max(...tiles.map((t2) => t2.y), 0);
20245
20315
  const baseOffsetX = (maxY + 1) * (TILE_WIDTH * scale / 2);
20246
20316
  const tileToScreen = React83.useCallback(
20247
20317
  (tx, ty) => isoToScreen(tx, ty, scale, baseOffsetX),
@@ -20276,6 +20346,11 @@ function CastleBoard({
20276
20346
  }
20277
20347
  }, [units, onUnitClick, unitClickEvent, eventBus]);
20278
20348
  const clearSelection = React83.useCallback(() => setSelectedFeature(null), []);
20349
+ const handlePlayAgain = React83.useCallback(() => {
20350
+ if (playAgainEvent) {
20351
+ eventBus.emit(`UI:${playAgainEvent}`, {});
20352
+ }
20353
+ }, [playAgainEvent, eventBus]);
20279
20354
  const ctx = React83.useMemo(
20280
20355
  () => ({
20281
20356
  hoveredTile,
@@ -20284,11 +20359,18 @@ function CastleBoard({
20284
20359
  selectedFeature,
20285
20360
  clearSelection,
20286
20361
  tileToScreen,
20287
- scale
20362
+ scale,
20363
+ gold,
20364
+ health,
20365
+ maxHealth,
20366
+ wave,
20367
+ tickCount,
20368
+ buildings,
20369
+ result
20288
20370
  }),
20289
- [hoveredTile, hoveredFeature, hoveredUnit, selectedFeature, clearSelection, tileToScreen, scale]
20371
+ [hoveredTile, hoveredFeature, hoveredUnit, selectedFeature, clearSelection, tileToScreen, scale, gold, health, maxHealth, wave, tickCount, buildings, result]
20290
20372
  );
20291
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("castle-board min-h-screen flex flex-col bg-background", className), children: [
20373
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("castle-board relative min-h-screen flex flex-col bg-background", className), children: [
20292
20374
  header && header(ctx),
20293
20375
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 overflow-hidden", children: [
20294
20376
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 overflow-auto p-4 relative", children: [
@@ -20313,7 +20395,29 @@ function CastleBoard({
20313
20395
  ] }),
20314
20396
  sidePanel && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-96 shrink-0 border-l border-border bg-surface overflow-y-auto", children: sidePanel(ctx) })
20315
20397
  ] }),
20316
- footer && footer(ctx)
20398
+ footer && footer(ctx),
20399
+ result !== "none" && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "absolute inset-0 z-50 flex items-center justify-center bg-background/70 backdrop-blur-sm rounded-container", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { className: "text-center p-8", gap: "lg", children: [
20400
+ /* @__PURE__ */ jsxRuntime.jsx(
20401
+ Typography,
20402
+ {
20403
+ variant: "h2",
20404
+ className: cn(
20405
+ "text-4xl font-black tracking-widest uppercase",
20406
+ result === "victory" ? "text-warning" : "text-error"
20407
+ ),
20408
+ children: result === "victory" ? t("battle.victory") : t("battle.defeat")
20409
+ }
20410
+ ),
20411
+ /* @__PURE__ */ jsxRuntime.jsx(
20412
+ Button,
20413
+ {
20414
+ variant: "primary",
20415
+ className: "px-8 py-3 font-semibold",
20416
+ onClick: handlePlayAgain,
20417
+ children: t("battle.playAgain")
20418
+ }
20419
+ )
20420
+ ] }) })
20317
20421
  ] });
20318
20422
  }
20319
20423
  var init_CastleBoard = __esm({
@@ -20321,6 +20425,10 @@ var init_CastleBoard = __esm({
20321
20425
  "use client";
20322
20426
  init_cn();
20323
20427
  init_useEventBus();
20428
+ init_Box();
20429
+ init_Button();
20430
+ init_Typography();
20431
+ init_Stack();
20324
20432
  init_IsometricCanvas();
20325
20433
  init_boardEntity();
20326
20434
  init_isometric();
@@ -21197,22 +21305,16 @@ function ClassifierBoard({
21197
21305
  const { emit } = useEventBus();
21198
21306
  const { t } = hooks.useTranslate();
21199
21307
  const resolved = boardEntity(entity);
21200
- const [localAssignments, setLocalAssignments] = React83.useState({});
21201
21308
  const [headerError, setHeaderError] = React83.useState(false);
21202
- const [localSubmitted, setLocalSubmitted] = React83.useState(false);
21203
- const [localAttempts, setLocalAttempts] = React83.useState(0);
21204
- const [showHint, setShowHint] = React83.useState(false);
21205
21309
  const items = Array.isArray(resolved?.items) ? resolved.items : [];
21206
21310
  const categories = Array.isArray(resolved?.categories) ? resolved.categories : [];
21207
- const entityResult = str(resolved?.result);
21208
- const entityDrivesResult = entityResult.length > 0;
21209
- const entityHasAssignments = items.some((item) => item.assignedCategory != null);
21210
- const assignments = entityHasAssignments ? items.reduce((acc, item) => {
21211
- if (item.assignedCategory != null) acc[item.id] = item.assignedCategory;
21311
+ const result = str(resolved?.result);
21312
+ const submitted = result === "win";
21313
+ const attempts = num(resolved?.attempts);
21314
+ const assignments = items.reduce((acc, item) => {
21315
+ if (item.assignedCategory != null && item.assignedCategory !== "") acc[item.id] = item.assignedCategory;
21212
21316
  return acc;
21213
- }, {}) : localAssignments;
21214
- const attempts = entityDrivesResult ? num(resolved?.attempts) : localAttempts;
21215
- const submitted = entityDrivesResult || localSubmitted;
21317
+ }, {});
21216
21318
  const unassignedItems = items.filter((item) => !assignments[item.id]);
21217
21319
  const allAssigned = items.length > 0 && Object.keys(assignments).length === items.length;
21218
21320
  const results = submitted ? items.map((item) => ({
@@ -21220,54 +21322,25 @@ function ClassifierBoard({
21220
21322
  assigned: assignments[item.id],
21221
21323
  correct: assignments[item.id] === item.correctCategory
21222
21324
  })) : [];
21223
- const allCorrect = entityDrivesResult ? entityResult === "success" : results.length > 0 && results.every((r) => r.correct);
21325
+ const allCorrect = result === "win";
21224
21326
  const correctCount = results.filter((r) => r.correct).length;
21327
+ const showHint = attempts >= 2 && Boolean(str(resolved?.hint));
21225
21328
  const handleAssign = (itemId, categoryId) => {
21226
21329
  if (submitted) return;
21227
- if (assignEvent) {
21228
- emit(`UI:${assignEvent}`, { itemId, categoryId });
21229
- }
21230
- if (!entityHasAssignments) {
21231
- setLocalAssignments((prev) => ({ ...prev, [itemId]: categoryId }));
21232
- }
21330
+ if (assignEvent) emit(`UI:${assignEvent}`, { itemId, categoryId });
21233
21331
  };
21234
21332
  const handleUnassign = (itemId) => {
21235
21333
  if (submitted) return;
21236
- if (!entityHasAssignments) {
21237
- setLocalAssignments((prev) => {
21238
- const next = { ...prev };
21239
- delete next[itemId];
21240
- return next;
21241
- });
21242
- }
21334
+ if (assignEvent) emit(`UI:${assignEvent}`, { itemId, categoryId: "" });
21243
21335
  };
21244
- const handleSubmit = React83.useCallback(() => {
21245
- if (checkEvent) {
21246
- emit(`UI:${checkEvent}`, {});
21247
- }
21248
- if (!entityDrivesResult) {
21249
- setLocalSubmitted(true);
21250
- setLocalAttempts((a) => a + 1);
21251
- const correct = items.every((item) => assignments[item.id] === item.correctCategory);
21252
- if (correct) {
21253
- emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
21254
- }
21255
- }
21256
- }, [checkEvent, entityDrivesResult, items, assignments, attempts, completeEvent, emit]);
21257
- const handleReset = () => {
21258
- if (!entityDrivesResult) setLocalSubmitted(false);
21259
- if (attempts >= 2 && str(resolved?.hint)) {
21260
- setShowHint(true);
21336
+ const handleSubmit = () => {
21337
+ if (checkEvent) emit(`UI:${checkEvent}`, {});
21338
+ if (allAssigned && items.every((item) => assignments[item.id] === item.correctCategory)) {
21339
+ emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
21261
21340
  }
21262
21341
  };
21263
21342
  const handleFullReset = () => {
21264
- if (playAgainEvent) {
21265
- emit(`UI:${playAgainEvent}`, {});
21266
- }
21267
- setLocalAssignments({});
21268
- setLocalSubmitted(false);
21269
- setLocalAttempts(0);
21270
- setShowHint(false);
21343
+ if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
21271
21344
  };
21272
21345
  if (!resolved) return null;
21273
21346
  const theme = resolved.theme ?? void 0;
@@ -21306,17 +21379,17 @@ function ClassifierBoard({
21306
21379
  /* @__PURE__ */ jsxRuntime.jsx(Badge, { size: "sm", children: catItems.length })
21307
21380
  ] }),
21308
21381
  /* @__PURE__ */ jsxRuntime.jsx(HStack, { gap: "xs", className: "flex-wrap min-h-[2rem]", children: catItems.map((item) => {
21309
- const result = results.find((r) => r.item.id === item.id);
21382
+ const result2 = results.find((r) => r.item.id === item.id);
21310
21383
  return /* @__PURE__ */ jsxRuntime.jsxs(
21311
21384
  Badge,
21312
21385
  {
21313
21386
  size: "sm",
21314
- className: `cursor-pointer ${result ? result.correct ? "border-success bg-success/10" : "border-error bg-error/10" : ""}`,
21387
+ className: `cursor-pointer ${result2 ? result2.correct ? "border-success bg-success/10" : "border-error bg-error/10" : ""}`,
21315
21388
  onClick: () => handleUnassign(item.id),
21316
21389
  children: [
21317
21390
  item.iconUrl && /* @__PURE__ */ jsxRuntime.jsx("img", { src: item.iconUrl, alt: "", className: "w-3 h-3 object-contain inline-block" }),
21318
21391
  item.label,
21319
- result && /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: result.correct ? LucideIcons2.CheckCircle : LucideIcons2.XCircle, size: "xs", className: result.correct ? "text-success" : "text-error" })
21392
+ result2 && /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: result2.correct ? LucideIcons2.CheckCircle : LucideIcons2.XCircle, size: "xs", className: result2.correct ? "text-success" : "text-error" })
21320
21393
  ]
21321
21394
  },
21322
21395
  item.id
@@ -21345,10 +21418,10 @@ function ClassifierBoard({
21345
21418
  ] }) }),
21346
21419
  showHint && hint && /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: hint }) }),
21347
21420
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", justify: "center", children: [
21348
- !submitted ? /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: !allAssigned, children: [
21421
+ !submitted && /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: !allAssigned, children: [
21349
21422
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: LucideIcons2.Send, size: "sm" }),
21350
21423
  t("classifier.check")
21351
- ] }) : !allCorrect ? /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "primary", onClick: handleReset, children: t("classifier.tryAgain") }) : null,
21424
+ ] }),
21352
21425
  /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "secondary", onClick: handleFullReset, children: [
21353
21426
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: LucideIcons2.RotateCcw, size: "sm" }),
21354
21427
  t("classifier.reset")
@@ -29377,7 +29450,7 @@ var init_MapView = __esm({
29377
29450
  shadowSize: [41, 41]
29378
29451
  });
29379
29452
  L.Marker.prototype.options.icon = defaultIcon;
29380
- const { useEffect: useEffect73, useRef: useRef68, useCallback: useCallback112, useState: useState104 } = React83__namespace.default;
29453
+ const { useEffect: useEffect73, useRef: useRef68, useCallback: useCallback111, useState: useState104 } = React83__namespace.default;
29381
29454
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
29382
29455
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
29383
29456
  function MapUpdater({ centerLat, centerLng, zoom }) {
@@ -29423,7 +29496,7 @@ var init_MapView = __esm({
29423
29496
  }) {
29424
29497
  const eventBus = useEventBus2();
29425
29498
  const [clickedPosition, setClickedPosition] = useState104(null);
29426
- const handleMapClick = useCallback112((lat, lng) => {
29499
+ const handleMapClick = useCallback111((lat, lng) => {
29427
29500
  if (showClickedPin) {
29428
29501
  setClickedPosition({ lat, lng });
29429
29502
  }
@@ -29432,7 +29505,7 @@ var init_MapView = __esm({
29432
29505
  eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
29433
29506
  }
29434
29507
  }, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
29435
- const handleMarkerClick = useCallback112((marker) => {
29508
+ const handleMarkerClick = useCallback111((marker) => {
29436
29509
  onMarkerClick?.(marker);
29437
29510
  if (markerClickEvent) {
29438
29511
  eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
@@ -38663,44 +38736,27 @@ function DebuggerBoard({
38663
38736
  const { t } = hooks.useTranslate();
38664
38737
  const resolved = boardEntity(entity);
38665
38738
  const [headerError, setHeaderError] = React83.useState(false);
38666
- const [showHint, setShowHint] = React83.useState(false);
38667
38739
  const lines = Array.isArray(resolved?.lines) ? resolved.lines : [];
38668
- const result = resolved?.result ?? null;
38740
+ const result = str(resolved?.result) || "none";
38669
38741
  const attempts = num(resolved?.attempts);
38670
- const submitted = result != null;
38742
+ const submitted = result === "win";
38743
+ const showHint = !submitted && attempts >= 2 && Boolean(str(resolved?.hint));
38671
38744
  const bugLines = lines.filter((l) => l.isBug);
38672
38745
  const flaggedLines = lines.filter((l) => l.isFlagged);
38673
38746
  const correctFlags = lines.filter((l) => l.isBug && l.isFlagged);
38674
38747
  const falseFlags = lines.filter((l) => !l.isBug && l.isFlagged);
38675
- const allCorrect = result === "win";
38748
+ const allCorrect = submitted;
38676
38749
  const toggleLine = (lineId) => {
38677
38750
  if (submitted) return;
38678
- if (toggleFlagEvent) {
38679
- emit(`UI:${toggleFlagEvent}`, { lineId });
38680
- }
38751
+ if (toggleFlagEvent) emit(`UI:${toggleFlagEvent}`, { lineId });
38681
38752
  };
38682
38753
  const handleSubmit = React83.useCallback(() => {
38683
- if (checkEvent) {
38684
- emit(`UI:${checkEvent}`, {});
38685
- }
38754
+ if (checkEvent) emit(`UI:${checkEvent}`, {});
38686
38755
  const correct = correctFlags.length === bugLines.length && falseFlags.length === 0;
38687
- if (correct) {
38688
- emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
38689
- }
38756
+ if (correct) emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
38690
38757
  }, [checkEvent, correctFlags.length, bugLines.length, falseFlags.length, attempts, completeEvent, emit]);
38691
38758
  const handleReset = () => {
38692
- if (playAgainEvent) {
38693
- emit(`UI:${playAgainEvent}`, {});
38694
- }
38695
- if (attempts >= 2 && str(resolved?.hint)) {
38696
- setShowHint(true);
38697
- }
38698
- };
38699
- const handleFullReset = () => {
38700
- if (playAgainEvent) {
38701
- emit(`UI:${playAgainEvent}`, {});
38702
- }
38703
- setShowHint(false);
38759
+ if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
38704
38760
  };
38705
38761
  if (!resolved) return null;
38706
38762
  const theme = resolved.theme ?? void 0;
@@ -38724,7 +38780,7 @@ function DebuggerBoard({
38724
38780
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", weight: "bold", children: str(resolved.title) })
38725
38781
  ] }),
38726
38782
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: str(resolved.description) }),
38727
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "text-muted-foreground", children: t("debugger.findBugs", { count: String(num(resolved.bugCount)) }) })
38783
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "text-muted-foreground", children: t("debugger.findBugs", { count: String(lines.filter((l) => l.isBug).length) }) })
38728
38784
  ] }) }),
38729
38785
  /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-0 overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx(VStack, { gap: "none", children: lines.map((line, i) => {
38730
38786
  const isFlagged = !!line.isFlagged;
@@ -38774,11 +38830,11 @@ function DebuggerBoard({
38774
38830
  ] }) }),
38775
38831
  showHint && hint && /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: hint }) }),
38776
38832
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", justify: "center", children: [
38777
- !submitted ? /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: flaggedLines.length === 0, children: [
38833
+ !submitted && /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: flaggedLines.length === 0, children: [
38778
38834
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: LucideIcons2.Send, size: "sm" }),
38779
38835
  t("debugger.submit")
38780
- ] }) : !allCorrect ? /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "primary", onClick: handleReset, children: t("debugger.tryAgain") }) : null,
38781
- /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "secondary", onClick: handleFullReset, children: [
38836
+ ] }),
38837
+ /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "secondary", onClick: handleReset, children: [
38782
38838
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: LucideIcons2.RotateCcw, size: "sm" }),
38783
38839
  t("debugger.reset")
38784
38840
  ] })
@@ -39779,39 +39835,40 @@ function EventHandlerBoard({
39779
39835
  stepDurationMs = 800,
39780
39836
  playEvent,
39781
39837
  completeEvent,
39838
+ editRuleEvent,
39839
+ playAgainEvent,
39782
39840
  className
39783
39841
  }) {
39784
39842
  const { emit } = useEventBus();
39785
39843
  const { t } = hooks.useTranslate();
39786
39844
  const resolved = boardEntity(entity);
39787
- const entityObjects = rows(resolved?.objects);
39788
- const [objects, setObjects] = React83.useState(() => [...entityObjects]);
39845
+ const objects = rows(resolved?.objects);
39846
+ const entityResult = str(resolved?.result) || "none";
39847
+ const isSuccess = entityResult === "win";
39848
+ const attempts = typeof resolved?.attempts === "number" ? resolved.attempts : 0;
39789
39849
  const [selectedObjectId, setSelectedObjectId] = React83.useState(
39790
- entityObjects[0] ? objId(entityObjects[0]) : null
39850
+ objects[0] ? objId(objects[0]) : null
39791
39851
  );
39792
39852
  const [headerError, setHeaderError] = React83.useState(false);
39793
- const [playState, setPlayState] = React83.useState("editing");
39853
+ const [isPlaying, setIsPlaying] = React83.useState(false);
39794
39854
  const [eventLog, setEventLog] = React83.useState([]);
39795
- const [attempts, setAttempts] = React83.useState(0);
39796
39855
  const timerRef = React83.useRef(null);
39797
39856
  const logIdCounter = React83.useRef(0);
39798
39857
  React83.useEffect(() => () => {
39799
39858
  if (timerRef.current) clearTimeout(timerRef.current);
39800
39859
  }, []);
39801
- const selectedObject = objects.find((o) => objId(o) === selectedObjectId) || null;
39860
+ const selectedObject = objects.find((o) => objId(o) === selectedObjectId) ?? null;
39802
39861
  const handleRulesChange = React83.useCallback((objectId, rules) => {
39803
- setObjects((prev) => prev.map(
39804
- (o) => objId(o) === objectId ? { ...o, rules } : o
39805
- ));
39806
- }, []);
39862
+ if (editRuleEvent) emit(`UI:${editRuleEvent}`, { objectId, rules });
39863
+ }, [editRuleEvent, emit]);
39807
39864
  const addLogEntry = React83.useCallback((icon, message, status = "done") => {
39808
39865
  const id = `log-${logIdCounter.current++}`;
39809
39866
  setEventLog((prev) => [...prev, { id, timestamp: Date.now(), icon, message, status }]);
39810
39867
  }, []);
39811
39868
  const handlePlay = React83.useCallback(() => {
39812
- if (playState !== "editing") return;
39869
+ if (isPlaying || isSuccess) return;
39813
39870
  if (playEvent) emit(`UI:${playEvent}`, {});
39814
- setPlayState("playing");
39871
+ setIsPlaying(true);
39815
39872
  setEventLog([]);
39816
39873
  const allRules = [];
39817
39874
  objects.forEach((obj) => {
@@ -39827,15 +39884,8 @@ function EventHandlerBoard({
39827
39884
  let goalReached = false;
39828
39885
  const processNext = () => {
39829
39886
  if (eventQueue.length === 0 || stepIdx > 20) {
39830
- if (goalReached) {
39831
- setPlayState("success");
39832
- if (completeEvent) {
39833
- emit(`UI:${completeEvent}`, { success: true });
39834
- }
39835
- } else {
39836
- setAttempts((prev) => prev + 1);
39837
- setPlayState("fail");
39838
- }
39887
+ setIsPlaying(false);
39888
+ if (goalReached && completeEvent) emit(`UI:${completeEvent}`, { success: true });
39839
39889
  return;
39840
39890
  }
39841
39891
  const currentEvent = eventQueue.shift();
@@ -39866,21 +39916,19 @@ function EventHandlerBoard({
39866
39916
  addLogEntry("\u{1F3AC}", t("eventHandler.simulationStarted", { events: triggers.join(", ") }), "active");
39867
39917
  }
39868
39918
  timerRef.current = setTimeout(processNext, stepDurationMs);
39869
- }, [playState, objects, resolved, stepDurationMs, playEvent, completeEvent, emit, addLogEntry, t]);
39919
+ }, [isPlaying, isSuccess, objects, resolved, stepDurationMs, playEvent, completeEvent, emit, addLogEntry, t]);
39870
39920
  const handleTryAgain = React83.useCallback(() => {
39871
39921
  if (timerRef.current) clearTimeout(timerRef.current);
39872
- setPlayState("editing");
39922
+ setIsPlaying(false);
39873
39923
  setEventLog([]);
39874
39924
  }, []);
39875
39925
  const handleReset = React83.useCallback(() => {
39876
39926
  if (timerRef.current) clearTimeout(timerRef.current);
39877
- const resetObjects = rows(resolved?.objects);
39878
- setObjects([...resetObjects]);
39879
- setPlayState("editing");
39927
+ setIsPlaying(false);
39880
39928
  setEventLog([]);
39881
- setSelectedObjectId(resetObjects[0] ? objId(resetObjects[0]) : null);
39882
- setAttempts(0);
39883
- }, [resolved?.objects]);
39929
+ setSelectedObjectId(objects[0] ? objId(objects[0]) : null);
39930
+ if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
39931
+ }, [objects, playAgainEvent, emit]);
39884
39932
  if (!resolved) return null;
39885
39933
  const objectViewers = objects.map((obj) => {
39886
39934
  const states = objStates(obj);
@@ -39949,12 +39997,12 @@ function EventHandlerBoard({
39949
39997
  {
39950
39998
  object: selectedObject,
39951
39999
  onRulesChange: handleRulesChange,
39952
- disabled: playState !== "editing"
40000
+ disabled: isPlaying
39953
40001
  }
39954
40002
  ),
39955
40003
  eventLog.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(EventLog, { entries: eventLog }),
39956
- playState === "success" && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "p-4 rounded-container bg-success/20 border border-success text-center", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h5", className: "text-success", children: str(resolved.successMessage) || t("eventHandler.chainComplete") }) }),
39957
- playState === "fail" && /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
40004
+ isSuccess && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "p-4 rounded-container bg-success/20 border border-success text-center", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h5", className: "text-success", children: str(resolved.successMessage) || t("eventHandler.chainComplete") }) }),
40005
+ !isPlaying && !isSuccess && attempts > 0 && /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
39958
40006
  /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "p-4 rounded-container bg-warning/10 border border-warning/30 text-center", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body1", className: "text-foreground font-medium", children: t(encourageKey) }) }),
39959
40007
  showHint && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "p-3 rounded-container bg-accent/10 border border-accent/30", children: /* @__PURE__ */ jsxRuntime.jsxs(HStack, { className: "items-start", gap: "xs", children: [
39960
40008
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-accent font-bold shrink-0", children: "\u{1F4A1} " + t("game.hint") + ":" }),
@@ -39962,12 +40010,12 @@ function EventHandlerBoard({
39962
40010
  ] }) })
39963
40011
  ] }),
39964
40012
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", children: [
39965
- playState === "fail" ? /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "primary", onClick: handleTryAgain, children: "\u{1F504} " + t("puzzle.tryAgainButton") }) : /* @__PURE__ */ jsxRuntime.jsx(
40013
+ !isPlaying && !isSuccess && attempts > 0 ? /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "primary", onClick: handleTryAgain, children: "\u{1F504} " + t("puzzle.tryAgainButton") }) : /* @__PURE__ */ jsxRuntime.jsx(
39966
40014
  Button,
39967
40015
  {
39968
40016
  variant: "primary",
39969
40017
  onClick: handlePlay,
39970
- disabled: playState !== "editing",
40018
+ disabled: isPlaying || isSuccess,
39971
40019
  children: "\u25B6 " + t("game.play")
39972
40020
  }
39973
40021
  ),
@@ -42520,21 +42568,6 @@ var init_ModalSlot = __esm({
42520
42568
  ModalSlot.displayName = "ModalSlot";
42521
42569
  }
42522
42570
  });
42523
- function getOpponentAction(strategy, actions, history) {
42524
- const actionIds = actions.map((a) => a.id);
42525
- switch (strategy) {
42526
- case "always-cooperate":
42527
- return actionIds[0];
42528
- case "always-defect":
42529
- return actionIds[actionIds.length - 1];
42530
- case "tit-for-tat":
42531
- if (history.length === 0) return actionIds[0];
42532
- return history[history.length - 1].playerAction;
42533
- case "random":
42534
- default:
42535
- return actionIds[Math.floor(Math.random() * actionIds.length)];
42536
- }
42537
- }
42538
42571
  function NegotiatorBoard({
42539
42572
  entity,
42540
42573
  completeEvent = "PUZZLE_COMPLETE",
@@ -42556,28 +42589,35 @@ function NegotiatorBoard({
42556
42589
  const playerTotal = num(resolved?.score);
42557
42590
  const isComplete = result !== "none" || totalRounds > 0 && currentRound >= totalRounds;
42558
42591
  const won = result === "win";
42559
- const opponentTotal = history.reduce((s, r) => s + r.opponentPayoff, 0);
42592
+ const lastPlayerAction = str(resolved?.lastPlayerAction);
42593
+ const lastOpponentAction = str(resolved?.lastOpponentAction);
42594
+ const lastPayoff = num(resolved?.lastPayoff);
42560
42595
  const actions = Array.isArray(resolved?.actions) ? resolved.actions : [];
42561
42596
  const payoffMatrix = Array.isArray(resolved?.payoffMatrix) ? resolved.payoffMatrix : [];
42597
+ const prevRoundRef = React83.useRef(currentRound);
42598
+ React83.useEffect(() => {
42599
+ if (currentRound > prevRoundRef.current && lastPlayerAction) {
42600
+ const opponentPayoffEntry = payoffMatrix.find(
42601
+ (p2) => p2.playerAction === lastPlayerAction && p2.opponentAction === lastOpponentAction
42602
+ );
42603
+ setHistory((prev) => [
42604
+ ...prev,
42605
+ {
42606
+ round: currentRound,
42607
+ playerAction: lastPlayerAction,
42608
+ opponentAction: lastOpponentAction,
42609
+ playerPayoff: lastPayoff,
42610
+ opponentPayoff: opponentPayoffEntry?.opponentPayoff ?? 0
42611
+ }
42612
+ ]);
42613
+ }
42614
+ prevRoundRef.current = currentRound;
42615
+ }, [currentRound, lastPlayerAction, lastOpponentAction, lastPayoff, payoffMatrix]);
42616
+ const opponentTotal = React83.useMemo(() => history.reduce((s, r) => s + r.opponentPayoff, 0), [history]);
42562
42617
  const handleAction = React83.useCallback((actionId) => {
42563
42618
  if (isComplete) return;
42564
- const opponentAction = getOpponentAction(str(resolved?.opponentStrategy) || "random", actions, history);
42565
- const payoff = payoffMatrix.find(
42566
- (p2) => p2.playerAction === actionId && p2.opponentAction === opponentAction
42567
- );
42568
- const playerPayoff = payoff?.playerPayoff ?? 0;
42569
- setHistory((prev) => [
42570
- ...prev,
42571
- {
42572
- round: prev.length + 1,
42573
- playerAction: actionId,
42574
- opponentAction,
42575
- playerPayoff,
42576
- opponentPayoff: payoff?.opponentPayoff ?? 0
42577
- }
42578
- ]);
42579
42619
  if (playRoundEvent) {
42580
- emit(`UI:${playRoundEvent}`, { playerAction: actionId, payoff: playerPayoff });
42620
+ emit(`UI:${playRoundEvent}`, { playerAction: actionId, payoff: 0 });
42581
42621
  }
42582
42622
  if (totalRounds > 0 && currentRound + 1 >= totalRounds) {
42583
42623
  if (finishEvent) {
@@ -42587,10 +42627,11 @@ function NegotiatorBoard({
42587
42627
  setShowHint(true);
42588
42628
  }
42589
42629
  }
42590
- }, [isComplete, resolved, totalRounds, currentRound, actions, payoffMatrix, history, playRoundEvent, finishEvent, emit]);
42630
+ }, [isComplete, resolved, totalRounds, currentRound, playRoundEvent, finishEvent, emit]);
42591
42631
  const handleReset = React83.useCallback(() => {
42592
42632
  setHistory([]);
42593
42633
  setShowHint(false);
42634
+ prevRoundRef.current = 0;
42594
42635
  if (playAgainEvent) {
42595
42636
  emit(`UI:${playAgainEvent}`, {});
42596
42637
  }
@@ -44926,17 +44967,24 @@ function SequencerBoard({
44926
44967
  const { emit } = useEventBus();
44927
44968
  const { t } = hooks.useTranslate();
44928
44969
  const resolved = boardEntity(entity);
44929
- const maxSlots = num(resolved?.maxSlots);
44970
+ const maxSlots = num(resolved?.maxSlots) || 3;
44930
44971
  const solutions = Array.isArray(resolved?.solutions) ? resolved.solutions : [];
44931
44972
  const availableActions = Array.isArray(resolved?.availableActions) ? resolved.availableActions : [];
44932
44973
  const allowDuplicates = resolved?.allowDuplicates !== false;
44974
+ const entitySlots = Array.isArray(resolved?.slots) ? resolved.slots : [];
44975
+ const entityResult = str(resolved?.result);
44976
+ const entityAttempts = num(resolved?.attempts);
44977
+ const entityCurrentStep = typeof resolved?.currentStep === "number" ? resolved.currentStep : -1;
44978
+ const slots = Array.from({ length: maxSlots }, (_, i) => {
44979
+ const entitySlot = entitySlots.find((s) => s.index === i);
44980
+ if (!entitySlot?.placedActionId) return void 0;
44981
+ return availableActions.find((a) => a.id === entitySlot.placedActionId);
44982
+ });
44983
+ const isSuccess = entityResult === "win";
44984
+ const attempts = entityAttempts;
44985
+ const isPlayingBack = entityCurrentStep >= 0 && !isSuccess;
44986
+ const currentStep = entityCurrentStep;
44933
44987
  const [headerError, setHeaderError] = React83.useState(false);
44934
- const [slots, setSlots] = React83.useState(
44935
- () => Array.from({ length: maxSlots }, () => void 0)
44936
- );
44937
- const [playState, setPlayState] = React83.useState("idle");
44938
- const [currentStep, setCurrentStep] = React83.useState(-1);
44939
- const [attempts, setAttempts] = React83.useState(0);
44940
44988
  const [slotFeedback, setSlotFeedback] = React83.useState(
44941
44989
  () => Array.from({ length: maxSlots }, () => null)
44942
44990
  );
@@ -44945,11 +44993,6 @@ function SequencerBoard({
44945
44993
  if (timerRef.current) clearTimeout(timerRef.current);
44946
44994
  }, []);
44947
44995
  const handleSlotDrop = React83.useCallback((index, item) => {
44948
- setSlots((prev) => {
44949
- const next = [...prev];
44950
- next[index] = item;
44951
- return next;
44952
- });
44953
44996
  setSlotFeedback((prev) => {
44954
44997
  const next = [...prev];
44955
44998
  next[index] = null;
@@ -44959,11 +45002,6 @@ function SequencerBoard({
44959
45002
  if (placeEvent) emit(`UI:${placeEvent}`, { slotIndex: index, actionId: item.id });
44960
45003
  }, [emit, placeEvent]);
44961
45004
  const handleSlotRemove = React83.useCallback((index) => {
44962
- setSlots((prev) => {
44963
- const next = [...prev];
44964
- next[index] = void 0;
44965
- return next;
44966
- });
44967
45005
  setSlotFeedback((prev) => {
44968
45006
  const next = [...prev];
44969
45007
  next[index] = null;
@@ -44974,48 +45012,34 @@ function SequencerBoard({
44974
45012
  }, [emit, removeEvent]);
44975
45013
  const handleReset = React83.useCallback(() => {
44976
45014
  if (timerRef.current) clearTimeout(timerRef.current);
44977
- setSlots(Array.from({ length: maxSlots }, () => void 0));
44978
- setPlayState("idle");
44979
- setCurrentStep(-1);
44980
- setAttempts(0);
44981
45015
  setSlotFeedback(Array.from({ length: maxSlots }, () => null));
44982
45016
  if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
44983
45017
  }, [maxSlots, playAgainEvent, emit]);
44984
45018
  const filledSlots = slots.filter((s) => !!s);
44985
- const canPlay = filledSlots.length > 0 && playState === "idle";
45019
+ const canPlay = filledSlots.length > 0 && !isPlayingBack && !isSuccess;
44986
45020
  const handlePlay = React83.useCallback(() => {
44987
45021
  if (!canPlay) return;
44988
45022
  setSlotFeedback(Array.from({ length: maxSlots }, () => null));
44989
45023
  emit("UI:PLAY_SOUND", { key: "confirm" });
44990
45024
  const sequence = slots.map((s) => s?.id || "");
44991
- if (playEvent) {
44992
- emit(`UI:${playEvent}`, { sequence });
44993
- }
44994
- setPlayState("playing");
44995
- setCurrentStep(0);
45025
+ const playerIds = slots.filter(Boolean).map((s) => s?.id || "");
45026
+ if (playEvent) emit(`UI:${playEvent}`, { sequence });
44996
45027
  let step = 0;
44997
45028
  const advance = () => {
44998
45029
  step++;
45030
+ emit("UI:STEP", { step });
44999
45031
  if (step >= maxSlots) {
45032
+ if (checkEvent) emit(`UI:${checkEvent}`, { sequence: playerIds });
45000
45033
  const playerSeq = slots.map((s) => s?.id);
45001
- const playerIds = slots.filter(Boolean).map((s) => s?.id || "");
45002
45034
  const success = solutions.some(
45003
45035
  (sol) => sol.length === playerIds.length && sol.every((id, i) => id === playerIds[i])
45004
45036
  );
45005
- if (checkEvent) emit(`UI:${checkEvent}`, { sequence: playerIds });
45006
45037
  if (success) {
45007
- setPlayState("success");
45008
- setCurrentStep(-1);
45009
45038
  emit("UI:PLAY_SOUND", { key: "levelComplete" });
45010
- if (completeEvent) {
45011
- emit(`UI:${completeEvent}`, { success: true, sequence: playerIds });
45012
- }
45039
+ if (completeEvent) emit(`UI:${completeEvent}`, { success: true, sequence: playerIds });
45013
45040
  } else {
45014
- setAttempts((prev) => prev + 1);
45015
45041
  const feedback = computeSlotFeedback(playerSeq, solutions);
45016
45042
  setSlotFeedback(feedback);
45017
- setPlayState("idle");
45018
- setCurrentStep(-1);
45019
45043
  emit("UI:PLAY_SOUND", { key: "fail" });
45020
45044
  const correctCount2 = feedback.filter((f3) => f3 === "correct").length;
45021
45045
  for (let ci = 0; ci < correctCount2; ci++) {
@@ -45025,7 +45049,6 @@ function SequencerBoard({
45025
45049
  }
45026
45050
  }
45027
45051
  } else {
45028
- setCurrentStep(step);
45029
45052
  timerRef.current = setTimeout(advance, stepDurationMs);
45030
45053
  }
45031
45054
  };
@@ -45075,7 +45098,7 @@ function SequencerBoard({
45075
45098
  /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "xs", children: [
45076
45099
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { className: "items-center justify-between", children: [
45077
45100
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("sequencer.yourSequence") + ":" }),
45078
- hasFeedback && playState === "idle" && /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "caption", className: "text-muted-foreground", children: [
45101
+ hasFeedback && !isPlayingBack && !isSuccess && /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "caption", className: "text-muted-foreground", children: [
45079
45102
  `${correctCount}/${maxSlots} `,
45080
45103
  "\u2705"
45081
45104
  ] })
@@ -45087,7 +45110,7 @@ function SequencerBoard({
45087
45110
  maxSlots,
45088
45111
  onSlotDrop: handleSlotDrop,
45089
45112
  onSlotRemove: handleSlotRemove,
45090
- playing: playState === "playing",
45113
+ playing: isPlayingBack,
45091
45114
  currentStep,
45092
45115
  categoryColors,
45093
45116
  slotFeedback,
@@ -45095,7 +45118,7 @@ function SequencerBoard({
45095
45118
  }
45096
45119
  )
45097
45120
  ] }),
45098
- playState !== "playing" && /* @__PURE__ */ jsxRuntime.jsx(
45121
+ !isPlayingBack && /* @__PURE__ */ jsxRuntime.jsx(
45099
45122
  ActionPalette,
45100
45123
  {
45101
45124
  actions: availableActions,
@@ -45105,8 +45128,8 @@ function SequencerBoard({
45105
45128
  label: t("sequencer.dragActions")
45106
45129
  }
45107
45130
  ),
45108
- hasFeedback && playState === "idle" && attempts > 0 && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "p-3 rounded-container bg-warning/10 border border-warning/30 text-center", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-foreground", children: t(encourageKey) }) }),
45109
- playState === "success" && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "p-4 rounded-container bg-success/20 border border-success text-center", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h5", className: "text-success", children: str(resolved.successMessage) || t("sequencer.levelComplete") }) }),
45131
+ hasFeedback && !isPlayingBack && !isSuccess && attempts > 0 && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "p-3 rounded-container bg-warning/10 border border-warning/30 text-center", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-foreground", children: t(encourageKey) }) }),
45132
+ isSuccess && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "p-4 rounded-container bg-success/20 border border-success text-center", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h5", className: "text-success", children: str(resolved.successMessage) || t("sequencer.levelComplete") }) }),
45110
45133
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", children: [
45111
45134
  /* @__PURE__ */ jsxRuntime.jsx(
45112
45135
  Button,
@@ -46156,6 +46179,9 @@ function StateArchitectBoard({
46156
46179
  stepDurationMs = 600,
46157
46180
  testEvent,
46158
46181
  completeEvent,
46182
+ addTransitionEvent,
46183
+ removeTransitionEvent,
46184
+ playAgainEvent,
46159
46185
  className
46160
46186
  }) {
46161
46187
  const { emit } = useEventBus();
@@ -46168,14 +46194,16 @@ function StateArchitectBoard({
46168
46194
  const testCases = Array.isArray(resolved?.testCases) ? resolved.testCases : [];
46169
46195
  const entityTransitions = Array.isArray(resolved?.transitions) ? resolved.transitions : [];
46170
46196
  const entityVariables = rows(resolved?.variables);
46171
- const [transitions, setTransitions] = React83.useState(entityTransitions);
46197
+ const transitions = entityTransitions;
46198
+ const attempts = typeof resolved?.attempts === "number" ? resolved.attempts : 0;
46199
+ const entityResult = str(resolved?.result) || "none";
46200
+ const isSuccess = entityResult === "win";
46201
+ const [isTesting, setIsTesting] = React83.useState(false);
46172
46202
  const [headerError, setHeaderError] = React83.useState(false);
46173
- const [playState, setPlayState] = React83.useState("editing");
46174
46203
  const [currentState, setCurrentState] = React83.useState(initialState);
46175
46204
  const [selectedState, setSelectedState] = React83.useState(null);
46176
46205
  const [testResults, setTestResults] = React83.useState([]);
46177
46206
  const [variables, setVariables] = React83.useState(() => [...entityVariables]);
46178
- const [attempts, setAttempts] = React83.useState(0);
46179
46207
  const timerRef = React83.useRef(null);
46180
46208
  const [addingFrom, setAddingFrom] = React83.useState(null);
46181
46209
  React83.useEffect(() => () => {
@@ -46185,7 +46213,7 @@ function StateArchitectBoard({
46185
46213
  const GRAPH_H = 400;
46186
46214
  const positions = React83.useMemo(() => layoutStates(entityStates, GRAPH_W, GRAPH_H), [entityStates]);
46187
46215
  const handleStateClick = React83.useCallback((state) => {
46188
- if (playState !== "editing") return;
46216
+ if (isTesting) return;
46189
46217
  if (addingFrom) {
46190
46218
  if (addingFrom !== state) {
46191
46219
  const event = availableEvents[0] || "EVENT";
@@ -46195,20 +46223,20 @@ function StateArchitectBoard({
46195
46223
  to: state,
46196
46224
  event
46197
46225
  };
46198
- setTransitions((prev) => [...prev, newTrans]);
46226
+ if (addTransitionEvent) emit(`UI:${addTransitionEvent}`, { id: newTrans.id, from: newTrans.from, to: newTrans.to, event: newTrans.event });
46199
46227
  }
46200
46228
  setAddingFrom(null);
46201
46229
  } else {
46202
46230
  setSelectedState(state);
46203
46231
  }
46204
- }, [playState, addingFrom, availableEvents]);
46232
+ }, [isTesting, addingFrom, availableEvents, addTransitionEvent, emit]);
46205
46233
  const handleStartAddTransition = React83.useCallback(() => {
46206
46234
  if (!selectedState) return;
46207
46235
  setAddingFrom(selectedState);
46208
46236
  }, [selectedState]);
46209
46237
  const handleRemoveTransition = React83.useCallback((transId) => {
46210
- setTransitions((prev) => prev.filter((t2) => t2.id !== transId));
46211
- }, []);
46238
+ if (removeTransitionEvent) emit(`UI:${removeTransitionEvent}`, { id: transId });
46239
+ }, [removeTransitionEvent, emit]);
46212
46240
  const machine = React83.useMemo(() => ({
46213
46241
  name: entityName,
46214
46242
  description: str(resolved?.description),
@@ -46222,16 +46250,16 @@ function StateArchitectBoard({
46222
46250
  }))
46223
46251
  }), [entityName, resolved, entityStates, currentState, transitions]);
46224
46252
  const handleTest = React83.useCallback(() => {
46225
- if (playState !== "editing") return;
46253
+ if (isTesting) return;
46226
46254
  if (testEvent) emit(`UI:${testEvent}`, {});
46227
- setPlayState("testing");
46255
+ setIsTesting(true);
46228
46256
  setTestResults([]);
46229
46257
  const results = [];
46230
46258
  let testIdx = 0;
46231
46259
  const runNextTest = () => {
46232
46260
  if (testIdx >= testCases.length) {
46233
46261
  const allPassed = results.every((r) => r.passed);
46234
- setPlayState(allPassed ? "success" : "fail");
46262
+ setIsTesting(false);
46235
46263
  setTestResults(results);
46236
46264
  if (allPassed && completeEvent) {
46237
46265
  emit(`UI:${completeEvent}`, {
@@ -46239,9 +46267,6 @@ function StateArchitectBoard({
46239
46267
  passedTests: results.filter((r) => r.passed).length
46240
46268
  });
46241
46269
  }
46242
- if (!allPassed) {
46243
- setAttempts((prev) => prev + 1);
46244
- }
46245
46270
  return;
46246
46271
  }
46247
46272
  const testCase = testCases[testIdx];
@@ -46264,24 +46289,23 @@ function StateArchitectBoard({
46264
46289
  timerRef.current = setTimeout(runNextTest, stepDurationMs);
46265
46290
  };
46266
46291
  timerRef.current = setTimeout(runNextTest, stepDurationMs);
46267
- }, [playState, transitions, testCases, initialState, stepDurationMs, testEvent, completeEvent, emit]);
46292
+ }, [isTesting, transitions, testCases, initialState, stepDurationMs, testEvent, completeEvent, emit]);
46268
46293
  const handleTryAgain = React83.useCallback(() => {
46269
46294
  if (timerRef.current) clearTimeout(timerRef.current);
46270
- setPlayState("editing");
46295
+ setIsTesting(false);
46271
46296
  setCurrentState(initialState);
46272
46297
  setTestResults([]);
46273
46298
  }, [initialState]);
46274
46299
  const handleReset = React83.useCallback(() => {
46275
46300
  if (timerRef.current) clearTimeout(timerRef.current);
46276
- setTransitions(entityTransitions);
46277
- setPlayState("editing");
46301
+ setIsTesting(false);
46278
46302
  setCurrentState(initialState);
46279
46303
  setTestResults([]);
46280
46304
  setVariables([...entityVariables]);
46281
46305
  setSelectedState(null);
46282
46306
  setAddingFrom(null);
46283
- setAttempts(0);
46284
- }, [entityTransitions, initialState, entityVariables]);
46307
+ if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
46308
+ }, [initialState, entityVariables, playAgainEvent, emit]);
46285
46309
  const codeData = React83.useMemo(() => ({
46286
46310
  name: entityName,
46287
46311
  states: entityStates,
@@ -46377,7 +46401,7 @@ function StateArchitectBoard({
46377
46401
  ]
46378
46402
  }
46379
46403
  ),
46380
- playState === "editing" && /* @__PURE__ */ jsxRuntime.jsx(HStack, { gap: "sm", children: /* @__PURE__ */ jsxRuntime.jsx(
46404
+ !isTesting && /* @__PURE__ */ jsxRuntime.jsx(HStack, { gap: "sm", children: /* @__PURE__ */ jsxRuntime.jsx(
46381
46405
  Button,
46382
46406
  {
46383
46407
  variant: "ghost",
@@ -46399,7 +46423,7 @@ function StateArchitectBoard({
46399
46423
  t2.guardHint,
46400
46424
  ")"
46401
46425
  ] }),
46402
- playState === "editing" && /* @__PURE__ */ jsxRuntime.jsx(
46426
+ !isTesting && /* @__PURE__ */ jsxRuntime.jsx(
46403
46427
  Button,
46404
46428
  {
46405
46429
  variant: "ghost",
@@ -46431,21 +46455,21 @@ function StateArchitectBoard({
46431
46455
  resolved.showCodeView !== false && /* @__PURE__ */ jsxRuntime.jsx(StateJsonView, { data: codeData, label: "View Code" })
46432
46456
  ] })
46433
46457
  ] }),
46434
- playState === "success" && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "p-4 rounded-container bg-success/20 border border-success text-center", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h5", className: "text-success", children: str(resolved.successMessage) || t("stateArchitect.allPassed") }) }),
46435
- playState === "fail" && /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
46458
+ isSuccess && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "p-4 rounded-container bg-success/20 border border-success text-center", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h5", className: "text-success", children: str(resolved.successMessage) || t("stateArchitect.allPassed") }) }),
46459
+ !isTesting && !isSuccess && testResults.some((r) => !r.passed) && /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
46436
46460
  /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "p-4 rounded-container bg-warning/10 border border-warning/30 text-center", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body1", className: "text-foreground font-medium", children: t(ENCOURAGEMENT_KEYS3[Math.min(attempts - 1, ENCOURAGEMENT_KEYS3.length - 1)] ?? ENCOURAGEMENT_KEYS3[0]) }) }),
46437
- attempts >= 3 && hint && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "p-3 rounded-container bg-accent/10 border border-accent/30", children: /* @__PURE__ */ jsxRuntime.jsxs(HStack, { className: "items-start", gap: "xs", children: [
46461
+ !isSuccess && attempts >= 2 && Boolean(str(resolved?.hint)) && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "p-3 rounded-container bg-accent/10 border border-accent/30", children: /* @__PURE__ */ jsxRuntime.jsxs(HStack, { className: "items-start", gap: "xs", children: [
46438
46462
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-accent font-bold shrink-0", children: "\u{1F4A1} " + t("game.hint") + ":" }),
46439
46463
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-foreground", children: hint })
46440
46464
  ] }) })
46441
46465
  ] }),
46442
46466
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", children: [
46443
- playState === "fail" ? /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "primary", onClick: handleTryAgain, children: "\u{1F504} " + t("puzzle.tryAgainButton") }) : /* @__PURE__ */ jsxRuntime.jsx(
46467
+ !isTesting && !isSuccess && testResults.some((r) => !r.passed) ? /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "primary", onClick: handleTryAgain, children: "\u{1F504} " + t("puzzle.tryAgainButton") }) : /* @__PURE__ */ jsxRuntime.jsx(
46444
46468
  Button,
46445
46469
  {
46446
46470
  variant: "primary",
46447
46471
  onClick: handleTest,
46448
- disabled: playState !== "editing",
46472
+ disabled: isTesting,
46449
46473
  children: "\u25B6 " + t("game.runTests")
46450
46474
  }
46451
46475
  ),
@@ -47149,17 +47173,12 @@ var init_UncontrolledBattleBoard = __esm({
47149
47173
  }
47150
47174
  });
47151
47175
  function heroPosition(h) {
47176
+ if ("position" in h && h.position != null) return h.position;
47152
47177
  return vec2(h.position);
47153
47178
  }
47154
- function heroOwner(h) {
47155
- return str(h.owner);
47156
- }
47157
47179
  function heroMovement(h) {
47158
47180
  return num(h.movement);
47159
47181
  }
47160
- function hexPassable(h) {
47161
- return h.passable !== false;
47162
- }
47163
47182
  function defaultIsInRange(from, to, range) {
47164
47183
  return Math.abs(from.x - to.x) + Math.abs(from.y - to.y) <= range;
47165
47184
  }
@@ -47195,38 +47214,41 @@ function WorldMapBoard({
47195
47214
  }) {
47196
47215
  const eventBus = useEventBus();
47197
47216
  const resolved = boardEntity(entity);
47198
- const hexes = rows(resolved?.hexes);
47199
- const heroes = rows(resolved?.heroes);
47217
+ const entityUnits = rows(resolved?.units);
47218
+ const entityTiles = rows(resolved?.tiles);
47200
47219
  const features = propFeatures ?? (Array.isArray(resolved?.features) ? resolved.features : []);
47201
47220
  const selectedHeroId = resolved?.selectedHeroId ?? null;
47202
47221
  const assetManifest = propAssetManifest ?? resolved?.assetManifest;
47203
47222
  const backgroundImage = resolved?.backgroundImage;
47204
47223
  const [hoveredTile, setHoveredTile] = React83.useState(null);
47205
- const selectedHero = React83.useMemo(
47206
- () => heroes.find((h) => str(h.id) === selectedHeroId) ?? null,
47207
- [heroes, selectedHeroId]
47208
- );
47209
47224
  const derivedTiles = React83.useMemo(
47210
- () => hexes.map((hex) => ({
47211
- x: num(hex.x),
47212
- y: num(hex.y),
47213
- terrain: str(hex.terrain),
47214
- terrainSprite: hex.terrainSprite == null ? void 0 : str(hex.terrainSprite)
47225
+ () => entityTiles.map((t) => ({
47226
+ x: num(t.x),
47227
+ y: num(t.y),
47228
+ terrain: str(t.terrain),
47229
+ terrainSprite: t.terrainSprite == null ? void 0 : str(t.terrainSprite),
47230
+ passable: t.passable !== false
47215
47231
  })),
47216
- [hexes]
47232
+ [entityTiles]
47217
47233
  );
47218
47234
  const tiles = propTiles ?? derivedTiles;
47219
47235
  const baseUnits = React83.useMemo(
47220
- () => propUnits ?? heroes.map((hero) => ({
47221
- id: str(hero.id),
47222
- position: heroPosition(hero),
47223
- name: str(hero.name),
47224
- team: heroOwner(hero) === "enemy" ? "enemy" : "player",
47225
- health: 100,
47226
- maxHealth: 100,
47227
- sprite: hero.sprite == null ? void 0 : str(hero.sprite)
47236
+ () => propUnits ?? entityUnits.map((u) => ({
47237
+ id: str(u.id),
47238
+ position: heroPosition(u),
47239
+ name: str(u.name),
47240
+ // lolo uses `team` field (not `owner`)
47241
+ team: str(u.team) === "enemy" ? "enemy" : "player",
47242
+ health: num(u.health) || 100,
47243
+ maxHealth: num(u.maxHealth) || 100,
47244
+ sprite: u.sprite == null ? void 0 : str(u.sprite)
47228
47245
  })),
47229
- [heroes, propUnits]
47246
+ [entityUnits, propUnits]
47247
+ );
47248
+ const gameUnits = baseUnits;
47249
+ const selectedHero = React83.useMemo(
47250
+ () => gameUnits.find((u) => u.id === selectedHeroId) ?? null,
47251
+ [gameUnits, selectedHeroId]
47230
47252
  );
47231
47253
  const MOVE_SPEED_MS_PER_TILE = 300;
47232
47254
  const movementAnimRef = React83.useRef(null);
@@ -47272,51 +47294,50 @@ function WorldMapBoard({
47272
47294
  const validMoves = React83.useMemo(() => {
47273
47295
  if (!selectedHero || heroMovement(selectedHero) <= 0) return [];
47274
47296
  const sp = heroPosition(selectedHero);
47275
- const sOwner = heroOwner(selectedHero);
47297
+ const sTeam = str(selectedHero.team);
47276
47298
  const range = heroMovement(selectedHero);
47277
47299
  const moves = [];
47278
- hexes.forEach((hex) => {
47279
- const hx = num(hex.x);
47280
- const hy = num(hex.y);
47281
- if (!hexPassable(hex)) return;
47282
- if (hx === sp.x && hy === sp.y) return;
47283
- if (!isInRange(sp, { x: hx, y: hy }, range)) return;
47284
- if (heroes.some((h) => {
47285
- const hp = heroPosition(h);
47286
- return hp.x === hx && hp.y === hy && heroOwner(h) === sOwner;
47300
+ tiles.forEach((t) => {
47301
+ const tx = t.x;
47302
+ const ty = t.y;
47303
+ if (t.passable === false) return;
47304
+ if (tx === sp.x && ty === sp.y) return;
47305
+ if (!isInRange(sp, { x: tx, y: ty }, range)) return;
47306
+ if (gameUnits.some((u) => {
47307
+ const up = u.position ?? { x: u.x ?? -1, y: u.y ?? -1 };
47308
+ return up.x === tx && up.y === ty && str(u.team) === sTeam;
47287
47309
  })) return;
47288
- moves.push({ x: hx, y: hy });
47310
+ moves.push({ x: tx, y: ty });
47289
47311
  });
47290
47312
  return moves;
47291
- }, [selectedHero, hexes, heroes, isInRange]);
47313
+ }, [selectedHero, tiles, gameUnits, isInRange]);
47292
47314
  const attackTargets = React83.useMemo(() => {
47293
47315
  if (!selectedHero || heroMovement(selectedHero) <= 0) return [];
47294
47316
  const sp = heroPosition(selectedHero);
47295
- const sOwner = heroOwner(selectedHero);
47317
+ const sTeam = str(selectedHero.team);
47296
47318
  const range = heroMovement(selectedHero);
47297
- return heroes.filter((h) => heroOwner(h) !== sOwner).filter((h) => isInRange(sp, heroPosition(h), range)).map((h) => heroPosition(h));
47298
- }, [selectedHero, heroes, isInRange]);
47299
- const maxY = Math.max(...hexes.map((h) => num(h.y)), 0);
47319
+ 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 });
47320
+ }, [selectedHero, gameUnits, isInRange]);
47321
+ const maxY = Math.max(...tiles.map((t) => t.y), 0);
47300
47322
  const baseOffsetX = (maxY + 1) * (TILE_WIDTH * scale / 2);
47301
47323
  const tileToScreen = React83.useCallback(
47302
47324
  (tx, ty) => isoToScreen(tx, ty, scale, baseOffsetX),
47303
47325
  [scale, baseOffsetX]
47304
47326
  );
47305
47327
  const hoveredHex = React83.useMemo(
47306
- () => hoveredTile ? hexes.find((h) => num(h.x) === hoveredTile.x && num(h.y) === hoveredTile.y) ?? null : null,
47307
- [hoveredTile, hexes]
47328
+ () => hoveredTile ? tiles.find((t) => t.x === hoveredTile.x && t.y === hoveredTile.y) ?? null : null,
47329
+ [hoveredTile, tiles]
47308
47330
  );
47309
47331
  const hoveredHero = React83.useMemo(
47310
- () => hoveredTile ? heroes.find((h) => {
47311
- const hp = heroPosition(h);
47312
- return hp.x === hoveredTile.x && hp.y === hoveredTile.y;
47332
+ () => hoveredTile ? gameUnits.find((u) => {
47333
+ const up = u.position ?? { x: u.x ?? -1, y: u.y ?? -1 };
47334
+ return up.x === hoveredTile.x && up.y === hoveredTile.y;
47313
47335
  }) ?? null : null,
47314
- [hoveredTile, heroes]
47336
+ [hoveredTile, gameUnits]
47315
47337
  );
47316
47338
  const handleTileClick = React83.useCallback((x, y) => {
47317
47339
  if (movementAnimRef.current) return;
47318
- const hex = hexes.find((h) => num(h.x) === x && num(h.y) === y);
47319
- if (!hex) return;
47340
+ const tile = tiles.find((t) => t.x === x && t.y === y);
47320
47341
  if (tileClickEvent) {
47321
47342
  eventBus.emit(`UI:${tileClickEvent}`, { x, y });
47322
47343
  }
@@ -47327,38 +47348,39 @@ function WorldMapBoard({
47327
47348
  if (heroMoveEvent) {
47328
47349
  eventBus.emit(`UI:${heroMoveEvent}`, { heroId, toX: x, toY: y });
47329
47350
  }
47330
- const feature = str(hex.feature);
47351
+ const feature = tile ? str(tile.feature) : "";
47331
47352
  if (feature && feature !== "none") {
47332
- onFeatureEnter?.(heroId, hex);
47353
+ const tileRow = tile;
47354
+ onFeatureEnter?.(heroId, tileRow);
47333
47355
  if (featureEnterEvent) {
47334
- eventBus.emit(`UI:${featureEnterEvent}`, { heroId, feature, hex });
47356
+ eventBus.emit(`UI:${featureEnterEvent}`, { heroId, feature, hex: tileRow });
47335
47357
  }
47336
47358
  }
47337
47359
  });
47338
47360
  return;
47339
47361
  }
47340
- const enemy = heroes.find((h) => {
47341
- const hp = heroPosition(h);
47342
- return hp.x === x && hp.y === y && heroOwner(h) === "enemy";
47362
+ const enemy = gameUnits.find((u) => {
47363
+ const up = u.position ?? { x: u.x ?? -1, y: u.y ?? -1 };
47364
+ return up.x === x && up.y === y && str(u.team) === "enemy";
47343
47365
  });
47344
47366
  if (selectedHero && enemy && attackTargets.some((t) => t.x === x && t.y === y)) {
47345
47367
  const attackerId = str(selectedHero.id);
47346
- const defenderId = str(enemy.id);
47368
+ const defenderId = enemy.id;
47347
47369
  onBattleEncounter?.(attackerId, defenderId);
47348
47370
  if (battleEncounterEvent) {
47349
47371
  eventBus.emit(`UI:${battleEncounterEvent}`, { attackerId, defenderId });
47350
47372
  }
47351
47373
  }
47352
- }, [hexes, heroes, selectedHero, validMoves, attackTargets, startMoveAnimation, onHeroMove, onFeatureEnter, onBattleEncounter, eventBus, tileClickEvent, heroMoveEvent, featureEnterEvent, battleEncounterEvent]);
47374
+ }, [tiles, gameUnits, selectedHero, validMoves, attackTargets, startMoveAnimation, onHeroMove, onFeatureEnter, onBattleEncounter, eventBus, tileClickEvent, heroMoveEvent, featureEnterEvent, battleEncounterEvent]);
47353
47375
  const handleUnitClick = React83.useCallback((unitId) => {
47354
- const hero = heroes.find((h) => str(h.id) === unitId);
47355
- if (hero && (heroOwner(hero) === "player" || allowMoveAllHeroes)) {
47376
+ const unit = gameUnits.find((u) => u.id === unitId);
47377
+ if (unit && (str(unit.team) === "player" || allowMoveAllHeroes)) {
47356
47378
  onHeroSelect?.(unitId);
47357
47379
  if (heroSelectEvent) {
47358
47380
  eventBus.emit(`UI:${heroSelectEvent}`, { heroId: unitId });
47359
47381
  }
47360
47382
  }
47361
- }, [heroes, onHeroSelect, allowMoveAllHeroes, eventBus, heroSelectEvent]);
47383
+ }, [gameUnits, onHeroSelect, allowMoveAllHeroes, eventBus, heroSelectEvent]);
47362
47384
  const selectHero = React83.useCallback((id) => {
47363
47385
  onHeroSelect?.(id);
47364
47386
  if (heroSelectEvent) {