@almadar/ui 5.46.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.
@@ -7324,6 +7324,7 @@ var init_Sprite = __esm({
7324
7324
  }
7325
7325
  });
7326
7326
  function StateIndicator({
7327
+ assetUrl = DEFAULT_ASSET_URL,
7327
7328
  state = "idle",
7328
7329
  label,
7329
7330
  size = "md",
@@ -7346,18 +7347,29 @@ function StateIndicator({
7346
7347
  className
7347
7348
  ),
7348
7349
  children: [
7349
- /* @__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 }) }),
7350
+ /* @__PURE__ */ jsxRuntime.jsx(Box, { as: "span", children: assetUrl ? /* @__PURE__ */ jsxRuntime.jsx(
7351
+ "img",
7352
+ {
7353
+ src: assetUrl,
7354
+ alt: displayLabel,
7355
+ width: 16,
7356
+ height: 16,
7357
+ style: { imageRendering: "pixelated", objectFit: "contain" },
7358
+ className: "flex-shrink-0"
7359
+ }
7360
+ ) : 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 }) }),
7350
7361
  /* @__PURE__ */ jsxRuntime.jsx(Box, { as: "span", children: displayLabel })
7351
7362
  ]
7352
7363
  }
7353
7364
  );
7354
7365
  }
7355
- var DEFAULT_STATE_STYLES, DEFAULT_STYLE, SIZE_CLASSES;
7366
+ var DEFAULT_ASSET_URL, DEFAULT_STATE_STYLES, DEFAULT_STYLE, SIZE_CLASSES;
7356
7367
  var init_StateIndicator = __esm({
7357
7368
  "components/game/atoms/StateIndicator.tsx"() {
7358
7369
  init_Box();
7359
7370
  init_Icon();
7360
7371
  init_cn();
7372
+ DEFAULT_ASSET_URL = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
7361
7373
  DEFAULT_STATE_STYLES = {
7362
7374
  idle: { icon: "\u23F8", bgClass: "bg-muted" },
7363
7375
  active: { icon: "\u25B6", bgClass: "bg-success" },
@@ -7432,6 +7444,7 @@ var init_TimerDisplay = __esm({
7432
7444
  }
7433
7445
  });
7434
7446
  function ResourceCounter({
7447
+ assetUrl = DEFAULT_ASSET_URL2,
7435
7448
  icon,
7436
7449
  label = "Gold",
7437
7450
  value = 250,
@@ -7451,7 +7464,17 @@ function ResourceCounter({
7451
7464
  className
7452
7465
  ),
7453
7466
  children: [
7454
- 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 }) }),
7467
+ assetUrl ? /* @__PURE__ */ jsxRuntime.jsx(
7468
+ "img",
7469
+ {
7470
+ src: assetUrl,
7471
+ alt: label,
7472
+ width: sizes.img,
7473
+ height: sizes.img,
7474
+ style: { imageRendering: "pixelated", objectFit: "contain" },
7475
+ className: "flex-shrink-0"
7476
+ }
7477
+ ) : 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,
7455
7478
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: label }),
7456
7479
  /* @__PURE__ */ jsxRuntime.jsxs("span", { className: cn("font-bold tabular-nums", color && (color in colorTokenClasses2 ? colorTokenClasses2[color] : color)), children: [
7457
7480
  value,
@@ -7464,7 +7487,7 @@ function ResourceCounter({
7464
7487
  }
7465
7488
  );
7466
7489
  }
7467
- var colorTokenClasses2, sizeMap5;
7490
+ var colorTokenClasses2, DEFAULT_ASSET_URL2, sizeMap5;
7468
7491
  var init_ResourceCounter = __esm({
7469
7492
  "components/game/atoms/ResourceCounter.tsx"() {
7470
7493
  init_cn();
@@ -7477,16 +7500,17 @@ var init_ResourceCounter = __esm({
7477
7500
  error: "text-error",
7478
7501
  muted: "text-muted-foreground"
7479
7502
  };
7503
+ DEFAULT_ASSET_URL2 = "https://almadar-kflow-assets.web.app/shared/world-map/gold_mine.png";
7480
7504
  sizeMap5 = {
7481
- sm: { wrapper: "text-xs gap-1 px-1.5 py-0.5", icon: "text-sm" },
7482
- md: { wrapper: "text-sm gap-1.5 px-2 py-1", icon: "text-base" },
7483
- lg: { wrapper: "text-base gap-2 px-3 py-1.5", icon: "text-lg" }
7505
+ sm: { wrapper: "text-xs gap-1 px-1.5 py-0.5", icon: "text-sm", img: 16 },
7506
+ md: { wrapper: "text-sm gap-1.5 px-2 py-1", icon: "text-base", img: 20 },
7507
+ lg: { wrapper: "text-base gap-2 px-3 py-1.5", icon: "text-lg", img: 28 }
7484
7508
  };
7485
7509
  ResourceCounter.displayName = "ResourceCounter";
7486
7510
  }
7487
7511
  });
7488
7512
  function ItemSlot({
7489
- assetUrl = DEFAULT_ASSET_URL,
7513
+ assetUrl = DEFAULT_ASSET_URL3,
7490
7514
  icon = "sword",
7491
7515
  label = "Iron Sword",
7492
7516
  quantity,
@@ -7545,7 +7569,7 @@ function ItemSlot({
7545
7569
  }
7546
7570
  );
7547
7571
  }
7548
- var sizeMap6, rarityBorderMap, rarityGlowMap, DEFAULT_ASSET_URL, assetSizeMap;
7572
+ var sizeMap6, rarityBorderMap, rarityGlowMap, DEFAULT_ASSET_URL3, assetSizeMap;
7549
7573
  var init_ItemSlot = __esm({
7550
7574
  "components/game/atoms/ItemSlot.tsx"() {
7551
7575
  "use client";
@@ -7570,7 +7594,7 @@ var init_ItemSlot = __esm({
7570
7594
  epic: "shadow-lg",
7571
7595
  legendary: "shadow-lg"
7572
7596
  };
7573
- DEFAULT_ASSET_URL = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
7597
+ DEFAULT_ASSET_URL3 = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
7574
7598
  assetSizeMap = {
7575
7599
  sm: 28,
7576
7600
  md: 40,
@@ -7643,6 +7667,7 @@ function getComboScale(combo) {
7643
7667
  return "";
7644
7668
  }
7645
7669
  function ComboCounter({
7670
+ assetUrl = DEFAULT_ASSET_URL4,
7646
7671
  combo = 5,
7647
7672
  multiplier,
7648
7673
  streak,
@@ -7662,6 +7687,17 @@ function ComboCounter({
7662
7687
  className
7663
7688
  ),
7664
7689
  children: [
7690
+ assetUrl && /* @__PURE__ */ jsxRuntime.jsx(
7691
+ "img",
7692
+ {
7693
+ src: assetUrl,
7694
+ alt: "combo",
7695
+ width: 24,
7696
+ height: 24,
7697
+ style: { imageRendering: "pixelated", objectFit: "contain" },
7698
+ className: "flex-shrink-0 mb-0.5"
7699
+ }
7700
+ ),
7665
7701
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("font-black tabular-nums leading-none", sizes.combo, getComboIntensity(combo)), children: combo }),
7666
7702
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("font-bold uppercase tracking-wider text-muted-foreground", sizes.label), children: "combo" }),
7667
7703
  multiplier != null && multiplier > 1 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: cn("font-bold text-warning tabular-nums", sizes.multiplier), children: [
@@ -7676,10 +7712,11 @@ function ComboCounter({
7676
7712
  }
7677
7713
  );
7678
7714
  }
7679
- var sizeMap8;
7715
+ var DEFAULT_ASSET_URL4, sizeMap8;
7680
7716
  var init_ComboCounter = __esm({
7681
7717
  "components/game/atoms/ComboCounter.tsx"() {
7682
7718
  init_cn();
7719
+ DEFAULT_ASSET_URL4 = "https://almadar-kflow-assets.web.app/shared/effects/flash/flash00.png";
7683
7720
  sizeMap8 = {
7684
7721
  sm: { combo: "text-lg", label: "text-xs", multiplier: "text-xs" },
7685
7722
  md: { combo: "text-2xl", label: "text-xs", multiplier: "text-sm" },
@@ -7773,6 +7810,7 @@ var init_XPBar = __esm({
7773
7810
  }
7774
7811
  });
7775
7812
  function WaypointMarker({
7813
+ assetUrl = DEFAULT_ASSET_URL5,
7776
7814
  label,
7777
7815
  icon,
7778
7816
  active = true,
@@ -7811,7 +7849,17 @@ function WaypointMarker({
7811
7849
  active && !completed && "bg-info text-foreground",
7812
7850
  !active && !completed && "bg-muted"
7813
7851
  ),
7814
- children: completed ? checkIcon : icon && (typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: icon }) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon }))
7852
+ children: completed ? checkIcon : assetUrl ? /* @__PURE__ */ jsxRuntime.jsx(
7853
+ "img",
7854
+ {
7855
+ src: assetUrl,
7856
+ alt: label,
7857
+ width: sizes.img,
7858
+ height: sizes.img,
7859
+ style: { imageRendering: "pixelated", objectFit: "contain" },
7860
+ className: "flex-shrink-0"
7861
+ }
7862
+ ) : icon ? typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: icon }) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon }) : null
7815
7863
  }
7816
7864
  )
7817
7865
  ] }),
@@ -7828,15 +7876,16 @@ function WaypointMarker({
7828
7876
  )
7829
7877
  ] });
7830
7878
  }
7831
- var sizeMap10, checkIcon;
7879
+ var DEFAULT_ASSET_URL5, sizeMap10, checkIcon;
7832
7880
  var init_WaypointMarker = __esm({
7833
7881
  "components/game/atoms/WaypointMarker.tsx"() {
7834
7882
  init_cn();
7835
7883
  init_Icon();
7884
+ DEFAULT_ASSET_URL5 = "https://almadar-kflow-assets.web.app/shared/world-map/battle_marker.png";
7836
7885
  sizeMap10 = {
7837
- sm: { dot: "w-4 h-4", ring: "w-6 h-6", label: "text-xs mt-1" },
7838
- md: { dot: "w-6 h-6", ring: "w-8 h-8", label: "text-sm mt-1.5" },
7839
- lg: { dot: "w-8 h-8", ring: "w-10 h-10", label: "text-base mt-2" }
7886
+ sm: { dot: "w-4 h-4", ring: "w-6 h-6", label: "text-xs mt-1", img: 12 },
7887
+ md: { dot: "w-6 h-6", ring: "w-8 h-8", label: "text-sm mt-1.5", img: 18 },
7888
+ lg: { dot: "w-8 h-8", ring: "w-10 h-10", label: "text-base mt-2", img: 24 }
7840
7889
  };
7841
7890
  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" }) });
7842
7891
  WaypointMarker.displayName = "WaypointMarker";
@@ -7849,6 +7898,7 @@ function formatDuration(seconds) {
7849
7898
  return `${Math.round(seconds)}s`;
7850
7899
  }
7851
7900
  function StatusEffect({
7901
+ assetUrl = DEFAULT_ASSET_URL6,
7852
7902
  icon = "shield",
7853
7903
  label = "Shield",
7854
7904
  duration = 30,
@@ -7869,7 +7919,17 @@ function StatusEffect({
7869
7919
  ),
7870
7920
  title: label,
7871
7921
  children: [
7872
- /* @__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" }) }),
7922
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("flex items-center justify-center", sizes.icon), children: assetUrl ? /* @__PURE__ */ jsxRuntime.jsx(
7923
+ "img",
7924
+ {
7925
+ src: assetUrl,
7926
+ alt: label,
7927
+ width: sizes.img,
7928
+ height: sizes.img,
7929
+ style: { imageRendering: "pixelated", objectFit: "contain" },
7930
+ className: "flex-shrink-0"
7931
+ }
7932
+ ) : typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: icon, size: "sm" }) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon, size: "sm" }) }),
7873
7933
  duration !== void 0 && /* @__PURE__ */ jsxRuntime.jsx(
7874
7934
  "span",
7875
7935
  {
@@ -7896,15 +7956,16 @@ function StatusEffect({
7896
7956
  label && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground mt-0.5 text-center whitespace-nowrap", children: label })
7897
7957
  ] });
7898
7958
  }
7899
- var sizeMap11, variantStyles7;
7959
+ var DEFAULT_ASSET_URL6, sizeMap11, variantStyles7;
7900
7960
  var init_StatusEffect = __esm({
7901
7961
  "components/game/atoms/StatusEffect.tsx"() {
7902
7962
  init_cn();
7903
7963
  init_Icon();
7964
+ DEFAULT_ASSET_URL6 = "https://almadar-kflow-assets.web.app/shared/effects/particles/flame_01.png";
7904
7965
  sizeMap11 = {
7905
- sm: { container: "w-8 h-8", icon: "text-sm", badge: "text-xs -top-1 -right-1 w-4 h-4", timer: "text-[9px]" },
7906
- md: { container: "w-10 h-10", icon: "text-base", badge: "text-xs -top-1 -right-1 w-5 h-5", timer: "text-xs" },
7907
- 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" }
7966
+ 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 },
7967
+ 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 },
7968
+ 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 }
7908
7969
  };
7909
7970
  variantStyles7 = {
7910
7971
  buff: "border-success bg-success/20",
@@ -11473,15 +11534,15 @@ function BattleBoard({
11473
11534
  const board = boardEntity(entity) ?? {};
11474
11535
  const tiles = propTiles ?? (Array.isArray(board.tiles) ? board.tiles : []);
11475
11536
  const features = propFeatures ?? (Array.isArray(board.features) ? board.features : []);
11476
- const boardWidth = num(board.boardWidth, 8);
11477
- const boardHeight = num(board.boardHeight, 6);
11537
+ const boardWidth = num(board.gridWidth ?? board.boardWidth, 8);
11538
+ const boardHeight = num(board.gridHeight ?? board.boardHeight, 6);
11478
11539
  const assetManifest = propAssetManifest ?? board.assetManifest;
11479
11540
  const backgroundImage = board.backgroundImage;
11480
11541
  const units = rows(board.units);
11481
11542
  const selectedUnitId = board.selectedUnitId ?? null;
11482
11543
  const currentPhase = str(board.phase) || "observation";
11483
11544
  const currentTurn = num(board.turn, 1);
11484
- const gameResult = board.gameResult ?? null;
11545
+ const gameResult = board.result ?? null;
11485
11546
  const eventBus = useEventBus();
11486
11547
  const { t } = hooks.useTranslate();
11487
11548
  const [hoveredTile, setHoveredTile] = React82.useState(null);
@@ -11502,7 +11563,7 @@ function BattleBoard({
11502
11563
  const validMoves = React82.useMemo(() => {
11503
11564
  if (!selectedUnit || currentPhase !== "movement") return [];
11504
11565
  const moves = [];
11505
- const range = num(selectedUnit.movement);
11566
+ const range = num(board.movementRange, 2);
11506
11567
  const origin = unitPosition(selectedUnit);
11507
11568
  for (let dy = -range; dy <= range; dy++) {
11508
11569
  for (let dx = -range; dx <= range; dx++) {
@@ -19787,15 +19848,24 @@ function CastleBoard({
19787
19848
  featureClickEvent,
19788
19849
  unitClickEvent,
19789
19850
  tileClickEvent,
19851
+ playAgainEvent,
19790
19852
  className
19791
19853
  }) {
19792
19854
  const eventBus = useEventBus();
19855
+ const { t } = hooks.useTranslate();
19793
19856
  const resolved = boardEntity(entity);
19794
19857
  const tiles = propTiles ?? (Array.isArray(resolved?.tiles) ? resolved.tiles : []);
19795
19858
  const features = propFeatures ?? (Array.isArray(resolved?.features) ? resolved.features : []);
19796
19859
  const units = propUnits ?? (Array.isArray(resolved?.units) ? resolved.units : []);
19797
19860
  const assetManifest = propAssetManifest ?? resolved?.assetManifest;
19798
19861
  const backgroundImage = resolved?.backgroundImage;
19862
+ const gold = num(resolved?.gold);
19863
+ const health = num(resolved?.health);
19864
+ const maxHealth = num(resolved?.maxHealth);
19865
+ const wave = num(resolved?.wave);
19866
+ const tickCount = num(resolved?.tickCount);
19867
+ const buildings = rows(resolved?.buildings);
19868
+ const result = str(resolved?.result) || "none";
19799
19869
  const [hoveredTile, setHoveredTile] = React82.useState(null);
19800
19870
  const [selectedFeature, setSelectedFeature] = React82.useState(null);
19801
19871
  const hoveredFeature = React82.useMemo(() => {
@@ -19808,7 +19878,7 @@ function CastleBoard({
19808
19878
  (u) => u.position?.x === hoveredTile.x && u.position?.y === hoveredTile.y
19809
19879
  ) ?? null;
19810
19880
  }, [hoveredTile, units]);
19811
- const maxY = Math.max(...tiles.map((t) => t.y), 0);
19881
+ const maxY = Math.max(...tiles.map((t2) => t2.y), 0);
19812
19882
  const baseOffsetX = (maxY + 1) * (TILE_WIDTH * scale / 2);
19813
19883
  const tileToScreen = React82.useCallback(
19814
19884
  (tx, ty) => isoToScreen(tx, ty, scale, baseOffsetX),
@@ -19843,6 +19913,11 @@ function CastleBoard({
19843
19913
  }
19844
19914
  }, [units, onUnitClick, unitClickEvent, eventBus]);
19845
19915
  const clearSelection = React82.useCallback(() => setSelectedFeature(null), []);
19916
+ const handlePlayAgain = React82.useCallback(() => {
19917
+ if (playAgainEvent) {
19918
+ eventBus.emit(`UI:${playAgainEvent}`, {});
19919
+ }
19920
+ }, [playAgainEvent, eventBus]);
19846
19921
  const ctx = React82.useMemo(
19847
19922
  () => ({
19848
19923
  hoveredTile,
@@ -19851,11 +19926,18 @@ function CastleBoard({
19851
19926
  selectedFeature,
19852
19927
  clearSelection,
19853
19928
  tileToScreen,
19854
- scale
19929
+ scale,
19930
+ gold,
19931
+ health,
19932
+ maxHealth,
19933
+ wave,
19934
+ tickCount,
19935
+ buildings,
19936
+ result
19855
19937
  }),
19856
- [hoveredTile, hoveredFeature, hoveredUnit, selectedFeature, clearSelection, tileToScreen, scale]
19938
+ [hoveredTile, hoveredFeature, hoveredUnit, selectedFeature, clearSelection, tileToScreen, scale, gold, health, maxHealth, wave, tickCount, buildings, result]
19857
19939
  );
19858
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("castle-board min-h-screen flex flex-col bg-background", className), children: [
19940
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("castle-board relative min-h-screen flex flex-col bg-background", className), children: [
19859
19941
  header && header(ctx),
19860
19942
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 overflow-hidden", children: [
19861
19943
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 overflow-auto p-4 relative", children: [
@@ -19880,7 +19962,29 @@ function CastleBoard({
19880
19962
  ] }),
19881
19963
  sidePanel && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-96 shrink-0 border-l border-border bg-surface overflow-y-auto", children: sidePanel(ctx) })
19882
19964
  ] }),
19883
- footer && footer(ctx)
19965
+ footer && footer(ctx),
19966
+ 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: [
19967
+ /* @__PURE__ */ jsxRuntime.jsx(
19968
+ Typography,
19969
+ {
19970
+ variant: "h2",
19971
+ className: cn(
19972
+ "text-4xl font-black tracking-widest uppercase",
19973
+ result === "victory" ? "text-warning" : "text-error"
19974
+ ),
19975
+ children: result === "victory" ? t("battle.victory") : t("battle.defeat")
19976
+ }
19977
+ ),
19978
+ /* @__PURE__ */ jsxRuntime.jsx(
19979
+ Button,
19980
+ {
19981
+ variant: "primary",
19982
+ className: "px-8 py-3 font-semibold",
19983
+ onClick: handlePlayAgain,
19984
+ children: t("battle.playAgain")
19985
+ }
19986
+ )
19987
+ ] }) })
19884
19988
  ] });
19885
19989
  }
19886
19990
  var init_CastleBoard = __esm({
@@ -19888,6 +19992,10 @@ var init_CastleBoard = __esm({
19888
19992
  "use client";
19889
19993
  init_cn();
19890
19994
  init_useEventBus();
19995
+ init_Box();
19996
+ init_Button();
19997
+ init_Typography();
19998
+ init_Stack();
19891
19999
  init_IsometricCanvas();
19892
20000
  init_boardEntity();
19893
20001
  init_isometric();
@@ -20764,22 +20872,16 @@ function ClassifierBoard({
20764
20872
  const { emit } = useEventBus();
20765
20873
  const { t } = hooks.useTranslate();
20766
20874
  const resolved = boardEntity(entity);
20767
- const [localAssignments, setLocalAssignments] = React82.useState({});
20768
20875
  const [headerError, setHeaderError] = React82.useState(false);
20769
- const [localSubmitted, setLocalSubmitted] = React82.useState(false);
20770
- const [localAttempts, setLocalAttempts] = React82.useState(0);
20771
- const [showHint, setShowHint] = React82.useState(false);
20772
20876
  const items = Array.isArray(resolved?.items) ? resolved.items : [];
20773
20877
  const categories = Array.isArray(resolved?.categories) ? resolved.categories : [];
20774
- const entityResult = str(resolved?.result);
20775
- const entityDrivesResult = entityResult.length > 0;
20776
- const entityHasAssignments = items.some((item) => item.assignedCategory != null);
20777
- const assignments = entityHasAssignments ? items.reduce((acc, item) => {
20778
- if (item.assignedCategory != null) acc[item.id] = item.assignedCategory;
20878
+ const result = str(resolved?.result);
20879
+ const submitted = result === "win";
20880
+ const attempts = num(resolved?.attempts);
20881
+ const assignments = items.reduce((acc, item) => {
20882
+ if (item.assignedCategory != null && item.assignedCategory !== "") acc[item.id] = item.assignedCategory;
20779
20883
  return acc;
20780
- }, {}) : localAssignments;
20781
- const attempts = entityDrivesResult ? num(resolved?.attempts) : localAttempts;
20782
- const submitted = entityDrivesResult || localSubmitted;
20884
+ }, {});
20783
20885
  const unassignedItems = items.filter((item) => !assignments[item.id]);
20784
20886
  const allAssigned = items.length > 0 && Object.keys(assignments).length === items.length;
20785
20887
  const results = submitted ? items.map((item) => ({
@@ -20787,54 +20889,25 @@ function ClassifierBoard({
20787
20889
  assigned: assignments[item.id],
20788
20890
  correct: assignments[item.id] === item.correctCategory
20789
20891
  })) : [];
20790
- const allCorrect = entityDrivesResult ? entityResult === "success" : results.length > 0 && results.every((r) => r.correct);
20892
+ const allCorrect = result === "win";
20791
20893
  const correctCount = results.filter((r) => r.correct).length;
20894
+ const showHint = attempts >= 2 && Boolean(str(resolved?.hint));
20792
20895
  const handleAssign = (itemId, categoryId) => {
20793
20896
  if (submitted) return;
20794
- if (assignEvent) {
20795
- emit(`UI:${assignEvent}`, { itemId, categoryId });
20796
- }
20797
- if (!entityHasAssignments) {
20798
- setLocalAssignments((prev) => ({ ...prev, [itemId]: categoryId }));
20799
- }
20897
+ if (assignEvent) emit(`UI:${assignEvent}`, { itemId, categoryId });
20800
20898
  };
20801
20899
  const handleUnassign = (itemId) => {
20802
20900
  if (submitted) return;
20803
- if (!entityHasAssignments) {
20804
- setLocalAssignments((prev) => {
20805
- const next = { ...prev };
20806
- delete next[itemId];
20807
- return next;
20808
- });
20809
- }
20901
+ if (assignEvent) emit(`UI:${assignEvent}`, { itemId, categoryId: "" });
20810
20902
  };
20811
- const handleSubmit = React82.useCallback(() => {
20812
- if (checkEvent) {
20813
- emit(`UI:${checkEvent}`, {});
20814
- }
20815
- if (!entityDrivesResult) {
20816
- setLocalSubmitted(true);
20817
- setLocalAttempts((a) => a + 1);
20818
- const correct = items.every((item) => assignments[item.id] === item.correctCategory);
20819
- if (correct) {
20820
- emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
20821
- }
20822
- }
20823
- }, [checkEvent, entityDrivesResult, items, assignments, attempts, completeEvent, emit]);
20824
- const handleReset = () => {
20825
- if (!entityDrivesResult) setLocalSubmitted(false);
20826
- if (attempts >= 2 && str(resolved?.hint)) {
20827
- setShowHint(true);
20903
+ const handleSubmit = () => {
20904
+ if (checkEvent) emit(`UI:${checkEvent}`, {});
20905
+ if (allAssigned && items.every((item) => assignments[item.id] === item.correctCategory)) {
20906
+ emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
20828
20907
  }
20829
20908
  };
20830
20909
  const handleFullReset = () => {
20831
- if (playAgainEvent) {
20832
- emit(`UI:${playAgainEvent}`, {});
20833
- }
20834
- setLocalAssignments({});
20835
- setLocalSubmitted(false);
20836
- setLocalAttempts(0);
20837
- setShowHint(false);
20910
+ if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
20838
20911
  };
20839
20912
  if (!resolved) return null;
20840
20913
  const theme = resolved.theme ?? void 0;
@@ -20873,17 +20946,17 @@ function ClassifierBoard({
20873
20946
  /* @__PURE__ */ jsxRuntime.jsx(Badge, { size: "sm", children: catItems.length })
20874
20947
  ] }),
20875
20948
  /* @__PURE__ */ jsxRuntime.jsx(HStack, { gap: "xs", className: "flex-wrap min-h-[2rem]", children: catItems.map((item) => {
20876
- const result = results.find((r) => r.item.id === item.id);
20949
+ const result2 = results.find((r) => r.item.id === item.id);
20877
20950
  return /* @__PURE__ */ jsxRuntime.jsxs(
20878
20951
  Badge,
20879
20952
  {
20880
20953
  size: "sm",
20881
- className: `cursor-pointer ${result ? result.correct ? "border-success bg-success/10" : "border-error bg-error/10" : ""}`,
20954
+ className: `cursor-pointer ${result2 ? result2.correct ? "border-success bg-success/10" : "border-error bg-error/10" : ""}`,
20882
20955
  onClick: () => handleUnassign(item.id),
20883
20956
  children: [
20884
20957
  item.iconUrl && /* @__PURE__ */ jsxRuntime.jsx("img", { src: item.iconUrl, alt: "", className: "w-3 h-3 object-contain inline-block" }),
20885
20958
  item.label,
20886
- result && /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: result.correct ? LucideIcons2.CheckCircle : LucideIcons2.XCircle, size: "xs", className: result.correct ? "text-success" : "text-error" })
20959
+ result2 && /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: result2.correct ? LucideIcons2.CheckCircle : LucideIcons2.XCircle, size: "xs", className: result2.correct ? "text-success" : "text-error" })
20887
20960
  ]
20888
20961
  },
20889
20962
  item.id
@@ -20912,10 +20985,10 @@ function ClassifierBoard({
20912
20985
  ] }) }),
20913
20986
  showHint && hint && /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: hint }) }),
20914
20987
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", justify: "center", children: [
20915
- !submitted ? /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: !allAssigned, children: [
20988
+ !submitted && /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: !allAssigned, children: [
20916
20989
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: LucideIcons2.Send, size: "sm" }),
20917
20990
  t("classifier.check")
20918
- ] }) : !allCorrect ? /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "primary", onClick: handleReset, children: t("classifier.tryAgain") }) : null,
20991
+ ] }),
20919
20992
  /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "secondary", onClick: handleFullReset, children: [
20920
20993
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: LucideIcons2.RotateCcw, size: "sm" }),
20921
20994
  t("classifier.reset")
@@ -28944,7 +29017,7 @@ var init_MapView = __esm({
28944
29017
  shadowSize: [41, 41]
28945
29018
  });
28946
29019
  L.Marker.prototype.options.icon = defaultIcon;
28947
- const { useEffect: useEffect74, useRef: useRef68, useCallback: useCallback112, useState: useState107 } = React82__namespace.default;
29020
+ const { useEffect: useEffect74, useRef: useRef68, useCallback: useCallback111, useState: useState107 } = React82__namespace.default;
28948
29021
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
28949
29022
  const { useEventBus: useEventBus3 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
28950
29023
  function MapUpdater({ centerLat, centerLng, zoom }) {
@@ -28990,7 +29063,7 @@ var init_MapView = __esm({
28990
29063
  }) {
28991
29064
  const eventBus = useEventBus3();
28992
29065
  const [clickedPosition, setClickedPosition] = useState107(null);
28993
- const handleMapClick = useCallback112((lat, lng) => {
29066
+ const handleMapClick = useCallback111((lat, lng) => {
28994
29067
  if (showClickedPin) {
28995
29068
  setClickedPosition({ lat, lng });
28996
29069
  }
@@ -28999,7 +29072,7 @@ var init_MapView = __esm({
28999
29072
  eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
29000
29073
  }
29001
29074
  }, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
29002
- const handleMarkerClick = useCallback112((marker) => {
29075
+ const handleMarkerClick = useCallback111((marker) => {
29003
29076
  onMarkerClick?.(marker);
29004
29077
  if (markerClickEvent) {
29005
29078
  eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
@@ -38230,44 +38303,27 @@ function DebuggerBoard({
38230
38303
  const { t } = hooks.useTranslate();
38231
38304
  const resolved = boardEntity(entity);
38232
38305
  const [headerError, setHeaderError] = React82.useState(false);
38233
- const [showHint, setShowHint] = React82.useState(false);
38234
38306
  const lines = Array.isArray(resolved?.lines) ? resolved.lines : [];
38235
- const result = resolved?.result ?? null;
38307
+ const result = str(resolved?.result) || "none";
38236
38308
  const attempts = num(resolved?.attempts);
38237
- const submitted = result != null;
38309
+ const submitted = result === "win";
38310
+ const showHint = !submitted && attempts >= 2 && Boolean(str(resolved?.hint));
38238
38311
  const bugLines = lines.filter((l) => l.isBug);
38239
38312
  const flaggedLines = lines.filter((l) => l.isFlagged);
38240
38313
  const correctFlags = lines.filter((l) => l.isBug && l.isFlagged);
38241
38314
  const falseFlags = lines.filter((l) => !l.isBug && l.isFlagged);
38242
- const allCorrect = result === "win";
38315
+ const allCorrect = submitted;
38243
38316
  const toggleLine = (lineId) => {
38244
38317
  if (submitted) return;
38245
- if (toggleFlagEvent) {
38246
- emit(`UI:${toggleFlagEvent}`, { lineId });
38247
- }
38318
+ if (toggleFlagEvent) emit(`UI:${toggleFlagEvent}`, { lineId });
38248
38319
  };
38249
38320
  const handleSubmit = React82.useCallback(() => {
38250
- if (checkEvent) {
38251
- emit(`UI:${checkEvent}`, {});
38252
- }
38321
+ if (checkEvent) emit(`UI:${checkEvent}`, {});
38253
38322
  const correct = correctFlags.length === bugLines.length && falseFlags.length === 0;
38254
- if (correct) {
38255
- emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
38256
- }
38323
+ if (correct) emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
38257
38324
  }, [checkEvent, correctFlags.length, bugLines.length, falseFlags.length, attempts, completeEvent, emit]);
38258
38325
  const handleReset = () => {
38259
- if (playAgainEvent) {
38260
- emit(`UI:${playAgainEvent}`, {});
38261
- }
38262
- if (attempts >= 2 && str(resolved?.hint)) {
38263
- setShowHint(true);
38264
- }
38265
- };
38266
- const handleFullReset = () => {
38267
- if (playAgainEvent) {
38268
- emit(`UI:${playAgainEvent}`, {});
38269
- }
38270
- setShowHint(false);
38326
+ if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
38271
38327
  };
38272
38328
  if (!resolved) return null;
38273
38329
  const theme = resolved.theme ?? void 0;
@@ -38291,7 +38347,7 @@ function DebuggerBoard({
38291
38347
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", weight: "bold", children: str(resolved.title) })
38292
38348
  ] }),
38293
38349
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: str(resolved.description) }),
38294
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "text-muted-foreground", children: t("debugger.findBugs", { count: String(num(resolved.bugCount)) }) })
38350
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "text-muted-foreground", children: t("debugger.findBugs", { count: String(lines.filter((l) => l.isBug).length) }) })
38295
38351
  ] }) }),
38296
38352
  /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-0 overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx(VStack, { gap: "none", children: lines.map((line, i) => {
38297
38353
  const isFlagged = !!line.isFlagged;
@@ -38341,11 +38397,11 @@ function DebuggerBoard({
38341
38397
  ] }) }),
38342
38398
  showHint && hint && /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: hint }) }),
38343
38399
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", justify: "center", children: [
38344
- !submitted ? /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: flaggedLines.length === 0, children: [
38400
+ !submitted && /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: flaggedLines.length === 0, children: [
38345
38401
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: LucideIcons2.Send, size: "sm" }),
38346
38402
  t("debugger.submit")
38347
- ] }) : !allCorrect ? /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "primary", onClick: handleReset, children: t("debugger.tryAgain") }) : null,
38348
- /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "secondary", onClick: handleFullReset, children: [
38403
+ ] }),
38404
+ /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "secondary", onClick: handleReset, children: [
38349
38405
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: LucideIcons2.RotateCcw, size: "sm" }),
38350
38406
  t("debugger.reset")
38351
38407
  ] })
@@ -39346,39 +39402,40 @@ function EventHandlerBoard({
39346
39402
  stepDurationMs = 800,
39347
39403
  playEvent,
39348
39404
  completeEvent,
39405
+ editRuleEvent,
39406
+ playAgainEvent,
39349
39407
  className
39350
39408
  }) {
39351
39409
  const { emit } = useEventBus();
39352
39410
  const { t } = hooks.useTranslate();
39353
39411
  const resolved = boardEntity(entity);
39354
- const entityObjects = rows(resolved?.objects);
39355
- const [objects, setObjects] = React82.useState(() => [...entityObjects]);
39412
+ const objects = rows(resolved?.objects);
39413
+ const entityResult = str(resolved?.result) || "none";
39414
+ const isSuccess = entityResult === "win";
39415
+ const attempts = typeof resolved?.attempts === "number" ? resolved.attempts : 0;
39356
39416
  const [selectedObjectId, setSelectedObjectId] = React82.useState(
39357
- entityObjects[0] ? objId(entityObjects[0]) : null
39417
+ objects[0] ? objId(objects[0]) : null
39358
39418
  );
39359
39419
  const [headerError, setHeaderError] = React82.useState(false);
39360
- const [playState, setPlayState] = React82.useState("editing");
39420
+ const [isPlaying, setIsPlaying] = React82.useState(false);
39361
39421
  const [eventLog, setEventLog] = React82.useState([]);
39362
- const [attempts, setAttempts] = React82.useState(0);
39363
39422
  const timerRef = React82.useRef(null);
39364
39423
  const logIdCounter = React82.useRef(0);
39365
39424
  React82.useEffect(() => () => {
39366
39425
  if (timerRef.current) clearTimeout(timerRef.current);
39367
39426
  }, []);
39368
- const selectedObject = objects.find((o) => objId(o) === selectedObjectId) || null;
39427
+ const selectedObject = objects.find((o) => objId(o) === selectedObjectId) ?? null;
39369
39428
  const handleRulesChange = React82.useCallback((objectId, rules) => {
39370
- setObjects((prev) => prev.map(
39371
- (o) => objId(o) === objectId ? { ...o, rules } : o
39372
- ));
39373
- }, []);
39429
+ if (editRuleEvent) emit(`UI:${editRuleEvent}`, { objectId, rules });
39430
+ }, [editRuleEvent, emit]);
39374
39431
  const addLogEntry = React82.useCallback((icon, message, status = "done") => {
39375
39432
  const id = `log-${logIdCounter.current++}`;
39376
39433
  setEventLog((prev) => [...prev, { id, timestamp: Date.now(), icon, message, status }]);
39377
39434
  }, []);
39378
39435
  const handlePlay = React82.useCallback(() => {
39379
- if (playState !== "editing") return;
39436
+ if (isPlaying || isSuccess) return;
39380
39437
  if (playEvent) emit(`UI:${playEvent}`, {});
39381
- setPlayState("playing");
39438
+ setIsPlaying(true);
39382
39439
  setEventLog([]);
39383
39440
  const allRules = [];
39384
39441
  objects.forEach((obj) => {
@@ -39394,15 +39451,8 @@ function EventHandlerBoard({
39394
39451
  let goalReached = false;
39395
39452
  const processNext = () => {
39396
39453
  if (eventQueue.length === 0 || stepIdx > 20) {
39397
- if (goalReached) {
39398
- setPlayState("success");
39399
- if (completeEvent) {
39400
- emit(`UI:${completeEvent}`, { success: true });
39401
- }
39402
- } else {
39403
- setAttempts((prev) => prev + 1);
39404
- setPlayState("fail");
39405
- }
39454
+ setIsPlaying(false);
39455
+ if (goalReached && completeEvent) emit(`UI:${completeEvent}`, { success: true });
39406
39456
  return;
39407
39457
  }
39408
39458
  const currentEvent = eventQueue.shift();
@@ -39433,21 +39483,19 @@ function EventHandlerBoard({
39433
39483
  addLogEntry("\u{1F3AC}", t("eventHandler.simulationStarted", { events: triggers.join(", ") }), "active");
39434
39484
  }
39435
39485
  timerRef.current = setTimeout(processNext, stepDurationMs);
39436
- }, [playState, objects, resolved, stepDurationMs, playEvent, completeEvent, emit, addLogEntry, t]);
39486
+ }, [isPlaying, isSuccess, objects, resolved, stepDurationMs, playEvent, completeEvent, emit, addLogEntry, t]);
39437
39487
  const handleTryAgain = React82.useCallback(() => {
39438
39488
  if (timerRef.current) clearTimeout(timerRef.current);
39439
- setPlayState("editing");
39489
+ setIsPlaying(false);
39440
39490
  setEventLog([]);
39441
39491
  }, []);
39442
39492
  const handleReset = React82.useCallback(() => {
39443
39493
  if (timerRef.current) clearTimeout(timerRef.current);
39444
- const resetObjects = rows(resolved?.objects);
39445
- setObjects([...resetObjects]);
39446
- setPlayState("editing");
39494
+ setIsPlaying(false);
39447
39495
  setEventLog([]);
39448
- setSelectedObjectId(resetObjects[0] ? objId(resetObjects[0]) : null);
39449
- setAttempts(0);
39450
- }, [resolved?.objects]);
39496
+ setSelectedObjectId(objects[0] ? objId(objects[0]) : null);
39497
+ if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
39498
+ }, [objects, playAgainEvent, emit]);
39451
39499
  if (!resolved) return null;
39452
39500
  const objectViewers = objects.map((obj) => {
39453
39501
  const states = objStates(obj);
@@ -39516,12 +39564,12 @@ function EventHandlerBoard({
39516
39564
  {
39517
39565
  object: selectedObject,
39518
39566
  onRulesChange: handleRulesChange,
39519
- disabled: playState !== "editing"
39567
+ disabled: isPlaying
39520
39568
  }
39521
39569
  ),
39522
39570
  eventLog.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(EventLog, { entries: eventLog }),
39523
- 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") }) }),
39524
- playState === "fail" && /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
39571
+ 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") }) }),
39572
+ !isPlaying && !isSuccess && attempts > 0 && /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
39525
39573
  /* @__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) }) }),
39526
39574
  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: [
39527
39575
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-accent font-bold shrink-0", children: "\u{1F4A1} " + t("game.hint") + ":" }),
@@ -39529,12 +39577,12 @@ function EventHandlerBoard({
39529
39577
  ] }) })
39530
39578
  ] }),
39531
39579
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", children: [
39532
- playState === "fail" ? /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "primary", onClick: handleTryAgain, children: "\u{1F504} " + t("puzzle.tryAgainButton") }) : /* @__PURE__ */ jsxRuntime.jsx(
39580
+ !isPlaying && !isSuccess && attempts > 0 ? /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "primary", onClick: handleTryAgain, children: "\u{1F504} " + t("puzzle.tryAgainButton") }) : /* @__PURE__ */ jsxRuntime.jsx(
39533
39581
  Button,
39534
39582
  {
39535
39583
  variant: "primary",
39536
39584
  onClick: handlePlay,
39537
- disabled: playState !== "editing",
39585
+ disabled: isPlaying || isSuccess,
39538
39586
  children: "\u25B6 " + t("game.play")
39539
39587
  }
39540
39588
  ),
@@ -42087,21 +42135,6 @@ var init_ModalSlot = __esm({
42087
42135
  ModalSlot.displayName = "ModalSlot";
42088
42136
  }
42089
42137
  });
42090
- function getOpponentAction(strategy, actions, history) {
42091
- const actionIds = actions.map((a) => a.id);
42092
- switch (strategy) {
42093
- case "always-cooperate":
42094
- return actionIds[0];
42095
- case "always-defect":
42096
- return actionIds[actionIds.length - 1];
42097
- case "tit-for-tat":
42098
- if (history.length === 0) return actionIds[0];
42099
- return history[history.length - 1].playerAction;
42100
- case "random":
42101
- default:
42102
- return actionIds[Math.floor(Math.random() * actionIds.length)];
42103
- }
42104
- }
42105
42138
  function NegotiatorBoard({
42106
42139
  entity,
42107
42140
  completeEvent = "PUZZLE_COMPLETE",
@@ -42123,28 +42156,35 @@ function NegotiatorBoard({
42123
42156
  const playerTotal = num(resolved?.score);
42124
42157
  const isComplete = result !== "none" || totalRounds > 0 && currentRound >= totalRounds;
42125
42158
  const won = result === "win";
42126
- const opponentTotal = history.reduce((s, r) => s + r.opponentPayoff, 0);
42159
+ const lastPlayerAction = str(resolved?.lastPlayerAction);
42160
+ const lastOpponentAction = str(resolved?.lastOpponentAction);
42161
+ const lastPayoff = num(resolved?.lastPayoff);
42127
42162
  const actions = Array.isArray(resolved?.actions) ? resolved.actions : [];
42128
42163
  const payoffMatrix = Array.isArray(resolved?.payoffMatrix) ? resolved.payoffMatrix : [];
42164
+ const prevRoundRef = React82.useRef(currentRound);
42165
+ React82.useEffect(() => {
42166
+ if (currentRound > prevRoundRef.current && lastPlayerAction) {
42167
+ const opponentPayoffEntry = payoffMatrix.find(
42168
+ (p2) => p2.playerAction === lastPlayerAction && p2.opponentAction === lastOpponentAction
42169
+ );
42170
+ setHistory((prev) => [
42171
+ ...prev,
42172
+ {
42173
+ round: currentRound,
42174
+ playerAction: lastPlayerAction,
42175
+ opponentAction: lastOpponentAction,
42176
+ playerPayoff: lastPayoff,
42177
+ opponentPayoff: opponentPayoffEntry?.opponentPayoff ?? 0
42178
+ }
42179
+ ]);
42180
+ }
42181
+ prevRoundRef.current = currentRound;
42182
+ }, [currentRound, lastPlayerAction, lastOpponentAction, lastPayoff, payoffMatrix]);
42183
+ const opponentTotal = React82.useMemo(() => history.reduce((s, r) => s + r.opponentPayoff, 0), [history]);
42129
42184
  const handleAction = React82.useCallback((actionId) => {
42130
42185
  if (isComplete) return;
42131
- const opponentAction = getOpponentAction(str(resolved?.opponentStrategy) || "random", actions, history);
42132
- const payoff = payoffMatrix.find(
42133
- (p2) => p2.playerAction === actionId && p2.opponentAction === opponentAction
42134
- );
42135
- const playerPayoff = payoff?.playerPayoff ?? 0;
42136
- setHistory((prev) => [
42137
- ...prev,
42138
- {
42139
- round: prev.length + 1,
42140
- playerAction: actionId,
42141
- opponentAction,
42142
- playerPayoff,
42143
- opponentPayoff: payoff?.opponentPayoff ?? 0
42144
- }
42145
- ]);
42146
42186
  if (playRoundEvent) {
42147
- emit(`UI:${playRoundEvent}`, { playerAction: actionId, payoff: playerPayoff });
42187
+ emit(`UI:${playRoundEvent}`, { playerAction: actionId, payoff: 0 });
42148
42188
  }
42149
42189
  if (totalRounds > 0 && currentRound + 1 >= totalRounds) {
42150
42190
  if (finishEvent) {
@@ -42154,10 +42194,11 @@ function NegotiatorBoard({
42154
42194
  setShowHint(true);
42155
42195
  }
42156
42196
  }
42157
- }, [isComplete, resolved, totalRounds, currentRound, actions, payoffMatrix, history, playRoundEvent, finishEvent, emit]);
42197
+ }, [isComplete, resolved, totalRounds, currentRound, playRoundEvent, finishEvent, emit]);
42158
42198
  const handleReset = React82.useCallback(() => {
42159
42199
  setHistory([]);
42160
42200
  setShowHint(false);
42201
+ prevRoundRef.current = 0;
42161
42202
  if (playAgainEvent) {
42162
42203
  emit(`UI:${playAgainEvent}`, {});
42163
42204
  }
@@ -44512,17 +44553,24 @@ function SequencerBoard({
44512
44553
  const { emit } = useEventBus();
44513
44554
  const { t } = hooks.useTranslate();
44514
44555
  const resolved = boardEntity(entity);
44515
- const maxSlots = num(resolved?.maxSlots);
44556
+ const maxSlots = num(resolved?.maxSlots) || 3;
44516
44557
  const solutions = Array.isArray(resolved?.solutions) ? resolved.solutions : [];
44517
44558
  const availableActions = Array.isArray(resolved?.availableActions) ? resolved.availableActions : [];
44518
44559
  const allowDuplicates = resolved?.allowDuplicates !== false;
44560
+ const entitySlots = Array.isArray(resolved?.slots) ? resolved.slots : [];
44561
+ const entityResult = str(resolved?.result);
44562
+ const entityAttempts = num(resolved?.attempts);
44563
+ const entityCurrentStep = typeof resolved?.currentStep === "number" ? resolved.currentStep : -1;
44564
+ const slots = Array.from({ length: maxSlots }, (_, i) => {
44565
+ const entitySlot = entitySlots.find((s) => s.index === i);
44566
+ if (!entitySlot?.placedActionId) return void 0;
44567
+ return availableActions.find((a) => a.id === entitySlot.placedActionId);
44568
+ });
44569
+ const isSuccess = entityResult === "win";
44570
+ const attempts = entityAttempts;
44571
+ const isPlayingBack = entityCurrentStep >= 0 && !isSuccess;
44572
+ const currentStep = entityCurrentStep;
44519
44573
  const [headerError, setHeaderError] = React82.useState(false);
44520
- const [slots, setSlots] = React82.useState(
44521
- () => Array.from({ length: maxSlots }, () => void 0)
44522
- );
44523
- const [playState, setPlayState] = React82.useState("idle");
44524
- const [currentStep, setCurrentStep] = React82.useState(-1);
44525
- const [attempts, setAttempts] = React82.useState(0);
44526
44574
  const [slotFeedback, setSlotFeedback] = React82.useState(
44527
44575
  () => Array.from({ length: maxSlots }, () => null)
44528
44576
  );
@@ -44531,11 +44579,6 @@ function SequencerBoard({
44531
44579
  if (timerRef.current) clearTimeout(timerRef.current);
44532
44580
  }, []);
44533
44581
  const handleSlotDrop = React82.useCallback((index, item) => {
44534
- setSlots((prev) => {
44535
- const next = [...prev];
44536
- next[index] = item;
44537
- return next;
44538
- });
44539
44582
  setSlotFeedback((prev) => {
44540
44583
  const next = [...prev];
44541
44584
  next[index] = null;
@@ -44545,11 +44588,6 @@ function SequencerBoard({
44545
44588
  if (placeEvent) emit(`UI:${placeEvent}`, { slotIndex: index, actionId: item.id });
44546
44589
  }, [emit, placeEvent]);
44547
44590
  const handleSlotRemove = React82.useCallback((index) => {
44548
- setSlots((prev) => {
44549
- const next = [...prev];
44550
- next[index] = void 0;
44551
- return next;
44552
- });
44553
44591
  setSlotFeedback((prev) => {
44554
44592
  const next = [...prev];
44555
44593
  next[index] = null;
@@ -44560,48 +44598,34 @@ function SequencerBoard({
44560
44598
  }, [emit, removeEvent]);
44561
44599
  const handleReset = React82.useCallback(() => {
44562
44600
  if (timerRef.current) clearTimeout(timerRef.current);
44563
- setSlots(Array.from({ length: maxSlots }, () => void 0));
44564
- setPlayState("idle");
44565
- setCurrentStep(-1);
44566
- setAttempts(0);
44567
44601
  setSlotFeedback(Array.from({ length: maxSlots }, () => null));
44568
44602
  if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
44569
44603
  }, [maxSlots, playAgainEvent, emit]);
44570
44604
  const filledSlots = slots.filter((s) => !!s);
44571
- const canPlay = filledSlots.length > 0 && playState === "idle";
44605
+ const canPlay = filledSlots.length > 0 && !isPlayingBack && !isSuccess;
44572
44606
  const handlePlay = React82.useCallback(() => {
44573
44607
  if (!canPlay) return;
44574
44608
  setSlotFeedback(Array.from({ length: maxSlots }, () => null));
44575
44609
  emit("UI:PLAY_SOUND", { key: "confirm" });
44576
44610
  const sequence = slots.map((s) => s?.id || "");
44577
- if (playEvent) {
44578
- emit(`UI:${playEvent}`, { sequence });
44579
- }
44580
- setPlayState("playing");
44581
- setCurrentStep(0);
44611
+ const playerIds = slots.filter(Boolean).map((s) => s?.id || "");
44612
+ if (playEvent) emit(`UI:${playEvent}`, { sequence });
44582
44613
  let step = 0;
44583
44614
  const advance = () => {
44584
44615
  step++;
44616
+ emit("UI:STEP", { step });
44585
44617
  if (step >= maxSlots) {
44618
+ if (checkEvent) emit(`UI:${checkEvent}`, { sequence: playerIds });
44586
44619
  const playerSeq = slots.map((s) => s?.id);
44587
- const playerIds = slots.filter(Boolean).map((s) => s?.id || "");
44588
44620
  const success = solutions.some(
44589
44621
  (sol) => sol.length === playerIds.length && sol.every((id, i) => id === playerIds[i])
44590
44622
  );
44591
- if (checkEvent) emit(`UI:${checkEvent}`, { sequence: playerIds });
44592
44623
  if (success) {
44593
- setPlayState("success");
44594
- setCurrentStep(-1);
44595
44624
  emit("UI:PLAY_SOUND", { key: "levelComplete" });
44596
- if (completeEvent) {
44597
- emit(`UI:${completeEvent}`, { success: true, sequence: playerIds });
44598
- }
44625
+ if (completeEvent) emit(`UI:${completeEvent}`, { success: true, sequence: playerIds });
44599
44626
  } else {
44600
- setAttempts((prev) => prev + 1);
44601
44627
  const feedback = computeSlotFeedback(playerSeq, solutions);
44602
44628
  setSlotFeedback(feedback);
44603
- setPlayState("idle");
44604
- setCurrentStep(-1);
44605
44629
  emit("UI:PLAY_SOUND", { key: "fail" });
44606
44630
  const correctCount2 = feedback.filter((f3) => f3 === "correct").length;
44607
44631
  for (let ci = 0; ci < correctCount2; ci++) {
@@ -44611,7 +44635,6 @@ function SequencerBoard({
44611
44635
  }
44612
44636
  }
44613
44637
  } else {
44614
- setCurrentStep(step);
44615
44638
  timerRef.current = setTimeout(advance, stepDurationMs);
44616
44639
  }
44617
44640
  };
@@ -44661,7 +44684,7 @@ function SequencerBoard({
44661
44684
  /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "xs", children: [
44662
44685
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { className: "items-center justify-between", children: [
44663
44686
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("sequencer.yourSequence") + ":" }),
44664
- hasFeedback && playState === "idle" && /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "caption", className: "text-muted-foreground", children: [
44687
+ hasFeedback && !isPlayingBack && !isSuccess && /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "caption", className: "text-muted-foreground", children: [
44665
44688
  `${correctCount}/${maxSlots} `,
44666
44689
  "\u2705"
44667
44690
  ] })
@@ -44673,7 +44696,7 @@ function SequencerBoard({
44673
44696
  maxSlots,
44674
44697
  onSlotDrop: handleSlotDrop,
44675
44698
  onSlotRemove: handleSlotRemove,
44676
- playing: playState === "playing",
44699
+ playing: isPlayingBack,
44677
44700
  currentStep,
44678
44701
  categoryColors,
44679
44702
  slotFeedback,
@@ -44681,7 +44704,7 @@ function SequencerBoard({
44681
44704
  }
44682
44705
  )
44683
44706
  ] }),
44684
- playState !== "playing" && /* @__PURE__ */ jsxRuntime.jsx(
44707
+ !isPlayingBack && /* @__PURE__ */ jsxRuntime.jsx(
44685
44708
  ActionPalette,
44686
44709
  {
44687
44710
  actions: availableActions,
@@ -44691,8 +44714,8 @@ function SequencerBoard({
44691
44714
  label: t("sequencer.dragActions")
44692
44715
  }
44693
44716
  ),
44694
- 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) }) }),
44695
- 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") }) }),
44717
+ 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) }) }),
44718
+ 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") }) }),
44696
44719
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", children: [
44697
44720
  /* @__PURE__ */ jsxRuntime.jsx(
44698
44721
  Button,
@@ -45742,6 +45765,9 @@ function StateArchitectBoard({
45742
45765
  stepDurationMs = 600,
45743
45766
  testEvent,
45744
45767
  completeEvent,
45768
+ addTransitionEvent,
45769
+ removeTransitionEvent,
45770
+ playAgainEvent,
45745
45771
  className
45746
45772
  }) {
45747
45773
  const { emit } = useEventBus();
@@ -45754,14 +45780,16 @@ function StateArchitectBoard({
45754
45780
  const testCases = Array.isArray(resolved?.testCases) ? resolved.testCases : [];
45755
45781
  const entityTransitions = Array.isArray(resolved?.transitions) ? resolved.transitions : [];
45756
45782
  const entityVariables = rows(resolved?.variables);
45757
- const [transitions, setTransitions] = React82.useState(entityTransitions);
45783
+ const transitions = entityTransitions;
45784
+ const attempts = typeof resolved?.attempts === "number" ? resolved.attempts : 0;
45785
+ const entityResult = str(resolved?.result) || "none";
45786
+ const isSuccess = entityResult === "win";
45787
+ const [isTesting, setIsTesting] = React82.useState(false);
45758
45788
  const [headerError, setHeaderError] = React82.useState(false);
45759
- const [playState, setPlayState] = React82.useState("editing");
45760
45789
  const [currentState, setCurrentState] = React82.useState(initialState);
45761
45790
  const [selectedState, setSelectedState] = React82.useState(null);
45762
45791
  const [testResults, setTestResults] = React82.useState([]);
45763
45792
  const [variables, setVariables] = React82.useState(() => [...entityVariables]);
45764
- const [attempts, setAttempts] = React82.useState(0);
45765
45793
  const timerRef = React82.useRef(null);
45766
45794
  const [addingFrom, setAddingFrom] = React82.useState(null);
45767
45795
  React82.useEffect(() => () => {
@@ -45771,7 +45799,7 @@ function StateArchitectBoard({
45771
45799
  const GRAPH_H = 400;
45772
45800
  const positions = React82.useMemo(() => layoutStates(entityStates, GRAPH_W, GRAPH_H), [entityStates]);
45773
45801
  const handleStateClick = React82.useCallback((state) => {
45774
- if (playState !== "editing") return;
45802
+ if (isTesting) return;
45775
45803
  if (addingFrom) {
45776
45804
  if (addingFrom !== state) {
45777
45805
  const event = availableEvents[0] || "EVENT";
@@ -45781,20 +45809,20 @@ function StateArchitectBoard({
45781
45809
  to: state,
45782
45810
  event
45783
45811
  };
45784
- setTransitions((prev) => [...prev, newTrans]);
45812
+ if (addTransitionEvent) emit(`UI:${addTransitionEvent}`, { id: newTrans.id, from: newTrans.from, to: newTrans.to, event: newTrans.event });
45785
45813
  }
45786
45814
  setAddingFrom(null);
45787
45815
  } else {
45788
45816
  setSelectedState(state);
45789
45817
  }
45790
- }, [playState, addingFrom, availableEvents]);
45818
+ }, [isTesting, addingFrom, availableEvents, addTransitionEvent, emit]);
45791
45819
  const handleStartAddTransition = React82.useCallback(() => {
45792
45820
  if (!selectedState) return;
45793
45821
  setAddingFrom(selectedState);
45794
45822
  }, [selectedState]);
45795
45823
  const handleRemoveTransition = React82.useCallback((transId) => {
45796
- setTransitions((prev) => prev.filter((t2) => t2.id !== transId));
45797
- }, []);
45824
+ if (removeTransitionEvent) emit(`UI:${removeTransitionEvent}`, { id: transId });
45825
+ }, [removeTransitionEvent, emit]);
45798
45826
  const machine = React82.useMemo(() => ({
45799
45827
  name: entityName,
45800
45828
  description: str(resolved?.description),
@@ -45808,16 +45836,16 @@ function StateArchitectBoard({
45808
45836
  }))
45809
45837
  }), [entityName, resolved, entityStates, currentState, transitions]);
45810
45838
  const handleTest = React82.useCallback(() => {
45811
- if (playState !== "editing") return;
45839
+ if (isTesting) return;
45812
45840
  if (testEvent) emit(`UI:${testEvent}`, {});
45813
- setPlayState("testing");
45841
+ setIsTesting(true);
45814
45842
  setTestResults([]);
45815
45843
  const results = [];
45816
45844
  let testIdx = 0;
45817
45845
  const runNextTest = () => {
45818
45846
  if (testIdx >= testCases.length) {
45819
45847
  const allPassed = results.every((r) => r.passed);
45820
- setPlayState(allPassed ? "success" : "fail");
45848
+ setIsTesting(false);
45821
45849
  setTestResults(results);
45822
45850
  if (allPassed && completeEvent) {
45823
45851
  emit(`UI:${completeEvent}`, {
@@ -45825,9 +45853,6 @@ function StateArchitectBoard({
45825
45853
  passedTests: results.filter((r) => r.passed).length
45826
45854
  });
45827
45855
  }
45828
- if (!allPassed) {
45829
- setAttempts((prev) => prev + 1);
45830
- }
45831
45856
  return;
45832
45857
  }
45833
45858
  const testCase = testCases[testIdx];
@@ -45850,24 +45875,23 @@ function StateArchitectBoard({
45850
45875
  timerRef.current = setTimeout(runNextTest, stepDurationMs);
45851
45876
  };
45852
45877
  timerRef.current = setTimeout(runNextTest, stepDurationMs);
45853
- }, [playState, transitions, testCases, initialState, stepDurationMs, testEvent, completeEvent, emit]);
45878
+ }, [isTesting, transitions, testCases, initialState, stepDurationMs, testEvent, completeEvent, emit]);
45854
45879
  const handleTryAgain = React82.useCallback(() => {
45855
45880
  if (timerRef.current) clearTimeout(timerRef.current);
45856
- setPlayState("editing");
45881
+ setIsTesting(false);
45857
45882
  setCurrentState(initialState);
45858
45883
  setTestResults([]);
45859
45884
  }, [initialState]);
45860
45885
  const handleReset = React82.useCallback(() => {
45861
45886
  if (timerRef.current) clearTimeout(timerRef.current);
45862
- setTransitions(entityTransitions);
45863
- setPlayState("editing");
45887
+ setIsTesting(false);
45864
45888
  setCurrentState(initialState);
45865
45889
  setTestResults([]);
45866
45890
  setVariables([...entityVariables]);
45867
45891
  setSelectedState(null);
45868
45892
  setAddingFrom(null);
45869
- setAttempts(0);
45870
- }, [entityTransitions, initialState, entityVariables]);
45893
+ if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
45894
+ }, [initialState, entityVariables, playAgainEvent, emit]);
45871
45895
  const codeData = React82.useMemo(() => ({
45872
45896
  name: entityName,
45873
45897
  states: entityStates,
@@ -45963,7 +45987,7 @@ function StateArchitectBoard({
45963
45987
  ]
45964
45988
  }
45965
45989
  ),
45966
- playState === "editing" && /* @__PURE__ */ jsxRuntime.jsx(HStack, { gap: "sm", children: /* @__PURE__ */ jsxRuntime.jsx(
45990
+ !isTesting && /* @__PURE__ */ jsxRuntime.jsx(HStack, { gap: "sm", children: /* @__PURE__ */ jsxRuntime.jsx(
45967
45991
  Button,
45968
45992
  {
45969
45993
  variant: "ghost",
@@ -45985,7 +46009,7 @@ function StateArchitectBoard({
45985
46009
  t2.guardHint,
45986
46010
  ")"
45987
46011
  ] }),
45988
- playState === "editing" && /* @__PURE__ */ jsxRuntime.jsx(
46012
+ !isTesting && /* @__PURE__ */ jsxRuntime.jsx(
45989
46013
  Button,
45990
46014
  {
45991
46015
  variant: "ghost",
@@ -46017,21 +46041,21 @@ function StateArchitectBoard({
46017
46041
  resolved.showCodeView !== false && /* @__PURE__ */ jsxRuntime.jsx(StateJsonView, { data: codeData, label: "View Code" })
46018
46042
  ] })
46019
46043
  ] }),
46020
- 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") }) }),
46021
- playState === "fail" && /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
46044
+ 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") }) }),
46045
+ !isTesting && !isSuccess && testResults.some((r) => !r.passed) && /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
46022
46046
  /* @__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]) }) }),
46023
- 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: [
46047
+ !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: [
46024
46048
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-accent font-bold shrink-0", children: "\u{1F4A1} " + t("game.hint") + ":" }),
46025
46049
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-foreground", children: hint })
46026
46050
  ] }) })
46027
46051
  ] }),
46028
46052
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", children: [
46029
- playState === "fail" ? /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "primary", onClick: handleTryAgain, children: "\u{1F504} " + t("puzzle.tryAgainButton") }) : /* @__PURE__ */ jsxRuntime.jsx(
46053
+ !isTesting && !isSuccess && testResults.some((r) => !r.passed) ? /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "primary", onClick: handleTryAgain, children: "\u{1F504} " + t("puzzle.tryAgainButton") }) : /* @__PURE__ */ jsxRuntime.jsx(
46030
46054
  Button,
46031
46055
  {
46032
46056
  variant: "primary",
46033
46057
  onClick: handleTest,
46034
- disabled: playState !== "editing",
46058
+ disabled: isTesting,
46035
46059
  children: "\u25B6 " + t("game.runTests")
46036
46060
  }
46037
46061
  ),
@@ -46735,17 +46759,12 @@ var init_UncontrolledBattleBoard = __esm({
46735
46759
  }
46736
46760
  });
46737
46761
  function heroPosition(h) {
46762
+ if ("position" in h && h.position != null) return h.position;
46738
46763
  return vec2(h.position);
46739
46764
  }
46740
- function heroOwner(h) {
46741
- return str(h.owner);
46742
- }
46743
46765
  function heroMovement(h) {
46744
46766
  return num(h.movement);
46745
46767
  }
46746
- function hexPassable(h) {
46747
- return h.passable !== false;
46748
- }
46749
46768
  function defaultIsInRange(from, to, range) {
46750
46769
  return Math.abs(from.x - to.x) + Math.abs(from.y - to.y) <= range;
46751
46770
  }
@@ -46781,38 +46800,41 @@ function WorldMapBoard({
46781
46800
  }) {
46782
46801
  const eventBus = useEventBus();
46783
46802
  const resolved = boardEntity(entity);
46784
- const hexes = rows(resolved?.hexes);
46785
- const heroes = rows(resolved?.heroes);
46803
+ const entityUnits = rows(resolved?.units);
46804
+ const entityTiles = rows(resolved?.tiles);
46786
46805
  const features = propFeatures ?? (Array.isArray(resolved?.features) ? resolved.features : []);
46787
46806
  const selectedHeroId = resolved?.selectedHeroId ?? null;
46788
46807
  const assetManifest = propAssetManifest ?? resolved?.assetManifest;
46789
46808
  const backgroundImage = resolved?.backgroundImage;
46790
46809
  const [hoveredTile, setHoveredTile] = React82.useState(null);
46791
- const selectedHero = React82.useMemo(
46792
- () => heroes.find((h) => str(h.id) === selectedHeroId) ?? null,
46793
- [heroes, selectedHeroId]
46794
- );
46795
46810
  const derivedTiles = React82.useMemo(
46796
- () => hexes.map((hex) => ({
46797
- x: num(hex.x),
46798
- y: num(hex.y),
46799
- terrain: str(hex.terrain),
46800
- terrainSprite: hex.terrainSprite == null ? void 0 : str(hex.terrainSprite)
46811
+ () => entityTiles.map((t) => ({
46812
+ x: num(t.x),
46813
+ y: num(t.y),
46814
+ terrain: str(t.terrain),
46815
+ terrainSprite: t.terrainSprite == null ? void 0 : str(t.terrainSprite),
46816
+ passable: t.passable !== false
46801
46817
  })),
46802
- [hexes]
46818
+ [entityTiles]
46803
46819
  );
46804
46820
  const tiles = propTiles ?? derivedTiles;
46805
46821
  const baseUnits = React82.useMemo(
46806
- () => propUnits ?? heroes.map((hero) => ({
46807
- id: str(hero.id),
46808
- position: heroPosition(hero),
46809
- name: str(hero.name),
46810
- team: heroOwner(hero) === "enemy" ? "enemy" : "player",
46811
- health: 100,
46812
- maxHealth: 100,
46813
- sprite: hero.sprite == null ? void 0 : str(hero.sprite)
46822
+ () => propUnits ?? entityUnits.map((u) => ({
46823
+ id: str(u.id),
46824
+ position: heroPosition(u),
46825
+ name: str(u.name),
46826
+ // lolo uses `team` field (not `owner`)
46827
+ team: str(u.team) === "enemy" ? "enemy" : "player",
46828
+ health: num(u.health) || 100,
46829
+ maxHealth: num(u.maxHealth) || 100,
46830
+ sprite: u.sprite == null ? void 0 : str(u.sprite)
46814
46831
  })),
46815
- [heroes, propUnits]
46832
+ [entityUnits, propUnits]
46833
+ );
46834
+ const gameUnits = baseUnits;
46835
+ const selectedHero = React82.useMemo(
46836
+ () => gameUnits.find((u) => u.id === selectedHeroId) ?? null,
46837
+ [gameUnits, selectedHeroId]
46816
46838
  );
46817
46839
  const MOVE_SPEED_MS_PER_TILE = 300;
46818
46840
  const movementAnimRef = React82.useRef(null);
@@ -46858,51 +46880,50 @@ function WorldMapBoard({
46858
46880
  const validMoves = React82.useMemo(() => {
46859
46881
  if (!selectedHero || heroMovement(selectedHero) <= 0) return [];
46860
46882
  const sp = heroPosition(selectedHero);
46861
- const sOwner = heroOwner(selectedHero);
46883
+ const sTeam = str(selectedHero.team);
46862
46884
  const range = heroMovement(selectedHero);
46863
46885
  const moves = [];
46864
- hexes.forEach((hex) => {
46865
- const hx = num(hex.x);
46866
- const hy = num(hex.y);
46867
- if (!hexPassable(hex)) return;
46868
- if (hx === sp.x && hy === sp.y) return;
46869
- if (!isInRange(sp, { x: hx, y: hy }, range)) return;
46870
- if (heroes.some((h) => {
46871
- const hp = heroPosition(h);
46872
- return hp.x === hx && hp.y === hy && heroOwner(h) === sOwner;
46886
+ tiles.forEach((t) => {
46887
+ const tx = t.x;
46888
+ const ty = t.y;
46889
+ if (t.passable === false) return;
46890
+ if (tx === sp.x && ty === sp.y) return;
46891
+ if (!isInRange(sp, { x: tx, y: ty }, range)) return;
46892
+ if (gameUnits.some((u) => {
46893
+ const up = u.position ?? { x: u.x ?? -1, y: u.y ?? -1 };
46894
+ return up.x === tx && up.y === ty && str(u.team) === sTeam;
46873
46895
  })) return;
46874
- moves.push({ x: hx, y: hy });
46896
+ moves.push({ x: tx, y: ty });
46875
46897
  });
46876
46898
  return moves;
46877
- }, [selectedHero, hexes, heroes, isInRange]);
46899
+ }, [selectedHero, tiles, gameUnits, isInRange]);
46878
46900
  const attackTargets = React82.useMemo(() => {
46879
46901
  if (!selectedHero || heroMovement(selectedHero) <= 0) return [];
46880
46902
  const sp = heroPosition(selectedHero);
46881
- const sOwner = heroOwner(selectedHero);
46903
+ const sTeam = str(selectedHero.team);
46882
46904
  const range = heroMovement(selectedHero);
46883
- return heroes.filter((h) => heroOwner(h) !== sOwner).filter((h) => isInRange(sp, heroPosition(h), range)).map((h) => heroPosition(h));
46884
- }, [selectedHero, heroes, isInRange]);
46885
- const maxY = Math.max(...hexes.map((h) => num(h.y)), 0);
46905
+ 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 });
46906
+ }, [selectedHero, gameUnits, isInRange]);
46907
+ const maxY = Math.max(...tiles.map((t) => t.y), 0);
46886
46908
  const baseOffsetX = (maxY + 1) * (TILE_WIDTH * scale / 2);
46887
46909
  const tileToScreen = React82.useCallback(
46888
46910
  (tx, ty) => isoToScreen(tx, ty, scale, baseOffsetX),
46889
46911
  [scale, baseOffsetX]
46890
46912
  );
46891
46913
  const hoveredHex = React82.useMemo(
46892
- () => hoveredTile ? hexes.find((h) => num(h.x) === hoveredTile.x && num(h.y) === hoveredTile.y) ?? null : null,
46893
- [hoveredTile, hexes]
46914
+ () => hoveredTile ? tiles.find((t) => t.x === hoveredTile.x && t.y === hoveredTile.y) ?? null : null,
46915
+ [hoveredTile, tiles]
46894
46916
  );
46895
46917
  const hoveredHero = React82.useMemo(
46896
- () => hoveredTile ? heroes.find((h) => {
46897
- const hp = heroPosition(h);
46898
- return hp.x === hoveredTile.x && hp.y === hoveredTile.y;
46918
+ () => hoveredTile ? gameUnits.find((u) => {
46919
+ const up = u.position ?? { x: u.x ?? -1, y: u.y ?? -1 };
46920
+ return up.x === hoveredTile.x && up.y === hoveredTile.y;
46899
46921
  }) ?? null : null,
46900
- [hoveredTile, heroes]
46922
+ [hoveredTile, gameUnits]
46901
46923
  );
46902
46924
  const handleTileClick = React82.useCallback((x, y) => {
46903
46925
  if (movementAnimRef.current) return;
46904
- const hex = hexes.find((h) => num(h.x) === x && num(h.y) === y);
46905
- if (!hex) return;
46926
+ const tile = tiles.find((t) => t.x === x && t.y === y);
46906
46927
  if (tileClickEvent) {
46907
46928
  eventBus.emit(`UI:${tileClickEvent}`, { x, y });
46908
46929
  }
@@ -46913,38 +46934,39 @@ function WorldMapBoard({
46913
46934
  if (heroMoveEvent) {
46914
46935
  eventBus.emit(`UI:${heroMoveEvent}`, { heroId, toX: x, toY: y });
46915
46936
  }
46916
- const feature = str(hex.feature);
46937
+ const feature = tile ? str(tile.feature) : "";
46917
46938
  if (feature && feature !== "none") {
46918
- onFeatureEnter?.(heroId, hex);
46939
+ const tileRow = tile;
46940
+ onFeatureEnter?.(heroId, tileRow);
46919
46941
  if (featureEnterEvent) {
46920
- eventBus.emit(`UI:${featureEnterEvent}`, { heroId, feature, hex });
46942
+ eventBus.emit(`UI:${featureEnterEvent}`, { heroId, feature, hex: tileRow });
46921
46943
  }
46922
46944
  }
46923
46945
  });
46924
46946
  return;
46925
46947
  }
46926
- const enemy = heroes.find((h) => {
46927
- const hp = heroPosition(h);
46928
- return hp.x === x && hp.y === y && heroOwner(h) === "enemy";
46948
+ const enemy = gameUnits.find((u) => {
46949
+ const up = u.position ?? { x: u.x ?? -1, y: u.y ?? -1 };
46950
+ return up.x === x && up.y === y && str(u.team) === "enemy";
46929
46951
  });
46930
46952
  if (selectedHero && enemy && attackTargets.some((t) => t.x === x && t.y === y)) {
46931
46953
  const attackerId = str(selectedHero.id);
46932
- const defenderId = str(enemy.id);
46954
+ const defenderId = enemy.id;
46933
46955
  onBattleEncounter?.(attackerId, defenderId);
46934
46956
  if (battleEncounterEvent) {
46935
46957
  eventBus.emit(`UI:${battleEncounterEvent}`, { attackerId, defenderId });
46936
46958
  }
46937
46959
  }
46938
- }, [hexes, heroes, selectedHero, validMoves, attackTargets, startMoveAnimation, onHeroMove, onFeatureEnter, onBattleEncounter, eventBus, tileClickEvent, heroMoveEvent, featureEnterEvent, battleEncounterEvent]);
46960
+ }, [tiles, gameUnits, selectedHero, validMoves, attackTargets, startMoveAnimation, onHeroMove, onFeatureEnter, onBattleEncounter, eventBus, tileClickEvent, heroMoveEvent, featureEnterEvent, battleEncounterEvent]);
46939
46961
  const handleUnitClick = React82.useCallback((unitId) => {
46940
- const hero = heroes.find((h) => str(h.id) === unitId);
46941
- if (hero && (heroOwner(hero) === "player" || allowMoveAllHeroes)) {
46962
+ const unit = gameUnits.find((u) => u.id === unitId);
46963
+ if (unit && (str(unit.team) === "player" || allowMoveAllHeroes)) {
46942
46964
  onHeroSelect?.(unitId);
46943
46965
  if (heroSelectEvent) {
46944
46966
  eventBus.emit(`UI:${heroSelectEvent}`, { heroId: unitId });
46945
46967
  }
46946
46968
  }
46947
- }, [heroes, onHeroSelect, allowMoveAllHeroes, eventBus, heroSelectEvent]);
46969
+ }, [gameUnits, onHeroSelect, allowMoveAllHeroes, eventBus, heroSelectEvent]);
46948
46970
  const selectHero = React82.useCallback((id) => {
46949
46971
  onHeroSelect?.(id);
46950
46972
  if (heroSelectEvent) {