@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.
@@ -11063,6 +11063,7 @@ var init_Sprite = __esm({
11063
11063
  }
11064
11064
  });
11065
11065
  function StateIndicator({
11066
+ assetUrl = DEFAULT_ASSET_URL,
11066
11067
  state = "idle",
11067
11068
  label,
11068
11069
  size = "md",
@@ -11085,18 +11086,29 @@ function StateIndicator({
11085
11086
  className
11086
11087
  ),
11087
11088
  children: [
11088
- /* @__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 }) }),
11089
+ /* @__PURE__ */ jsxRuntime.jsx(Box, { as: "span", children: assetUrl ? /* @__PURE__ */ jsxRuntime.jsx(
11090
+ "img",
11091
+ {
11092
+ src: assetUrl,
11093
+ alt: displayLabel,
11094
+ width: 16,
11095
+ height: 16,
11096
+ style: { imageRendering: "pixelated", objectFit: "contain" },
11097
+ className: "flex-shrink-0"
11098
+ }
11099
+ ) : 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 }) }),
11089
11100
  /* @__PURE__ */ jsxRuntime.jsx(Box, { as: "span", children: displayLabel })
11090
11101
  ]
11091
11102
  }
11092
11103
  );
11093
11104
  }
11094
- var DEFAULT_STATE_STYLES, DEFAULT_STYLE, SIZE_CLASSES;
11105
+ var DEFAULT_ASSET_URL, DEFAULT_STATE_STYLES, DEFAULT_STYLE, SIZE_CLASSES;
11095
11106
  var init_StateIndicator = __esm({
11096
11107
  "components/game/atoms/StateIndicator.tsx"() {
11097
11108
  init_Box();
11098
11109
  init_Icon();
11099
11110
  init_cn();
11111
+ DEFAULT_ASSET_URL = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
11100
11112
  DEFAULT_STATE_STYLES = {
11101
11113
  idle: { icon: "\u23F8", bgClass: "bg-muted" },
11102
11114
  active: { icon: "\u25B6", bgClass: "bg-success" },
@@ -11171,6 +11183,7 @@ var init_TimerDisplay = __esm({
11171
11183
  }
11172
11184
  });
11173
11185
  function ResourceCounter({
11186
+ assetUrl = DEFAULT_ASSET_URL2,
11174
11187
  icon,
11175
11188
  label = "Gold",
11176
11189
  value = 250,
@@ -11190,7 +11203,17 @@ function ResourceCounter({
11190
11203
  className
11191
11204
  ),
11192
11205
  children: [
11193
- 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 }) }),
11206
+ assetUrl ? /* @__PURE__ */ jsxRuntime.jsx(
11207
+ "img",
11208
+ {
11209
+ src: assetUrl,
11210
+ alt: label,
11211
+ width: sizes.img,
11212
+ height: sizes.img,
11213
+ style: { imageRendering: "pixelated", objectFit: "contain" },
11214
+ className: "flex-shrink-0"
11215
+ }
11216
+ ) : 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,
11194
11217
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: label }),
11195
11218
  /* @__PURE__ */ jsxRuntime.jsxs("span", { className: cn("font-bold tabular-nums", color && (color in colorTokenClasses2 ? colorTokenClasses2[color] : color)), children: [
11196
11219
  value,
@@ -11203,7 +11226,7 @@ function ResourceCounter({
11203
11226
  }
11204
11227
  );
11205
11228
  }
11206
- var colorTokenClasses2, sizeMap5;
11229
+ var colorTokenClasses2, DEFAULT_ASSET_URL2, sizeMap5;
11207
11230
  var init_ResourceCounter = __esm({
11208
11231
  "components/game/atoms/ResourceCounter.tsx"() {
11209
11232
  init_cn();
@@ -11216,16 +11239,17 @@ var init_ResourceCounter = __esm({
11216
11239
  error: "text-error",
11217
11240
  muted: "text-muted-foreground"
11218
11241
  };
11242
+ DEFAULT_ASSET_URL2 = "https://almadar-kflow-assets.web.app/shared/world-map/gold_mine.png";
11219
11243
  sizeMap5 = {
11220
- sm: { wrapper: "text-xs gap-1 px-1.5 py-0.5", icon: "text-sm" },
11221
- md: { wrapper: "text-sm gap-1.5 px-2 py-1", icon: "text-base" },
11222
- lg: { wrapper: "text-base gap-2 px-3 py-1.5", icon: "text-lg" }
11244
+ sm: { wrapper: "text-xs gap-1 px-1.5 py-0.5", icon: "text-sm", img: 16 },
11245
+ md: { wrapper: "text-sm gap-1.5 px-2 py-1", icon: "text-base", img: 20 },
11246
+ lg: { wrapper: "text-base gap-2 px-3 py-1.5", icon: "text-lg", img: 28 }
11223
11247
  };
11224
11248
  ResourceCounter.displayName = "ResourceCounter";
11225
11249
  }
11226
11250
  });
11227
11251
  function ItemSlot({
11228
- assetUrl = DEFAULT_ASSET_URL,
11252
+ assetUrl = DEFAULT_ASSET_URL3,
11229
11253
  icon = "sword",
11230
11254
  label = "Iron Sword",
11231
11255
  quantity,
@@ -11284,7 +11308,7 @@ function ItemSlot({
11284
11308
  }
11285
11309
  );
11286
11310
  }
11287
- var sizeMap6, rarityBorderMap, rarityGlowMap, DEFAULT_ASSET_URL, assetSizeMap;
11311
+ var sizeMap6, rarityBorderMap, rarityGlowMap, DEFAULT_ASSET_URL3, assetSizeMap;
11288
11312
  var init_ItemSlot = __esm({
11289
11313
  "components/game/atoms/ItemSlot.tsx"() {
11290
11314
  "use client";
@@ -11309,7 +11333,7 @@ var init_ItemSlot = __esm({
11309
11333
  epic: "shadow-lg",
11310
11334
  legendary: "shadow-lg"
11311
11335
  };
11312
- DEFAULT_ASSET_URL = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
11336
+ DEFAULT_ASSET_URL3 = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
11313
11337
  assetSizeMap = {
11314
11338
  sm: 28,
11315
11339
  md: 40,
@@ -11382,6 +11406,7 @@ function getComboScale(combo) {
11382
11406
  return "";
11383
11407
  }
11384
11408
  function ComboCounter({
11409
+ assetUrl = DEFAULT_ASSET_URL4,
11385
11410
  combo = 5,
11386
11411
  multiplier,
11387
11412
  streak,
@@ -11401,6 +11426,17 @@ function ComboCounter({
11401
11426
  className
11402
11427
  ),
11403
11428
  children: [
11429
+ assetUrl && /* @__PURE__ */ jsxRuntime.jsx(
11430
+ "img",
11431
+ {
11432
+ src: assetUrl,
11433
+ alt: "combo",
11434
+ width: 24,
11435
+ height: 24,
11436
+ style: { imageRendering: "pixelated", objectFit: "contain" },
11437
+ className: "flex-shrink-0 mb-0.5"
11438
+ }
11439
+ ),
11404
11440
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("font-black tabular-nums leading-none", sizes.combo, getComboIntensity(combo)), children: combo }),
11405
11441
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("font-bold uppercase tracking-wider text-muted-foreground", sizes.label), children: "combo" }),
11406
11442
  multiplier != null && multiplier > 1 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: cn("font-bold text-warning tabular-nums", sizes.multiplier), children: [
@@ -11415,10 +11451,11 @@ function ComboCounter({
11415
11451
  }
11416
11452
  );
11417
11453
  }
11418
- var sizeMap8;
11454
+ var DEFAULT_ASSET_URL4, sizeMap8;
11419
11455
  var init_ComboCounter = __esm({
11420
11456
  "components/game/atoms/ComboCounter.tsx"() {
11421
11457
  init_cn();
11458
+ DEFAULT_ASSET_URL4 = "https://almadar-kflow-assets.web.app/shared/effects/flash/flash00.png";
11422
11459
  sizeMap8 = {
11423
11460
  sm: { combo: "text-lg", label: "text-xs", multiplier: "text-xs" },
11424
11461
  md: { combo: "text-2xl", label: "text-xs", multiplier: "text-sm" },
@@ -11512,6 +11549,7 @@ var init_XPBar = __esm({
11512
11549
  }
11513
11550
  });
11514
11551
  function WaypointMarker({
11552
+ assetUrl = DEFAULT_ASSET_URL5,
11515
11553
  label,
11516
11554
  icon,
11517
11555
  active = true,
@@ -11550,7 +11588,17 @@ function WaypointMarker({
11550
11588
  active && !completed && "bg-info text-foreground",
11551
11589
  !active && !completed && "bg-muted"
11552
11590
  ),
11553
- children: completed ? checkIcon : icon && (typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: icon }) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon }))
11591
+ children: completed ? checkIcon : assetUrl ? /* @__PURE__ */ jsxRuntime.jsx(
11592
+ "img",
11593
+ {
11594
+ src: assetUrl,
11595
+ alt: label,
11596
+ width: sizes.img,
11597
+ height: sizes.img,
11598
+ style: { imageRendering: "pixelated", objectFit: "contain" },
11599
+ className: "flex-shrink-0"
11600
+ }
11601
+ ) : icon ? typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: icon }) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon }) : null
11554
11602
  }
11555
11603
  )
11556
11604
  ] }),
@@ -11567,15 +11615,16 @@ function WaypointMarker({
11567
11615
  )
11568
11616
  ] });
11569
11617
  }
11570
- var sizeMap10, checkIcon;
11618
+ var DEFAULT_ASSET_URL5, sizeMap10, checkIcon;
11571
11619
  var init_WaypointMarker = __esm({
11572
11620
  "components/game/atoms/WaypointMarker.tsx"() {
11573
11621
  init_cn();
11574
11622
  init_Icon();
11623
+ DEFAULT_ASSET_URL5 = "https://almadar-kflow-assets.web.app/shared/world-map/battle_marker.png";
11575
11624
  sizeMap10 = {
11576
- sm: { dot: "w-4 h-4", ring: "w-6 h-6", label: "text-xs mt-1" },
11577
- md: { dot: "w-6 h-6", ring: "w-8 h-8", label: "text-sm mt-1.5" },
11578
- lg: { dot: "w-8 h-8", ring: "w-10 h-10", label: "text-base mt-2" }
11625
+ sm: { dot: "w-4 h-4", ring: "w-6 h-6", label: "text-xs mt-1", img: 12 },
11626
+ md: { dot: "w-6 h-6", ring: "w-8 h-8", label: "text-sm mt-1.5", img: 18 },
11627
+ lg: { dot: "w-8 h-8", ring: "w-10 h-10", label: "text-base mt-2", img: 24 }
11579
11628
  };
11580
11629
  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" }) });
11581
11630
  WaypointMarker.displayName = "WaypointMarker";
@@ -11588,6 +11637,7 @@ function formatDuration(seconds) {
11588
11637
  return `${Math.round(seconds)}s`;
11589
11638
  }
11590
11639
  function StatusEffect({
11640
+ assetUrl = DEFAULT_ASSET_URL6,
11591
11641
  icon = "shield",
11592
11642
  label = "Shield",
11593
11643
  duration = 30,
@@ -11608,7 +11658,17 @@ function StatusEffect({
11608
11658
  ),
11609
11659
  title: label,
11610
11660
  children: [
11611
- /* @__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" }) }),
11661
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("flex items-center justify-center", sizes.icon), children: assetUrl ? /* @__PURE__ */ jsxRuntime.jsx(
11662
+ "img",
11663
+ {
11664
+ src: assetUrl,
11665
+ alt: label,
11666
+ width: sizes.img,
11667
+ height: sizes.img,
11668
+ style: { imageRendering: "pixelated", objectFit: "contain" },
11669
+ className: "flex-shrink-0"
11670
+ }
11671
+ ) : typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: icon, size: "sm" }) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon, size: "sm" }) }),
11612
11672
  duration !== void 0 && /* @__PURE__ */ jsxRuntime.jsx(
11613
11673
  "span",
11614
11674
  {
@@ -11635,15 +11695,16 @@ function StatusEffect({
11635
11695
  label && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground mt-0.5 text-center whitespace-nowrap", children: label })
11636
11696
  ] });
11637
11697
  }
11638
- var sizeMap11, variantStyles7;
11698
+ var DEFAULT_ASSET_URL6, sizeMap11, variantStyles7;
11639
11699
  var init_StatusEffect = __esm({
11640
11700
  "components/game/atoms/StatusEffect.tsx"() {
11641
11701
  init_cn();
11642
11702
  init_Icon();
11703
+ DEFAULT_ASSET_URL6 = "https://almadar-kflow-assets.web.app/shared/effects/particles/flame_01.png";
11643
11704
  sizeMap11 = {
11644
- sm: { container: "w-8 h-8", icon: "text-sm", badge: "text-xs -top-1 -right-1 w-4 h-4", timer: "text-[9px]" },
11645
- md: { container: "w-10 h-10", icon: "text-base", badge: "text-xs -top-1 -right-1 w-5 h-5", timer: "text-xs" },
11646
- 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" }
11705
+ 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 },
11706
+ 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 },
11707
+ 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 }
11647
11708
  };
11648
11709
  variantStyles7 = {
11649
11710
  buff: "border-success bg-success/20",
@@ -15212,15 +15273,15 @@ function BattleBoard({
15212
15273
  const board = boardEntity(entity) ?? {};
15213
15274
  const tiles = propTiles ?? (Array.isArray(board.tiles) ? board.tiles : []);
15214
15275
  const features = propFeatures ?? (Array.isArray(board.features) ? board.features : []);
15215
- const boardWidth = num(board.boardWidth, 8);
15216
- const boardHeight = num(board.boardHeight, 6);
15276
+ const boardWidth = num(board.gridWidth ?? board.boardWidth, 8);
15277
+ const boardHeight = num(board.gridHeight ?? board.boardHeight, 6);
15217
15278
  const assetManifest = propAssetManifest ?? board.assetManifest;
15218
15279
  const backgroundImage = board.backgroundImage;
15219
15280
  const units = rows(board.units);
15220
15281
  const selectedUnitId = board.selectedUnitId ?? null;
15221
15282
  const currentPhase = str(board.phase) || "observation";
15222
15283
  const currentTurn = num(board.turn, 1);
15223
- const gameResult = board.gameResult ?? null;
15284
+ const gameResult = board.result ?? null;
15224
15285
  const eventBus = useEventBus();
15225
15286
  const { t } = hooks.useTranslate();
15226
15287
  const [hoveredTile, setHoveredTile] = React91.useState(null);
@@ -15241,7 +15302,7 @@ function BattleBoard({
15241
15302
  const validMoves = React91.useMemo(() => {
15242
15303
  if (!selectedUnit || currentPhase !== "movement") return [];
15243
15304
  const moves = [];
15244
- const range = num(selectedUnit.movement);
15305
+ const range = num(board.movementRange, 2);
15245
15306
  const origin = unitPosition(selectedUnit);
15246
15307
  for (let dy = -range; dy <= range; dy++) {
15247
15308
  for (let dx = -range; dx <= range; dx++) {
@@ -22697,15 +22758,24 @@ function CastleBoard({
22697
22758
  featureClickEvent,
22698
22759
  unitClickEvent,
22699
22760
  tileClickEvent,
22761
+ playAgainEvent,
22700
22762
  className
22701
22763
  }) {
22702
22764
  const eventBus = useEventBus();
22765
+ const { t } = hooks.useTranslate();
22703
22766
  const resolved = boardEntity(entity);
22704
22767
  const tiles = propTiles ?? (Array.isArray(resolved?.tiles) ? resolved.tiles : []);
22705
22768
  const features = propFeatures ?? (Array.isArray(resolved?.features) ? resolved.features : []);
22706
22769
  const units = propUnits ?? (Array.isArray(resolved?.units) ? resolved.units : []);
22707
22770
  const assetManifest = propAssetManifest ?? resolved?.assetManifest;
22708
22771
  const backgroundImage = resolved?.backgroundImage;
22772
+ const gold = num(resolved?.gold);
22773
+ const health = num(resolved?.health);
22774
+ const maxHealth = num(resolved?.maxHealth);
22775
+ const wave = num(resolved?.wave);
22776
+ const tickCount = num(resolved?.tickCount);
22777
+ const buildings = rows(resolved?.buildings);
22778
+ const result = str(resolved?.result) || "none";
22709
22779
  const [hoveredTile, setHoveredTile] = React91.useState(null);
22710
22780
  const [selectedFeature, setSelectedFeature] = React91.useState(null);
22711
22781
  const hoveredFeature = React91.useMemo(() => {
@@ -22718,7 +22788,7 @@ function CastleBoard({
22718
22788
  (u) => u.position?.x === hoveredTile.x && u.position?.y === hoveredTile.y
22719
22789
  ) ?? null;
22720
22790
  }, [hoveredTile, units]);
22721
- const maxY = Math.max(...tiles.map((t) => t.y), 0);
22791
+ const maxY = Math.max(...tiles.map((t2) => t2.y), 0);
22722
22792
  const baseOffsetX = (maxY + 1) * (TILE_WIDTH * scale / 2);
22723
22793
  const tileToScreen = React91.useCallback(
22724
22794
  (tx, ty) => isoToScreen(tx, ty, scale, baseOffsetX),
@@ -22753,6 +22823,11 @@ function CastleBoard({
22753
22823
  }
22754
22824
  }, [units, onUnitClick, unitClickEvent, eventBus]);
22755
22825
  const clearSelection = React91.useCallback(() => setSelectedFeature(null), []);
22826
+ const handlePlayAgain = React91.useCallback(() => {
22827
+ if (playAgainEvent) {
22828
+ eventBus.emit(`UI:${playAgainEvent}`, {});
22829
+ }
22830
+ }, [playAgainEvent, eventBus]);
22756
22831
  const ctx = React91.useMemo(
22757
22832
  () => ({
22758
22833
  hoveredTile,
@@ -22761,11 +22836,18 @@ function CastleBoard({
22761
22836
  selectedFeature,
22762
22837
  clearSelection,
22763
22838
  tileToScreen,
22764
- scale
22839
+ scale,
22840
+ gold,
22841
+ health,
22842
+ maxHealth,
22843
+ wave,
22844
+ tickCount,
22845
+ buildings,
22846
+ result
22765
22847
  }),
22766
- [hoveredTile, hoveredFeature, hoveredUnit, selectedFeature, clearSelection, tileToScreen, scale]
22848
+ [hoveredTile, hoveredFeature, hoveredUnit, selectedFeature, clearSelection, tileToScreen, scale, gold, health, maxHealth, wave, tickCount, buildings, result]
22767
22849
  );
22768
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("castle-board min-h-screen flex flex-col bg-background", className), children: [
22850
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("castle-board relative min-h-screen flex flex-col bg-background", className), children: [
22769
22851
  header && header(ctx),
22770
22852
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 overflow-hidden", children: [
22771
22853
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 overflow-auto p-4 relative", children: [
@@ -22790,7 +22872,29 @@ function CastleBoard({
22790
22872
  ] }),
22791
22873
  sidePanel && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-96 shrink-0 border-l border-border bg-surface overflow-y-auto", children: sidePanel(ctx) })
22792
22874
  ] }),
22793
- footer && footer(ctx)
22875
+ footer && footer(ctx),
22876
+ 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: [
22877
+ /* @__PURE__ */ jsxRuntime.jsx(
22878
+ Typography,
22879
+ {
22880
+ variant: "h2",
22881
+ className: cn(
22882
+ "text-4xl font-black tracking-widest uppercase",
22883
+ result === "victory" ? "text-warning" : "text-error"
22884
+ ),
22885
+ children: result === "victory" ? t("battle.victory") : t("battle.defeat")
22886
+ }
22887
+ ),
22888
+ /* @__PURE__ */ jsxRuntime.jsx(
22889
+ Button,
22890
+ {
22891
+ variant: "primary",
22892
+ className: "px-8 py-3 font-semibold",
22893
+ onClick: handlePlayAgain,
22894
+ children: t("battle.playAgain")
22895
+ }
22896
+ )
22897
+ ] }) })
22794
22898
  ] });
22795
22899
  }
22796
22900
  var init_CastleBoard = __esm({
@@ -22798,6 +22902,10 @@ var init_CastleBoard = __esm({
22798
22902
  "use client";
22799
22903
  init_cn();
22800
22904
  init_useEventBus();
22905
+ init_Box();
22906
+ init_Button();
22907
+ init_Typography();
22908
+ init_Stack();
22801
22909
  init_IsometricCanvas();
22802
22910
  init_boardEntity();
22803
22911
  init_isometric();
@@ -23674,22 +23782,16 @@ function ClassifierBoard({
23674
23782
  const { emit } = useEventBus();
23675
23783
  const { t } = hooks.useTranslate();
23676
23784
  const resolved = boardEntity(entity);
23677
- const [localAssignments, setLocalAssignments] = React91.useState({});
23678
23785
  const [headerError, setHeaderError] = React91.useState(false);
23679
- const [localSubmitted, setLocalSubmitted] = React91.useState(false);
23680
- const [localAttempts, setLocalAttempts] = React91.useState(0);
23681
- const [showHint, setShowHint] = React91.useState(false);
23682
23786
  const items = Array.isArray(resolved?.items) ? resolved.items : [];
23683
23787
  const categories = Array.isArray(resolved?.categories) ? resolved.categories : [];
23684
- const entityResult = str(resolved?.result);
23685
- const entityDrivesResult = entityResult.length > 0;
23686
- const entityHasAssignments = items.some((item) => item.assignedCategory != null);
23687
- const assignments = entityHasAssignments ? items.reduce((acc, item) => {
23688
- if (item.assignedCategory != null) acc[item.id] = item.assignedCategory;
23788
+ const result = str(resolved?.result);
23789
+ const submitted = result === "win";
23790
+ const attempts = num(resolved?.attempts);
23791
+ const assignments = items.reduce((acc, item) => {
23792
+ if (item.assignedCategory != null && item.assignedCategory !== "") acc[item.id] = item.assignedCategory;
23689
23793
  return acc;
23690
- }, {}) : localAssignments;
23691
- const attempts = entityDrivesResult ? num(resolved?.attempts) : localAttempts;
23692
- const submitted = entityDrivesResult || localSubmitted;
23794
+ }, {});
23693
23795
  const unassignedItems = items.filter((item) => !assignments[item.id]);
23694
23796
  const allAssigned = items.length > 0 && Object.keys(assignments).length === items.length;
23695
23797
  const results = submitted ? items.map((item) => ({
@@ -23697,54 +23799,25 @@ function ClassifierBoard({
23697
23799
  assigned: assignments[item.id],
23698
23800
  correct: assignments[item.id] === item.correctCategory
23699
23801
  })) : [];
23700
- const allCorrect = entityDrivesResult ? entityResult === "success" : results.length > 0 && results.every((r2) => r2.correct);
23802
+ const allCorrect = result === "win";
23701
23803
  const correctCount = results.filter((r2) => r2.correct).length;
23804
+ const showHint = attempts >= 2 && Boolean(str(resolved?.hint));
23702
23805
  const handleAssign = (itemId, categoryId) => {
23703
23806
  if (submitted) return;
23704
- if (assignEvent) {
23705
- emit(`UI:${assignEvent}`, { itemId, categoryId });
23706
- }
23707
- if (!entityHasAssignments) {
23708
- setLocalAssignments((prev) => ({ ...prev, [itemId]: categoryId }));
23709
- }
23807
+ if (assignEvent) emit(`UI:${assignEvent}`, { itemId, categoryId });
23710
23808
  };
23711
23809
  const handleUnassign = (itemId) => {
23712
23810
  if (submitted) return;
23713
- if (!entityHasAssignments) {
23714
- setLocalAssignments((prev) => {
23715
- const next = { ...prev };
23716
- delete next[itemId];
23717
- return next;
23718
- });
23719
- }
23811
+ if (assignEvent) emit(`UI:${assignEvent}`, { itemId, categoryId: "" });
23720
23812
  };
23721
- const handleSubmit = React91.useCallback(() => {
23722
- if (checkEvent) {
23723
- emit(`UI:${checkEvent}`, {});
23724
- }
23725
- if (!entityDrivesResult) {
23726
- setLocalSubmitted(true);
23727
- setLocalAttempts((a) => a + 1);
23728
- const correct = items.every((item) => assignments[item.id] === item.correctCategory);
23729
- if (correct) {
23730
- emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
23731
- }
23732
- }
23733
- }, [checkEvent, entityDrivesResult, items, assignments, attempts, completeEvent, emit]);
23734
- const handleReset = () => {
23735
- if (!entityDrivesResult) setLocalSubmitted(false);
23736
- if (attempts >= 2 && str(resolved?.hint)) {
23737
- setShowHint(true);
23813
+ const handleSubmit = () => {
23814
+ if (checkEvent) emit(`UI:${checkEvent}`, {});
23815
+ if (allAssigned && items.every((item) => assignments[item.id] === item.correctCategory)) {
23816
+ emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
23738
23817
  }
23739
23818
  };
23740
23819
  const handleFullReset = () => {
23741
- if (playAgainEvent) {
23742
- emit(`UI:${playAgainEvent}`, {});
23743
- }
23744
- setLocalAssignments({});
23745
- setLocalSubmitted(false);
23746
- setLocalAttempts(0);
23747
- setShowHint(false);
23820
+ if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
23748
23821
  };
23749
23822
  if (!resolved) return null;
23750
23823
  const theme = resolved.theme ?? void 0;
@@ -23783,17 +23856,17 @@ function ClassifierBoard({
23783
23856
  /* @__PURE__ */ jsxRuntime.jsx(Badge, { size: "sm", children: catItems.length })
23784
23857
  ] }),
23785
23858
  /* @__PURE__ */ jsxRuntime.jsx(HStack, { gap: "xs", className: "flex-wrap min-h-[2rem]", children: catItems.map((item) => {
23786
- const result = results.find((r2) => r2.item.id === item.id);
23859
+ const result2 = results.find((r2) => r2.item.id === item.id);
23787
23860
  return /* @__PURE__ */ jsxRuntime.jsxs(
23788
23861
  Badge,
23789
23862
  {
23790
23863
  size: "sm",
23791
- className: `cursor-pointer ${result ? result.correct ? "border-success bg-success/10" : "border-error bg-error/10" : ""}`,
23864
+ className: `cursor-pointer ${result2 ? result2.correct ? "border-success bg-success/10" : "border-error bg-error/10" : ""}`,
23792
23865
  onClick: () => handleUnassign(item.id),
23793
23866
  children: [
23794
23867
  item.iconUrl && /* @__PURE__ */ jsxRuntime.jsx("img", { src: item.iconUrl, alt: "", className: "w-3 h-3 object-contain inline-block" }),
23795
23868
  item.label,
23796
- result && /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: result.correct ? LucideIcons2.CheckCircle : LucideIcons2.XCircle, size: "xs", className: result.correct ? "text-success" : "text-error" })
23869
+ result2 && /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: result2.correct ? LucideIcons2.CheckCircle : LucideIcons2.XCircle, size: "xs", className: result2.correct ? "text-success" : "text-error" })
23797
23870
  ]
23798
23871
  },
23799
23872
  item.id
@@ -23822,10 +23895,10 @@ function ClassifierBoard({
23822
23895
  ] }) }),
23823
23896
  showHint && hint && /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: hint }) }),
23824
23897
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", justify: "center", children: [
23825
- !submitted ? /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: !allAssigned, children: [
23898
+ !submitted && /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: !allAssigned, children: [
23826
23899
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: LucideIcons2.Send, size: "sm" }),
23827
23900
  t("classifier.check")
23828
- ] }) : !allCorrect ? /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "primary", onClick: handleReset, children: t("classifier.tryAgain") }) : null,
23901
+ ] }),
23829
23902
  /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "secondary", onClick: handleFullReset, children: [
23830
23903
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: LucideIcons2.RotateCcw, size: "sm" }),
23831
23904
  t("classifier.reset")
@@ -31854,7 +31927,7 @@ var init_MapView = __esm({
31854
31927
  shadowSize: [41, 41]
31855
31928
  });
31856
31929
  L.Marker.prototype.options.icon = defaultIcon;
31857
- const { useEffect: useEffect78, useRef: useRef70, useCallback: useCallback118, useState: useState115 } = React91__namespace.default;
31930
+ const { useEffect: useEffect78, useRef: useRef70, useCallback: useCallback117, useState: useState115 } = React91__namespace.default;
31858
31931
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
31859
31932
  const { useEventBus: useEventBus3 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
31860
31933
  function MapUpdater({ centerLat, centerLng, zoom }) {
@@ -31900,7 +31973,7 @@ var init_MapView = __esm({
31900
31973
  }) {
31901
31974
  const eventBus = useEventBus3();
31902
31975
  const [clickedPosition, setClickedPosition] = useState115(null);
31903
- const handleMapClick = useCallback118((lat, lng) => {
31976
+ const handleMapClick = useCallback117((lat, lng) => {
31904
31977
  if (showClickedPin) {
31905
31978
  setClickedPosition({ lat, lng });
31906
31979
  }
@@ -31909,7 +31982,7 @@ var init_MapView = __esm({
31909
31982
  eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
31910
31983
  }
31911
31984
  }, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
31912
- const handleMarkerClick = useCallback118((marker) => {
31985
+ const handleMarkerClick = useCallback117((marker) => {
31913
31986
  onMarkerClick?.(marker);
31914
31987
  if (markerClickEvent) {
31915
31988
  eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
@@ -40731,44 +40804,27 @@ function DebuggerBoard({
40731
40804
  const { t } = hooks.useTranslate();
40732
40805
  const resolved = boardEntity(entity);
40733
40806
  const [headerError, setHeaderError] = React91.useState(false);
40734
- const [showHint, setShowHint] = React91.useState(false);
40735
40807
  const lines = Array.isArray(resolved?.lines) ? resolved.lines : [];
40736
- const result = resolved?.result ?? null;
40808
+ const result = str(resolved?.result) || "none";
40737
40809
  const attempts = num(resolved?.attempts);
40738
- const submitted = result != null;
40810
+ const submitted = result === "win";
40811
+ const showHint = !submitted && attempts >= 2 && Boolean(str(resolved?.hint));
40739
40812
  const bugLines = lines.filter((l) => l.isBug);
40740
40813
  const flaggedLines = lines.filter((l) => l.isFlagged);
40741
40814
  const correctFlags = lines.filter((l) => l.isBug && l.isFlagged);
40742
40815
  const falseFlags = lines.filter((l) => !l.isBug && l.isFlagged);
40743
- const allCorrect = result === "win";
40816
+ const allCorrect = submitted;
40744
40817
  const toggleLine = (lineId) => {
40745
40818
  if (submitted) return;
40746
- if (toggleFlagEvent) {
40747
- emit(`UI:${toggleFlagEvent}`, { lineId });
40748
- }
40819
+ if (toggleFlagEvent) emit(`UI:${toggleFlagEvent}`, { lineId });
40749
40820
  };
40750
40821
  const handleSubmit = React91.useCallback(() => {
40751
- if (checkEvent) {
40752
- emit(`UI:${checkEvent}`, {});
40753
- }
40822
+ if (checkEvent) emit(`UI:${checkEvent}`, {});
40754
40823
  const correct = correctFlags.length === bugLines.length && falseFlags.length === 0;
40755
- if (correct) {
40756
- emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
40757
- }
40824
+ if (correct) emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
40758
40825
  }, [checkEvent, correctFlags.length, bugLines.length, falseFlags.length, attempts, completeEvent, emit]);
40759
40826
  const handleReset = () => {
40760
- if (playAgainEvent) {
40761
- emit(`UI:${playAgainEvent}`, {});
40762
- }
40763
- if (attempts >= 2 && str(resolved?.hint)) {
40764
- setShowHint(true);
40765
- }
40766
- };
40767
- const handleFullReset = () => {
40768
- if (playAgainEvent) {
40769
- emit(`UI:${playAgainEvent}`, {});
40770
- }
40771
- setShowHint(false);
40827
+ if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
40772
40828
  };
40773
40829
  if (!resolved) return null;
40774
40830
  const theme = resolved.theme ?? void 0;
@@ -40792,7 +40848,7 @@ function DebuggerBoard({
40792
40848
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", weight: "bold", children: str(resolved.title) })
40793
40849
  ] }),
40794
40850
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: str(resolved.description) }),
40795
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "text-muted-foreground", children: t("debugger.findBugs", { count: String(num(resolved.bugCount)) }) })
40851
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "text-muted-foreground", children: t("debugger.findBugs", { count: String(lines.filter((l) => l.isBug).length) }) })
40796
40852
  ] }) }),
40797
40853
  /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-0 overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx(VStack, { gap: "none", children: lines.map((line, i) => {
40798
40854
  const isFlagged = !!line.isFlagged;
@@ -40842,11 +40898,11 @@ function DebuggerBoard({
40842
40898
  ] }) }),
40843
40899
  showHint && hint && /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", children: hint }) }),
40844
40900
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", justify: "center", children: [
40845
- !submitted ? /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: flaggedLines.length === 0, children: [
40901
+ !submitted && /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: flaggedLines.length === 0, children: [
40846
40902
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: LucideIcons2.Send, size: "sm" }),
40847
40903
  t("debugger.submit")
40848
- ] }) : !allCorrect ? /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "primary", onClick: handleReset, children: t("debugger.tryAgain") }) : null,
40849
- /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "secondary", onClick: handleFullReset, children: [
40904
+ ] }),
40905
+ /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "secondary", onClick: handleReset, children: [
40850
40906
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: LucideIcons2.RotateCcw, size: "sm" }),
40851
40907
  t("debugger.reset")
40852
40908
  ] })
@@ -41847,39 +41903,40 @@ function EventHandlerBoard({
41847
41903
  stepDurationMs = 800,
41848
41904
  playEvent,
41849
41905
  completeEvent,
41906
+ editRuleEvent,
41907
+ playAgainEvent,
41850
41908
  className
41851
41909
  }) {
41852
41910
  const { emit } = useEventBus();
41853
41911
  const { t } = hooks.useTranslate();
41854
41912
  const resolved = boardEntity(entity);
41855
- const entityObjects = rows(resolved?.objects);
41856
- const [objects, setObjects] = React91.useState(() => [...entityObjects]);
41913
+ const objects = rows(resolved?.objects);
41914
+ const entityResult = str(resolved?.result) || "none";
41915
+ const isSuccess = entityResult === "win";
41916
+ const attempts = typeof resolved?.attempts === "number" ? resolved.attempts : 0;
41857
41917
  const [selectedObjectId, setSelectedObjectId] = React91.useState(
41858
- entityObjects[0] ? objId(entityObjects[0]) : null
41918
+ objects[0] ? objId(objects[0]) : null
41859
41919
  );
41860
41920
  const [headerError, setHeaderError] = React91.useState(false);
41861
- const [playState, setPlayState] = React91.useState("editing");
41921
+ const [isPlaying, setIsPlaying] = React91.useState(false);
41862
41922
  const [eventLog, setEventLog] = React91.useState([]);
41863
- const [attempts, setAttempts] = React91.useState(0);
41864
41923
  const timerRef = React91.useRef(null);
41865
41924
  const logIdCounter = React91.useRef(0);
41866
41925
  React91.useEffect(() => () => {
41867
41926
  if (timerRef.current) clearTimeout(timerRef.current);
41868
41927
  }, []);
41869
- const selectedObject = objects.find((o) => objId(o) === selectedObjectId) || null;
41928
+ const selectedObject = objects.find((o) => objId(o) === selectedObjectId) ?? null;
41870
41929
  const handleRulesChange = React91.useCallback((objectId, rules) => {
41871
- setObjects((prev) => prev.map(
41872
- (o) => objId(o) === objectId ? { ...o, rules } : o
41873
- ));
41874
- }, []);
41930
+ if (editRuleEvent) emit(`UI:${editRuleEvent}`, { objectId, rules });
41931
+ }, [editRuleEvent, emit]);
41875
41932
  const addLogEntry = React91.useCallback((icon, message, status = "done") => {
41876
41933
  const id = `log-${logIdCounter.current++}`;
41877
41934
  setEventLog((prev) => [...prev, { id, timestamp: Date.now(), icon, message, status }]);
41878
41935
  }, []);
41879
41936
  const handlePlay = React91.useCallback(() => {
41880
- if (playState !== "editing") return;
41937
+ if (isPlaying || isSuccess) return;
41881
41938
  if (playEvent) emit(`UI:${playEvent}`, {});
41882
- setPlayState("playing");
41939
+ setIsPlaying(true);
41883
41940
  setEventLog([]);
41884
41941
  const allRules = [];
41885
41942
  objects.forEach((obj) => {
@@ -41895,15 +41952,8 @@ function EventHandlerBoard({
41895
41952
  let goalReached = false;
41896
41953
  const processNext = () => {
41897
41954
  if (eventQueue.length === 0 || stepIdx > 20) {
41898
- if (goalReached) {
41899
- setPlayState("success");
41900
- if (completeEvent) {
41901
- emit(`UI:${completeEvent}`, { success: true });
41902
- }
41903
- } else {
41904
- setAttempts((prev) => prev + 1);
41905
- setPlayState("fail");
41906
- }
41955
+ setIsPlaying(false);
41956
+ if (goalReached && completeEvent) emit(`UI:${completeEvent}`, { success: true });
41907
41957
  return;
41908
41958
  }
41909
41959
  const currentEvent = eventQueue.shift();
@@ -41934,21 +41984,19 @@ function EventHandlerBoard({
41934
41984
  addLogEntry("\u{1F3AC}", t("eventHandler.simulationStarted", { events: triggers.join(", ") }), "active");
41935
41985
  }
41936
41986
  timerRef.current = setTimeout(processNext, stepDurationMs);
41937
- }, [playState, objects, resolved, stepDurationMs, playEvent, completeEvent, emit, addLogEntry, t]);
41987
+ }, [isPlaying, isSuccess, objects, resolved, stepDurationMs, playEvent, completeEvent, emit, addLogEntry, t]);
41938
41988
  const handleTryAgain = React91.useCallback(() => {
41939
41989
  if (timerRef.current) clearTimeout(timerRef.current);
41940
- setPlayState("editing");
41990
+ setIsPlaying(false);
41941
41991
  setEventLog([]);
41942
41992
  }, []);
41943
41993
  const handleReset = React91.useCallback(() => {
41944
41994
  if (timerRef.current) clearTimeout(timerRef.current);
41945
- const resetObjects = rows(resolved?.objects);
41946
- setObjects([...resetObjects]);
41947
- setPlayState("editing");
41995
+ setIsPlaying(false);
41948
41996
  setEventLog([]);
41949
- setSelectedObjectId(resetObjects[0] ? objId(resetObjects[0]) : null);
41950
- setAttempts(0);
41951
- }, [resolved?.objects]);
41997
+ setSelectedObjectId(objects[0] ? objId(objects[0]) : null);
41998
+ if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
41999
+ }, [objects, playAgainEvent, emit]);
41952
42000
  if (!resolved) return null;
41953
42001
  const objectViewers = objects.map((obj) => {
41954
42002
  const states = objStates(obj);
@@ -42017,12 +42065,12 @@ function EventHandlerBoard({
42017
42065
  {
42018
42066
  object: selectedObject,
42019
42067
  onRulesChange: handleRulesChange,
42020
- disabled: playState !== "editing"
42068
+ disabled: isPlaying
42021
42069
  }
42022
42070
  ),
42023
42071
  eventLog.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(EventLog, { entries: eventLog }),
42024
- 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") }) }),
42025
- playState === "fail" && /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
42072
+ 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") }) }),
42073
+ !isPlaying && !isSuccess && attempts > 0 && /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
42026
42074
  /* @__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) }) }),
42027
42075
  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: [
42028
42076
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-accent font-bold shrink-0", children: "\u{1F4A1} " + t("game.hint") + ":" }),
@@ -42030,12 +42078,12 @@ function EventHandlerBoard({
42030
42078
  ] }) })
42031
42079
  ] }),
42032
42080
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", children: [
42033
- playState === "fail" ? /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "primary", onClick: handleTryAgain, children: "\u{1F504} " + t("puzzle.tryAgainButton") }) : /* @__PURE__ */ jsxRuntime.jsx(
42081
+ !isPlaying && !isSuccess && attempts > 0 ? /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "primary", onClick: handleTryAgain, children: "\u{1F504} " + t("puzzle.tryAgainButton") }) : /* @__PURE__ */ jsxRuntime.jsx(
42034
42082
  Button,
42035
42083
  {
42036
42084
  variant: "primary",
42037
42085
  onClick: handlePlay,
42038
- disabled: playState !== "editing",
42086
+ disabled: isPlaying || isSuccess,
42039
42087
  children: "\u25B6 " + t("game.play")
42040
42088
  }
42041
42089
  ),
@@ -44588,21 +44636,6 @@ var init_ModalSlot = __esm({
44588
44636
  ModalSlot.displayName = "ModalSlot";
44589
44637
  }
44590
44638
  });
44591
- function getOpponentAction(strategy, actions, history) {
44592
- const actionIds = actions.map((a) => a.id);
44593
- switch (strategy) {
44594
- case "always-cooperate":
44595
- return actionIds[0];
44596
- case "always-defect":
44597
- return actionIds[actionIds.length - 1];
44598
- case "tit-for-tat":
44599
- if (history.length === 0) return actionIds[0];
44600
- return history[history.length - 1].playerAction;
44601
- case "random":
44602
- default:
44603
- return actionIds[Math.floor(Math.random() * actionIds.length)];
44604
- }
44605
- }
44606
44639
  function NegotiatorBoard({
44607
44640
  entity,
44608
44641
  completeEvent = "PUZZLE_COMPLETE",
@@ -44624,28 +44657,35 @@ function NegotiatorBoard({
44624
44657
  const playerTotal = num(resolved?.score);
44625
44658
  const isComplete = result !== "none" || totalRounds > 0 && currentRound >= totalRounds;
44626
44659
  const won = result === "win";
44627
- const opponentTotal = history.reduce((s, r2) => s + r2.opponentPayoff, 0);
44660
+ const lastPlayerAction = str(resolved?.lastPlayerAction);
44661
+ const lastOpponentAction = str(resolved?.lastOpponentAction);
44662
+ const lastPayoff = num(resolved?.lastPayoff);
44628
44663
  const actions = Array.isArray(resolved?.actions) ? resolved.actions : [];
44629
44664
  const payoffMatrix = Array.isArray(resolved?.payoffMatrix) ? resolved.payoffMatrix : [];
44665
+ const prevRoundRef = React91.useRef(currentRound);
44666
+ React91.useEffect(() => {
44667
+ if (currentRound > prevRoundRef.current && lastPlayerAction) {
44668
+ const opponentPayoffEntry = payoffMatrix.find(
44669
+ (p2) => p2.playerAction === lastPlayerAction && p2.opponentAction === lastOpponentAction
44670
+ );
44671
+ setHistory((prev) => [
44672
+ ...prev,
44673
+ {
44674
+ round: currentRound,
44675
+ playerAction: lastPlayerAction,
44676
+ opponentAction: lastOpponentAction,
44677
+ playerPayoff: lastPayoff,
44678
+ opponentPayoff: opponentPayoffEntry?.opponentPayoff ?? 0
44679
+ }
44680
+ ]);
44681
+ }
44682
+ prevRoundRef.current = currentRound;
44683
+ }, [currentRound, lastPlayerAction, lastOpponentAction, lastPayoff, payoffMatrix]);
44684
+ const opponentTotal = React91.useMemo(() => history.reduce((s, r2) => s + r2.opponentPayoff, 0), [history]);
44630
44685
  const handleAction = React91.useCallback((actionId) => {
44631
44686
  if (isComplete) return;
44632
- const opponentAction = getOpponentAction(str(resolved?.opponentStrategy) || "random", actions, history);
44633
- const payoff = payoffMatrix.find(
44634
- (p2) => p2.playerAction === actionId && p2.opponentAction === opponentAction
44635
- );
44636
- const playerPayoff = payoff?.playerPayoff ?? 0;
44637
- setHistory((prev) => [
44638
- ...prev,
44639
- {
44640
- round: prev.length + 1,
44641
- playerAction: actionId,
44642
- opponentAction,
44643
- playerPayoff,
44644
- opponentPayoff: payoff?.opponentPayoff ?? 0
44645
- }
44646
- ]);
44647
44687
  if (playRoundEvent) {
44648
- emit(`UI:${playRoundEvent}`, { playerAction: actionId, payoff: playerPayoff });
44688
+ emit(`UI:${playRoundEvent}`, { playerAction: actionId, payoff: 0 });
44649
44689
  }
44650
44690
  if (totalRounds > 0 && currentRound + 1 >= totalRounds) {
44651
44691
  if (finishEvent) {
@@ -44655,10 +44695,11 @@ function NegotiatorBoard({
44655
44695
  setShowHint(true);
44656
44696
  }
44657
44697
  }
44658
- }, [isComplete, resolved, totalRounds, currentRound, actions, payoffMatrix, history, playRoundEvent, finishEvent, emit]);
44698
+ }, [isComplete, resolved, totalRounds, currentRound, playRoundEvent, finishEvent, emit]);
44659
44699
  const handleReset = React91.useCallback(() => {
44660
44700
  setHistory([]);
44661
44701
  setShowHint(false);
44702
+ prevRoundRef.current = 0;
44662
44703
  if (playAgainEvent) {
44663
44704
  emit(`UI:${playAgainEvent}`, {});
44664
44705
  }
@@ -47013,17 +47054,24 @@ function SequencerBoard({
47013
47054
  const { emit } = useEventBus();
47014
47055
  const { t } = hooks.useTranslate();
47015
47056
  const resolved = boardEntity(entity);
47016
- const maxSlots = num(resolved?.maxSlots);
47057
+ const maxSlots = num(resolved?.maxSlots) || 3;
47017
47058
  const solutions = Array.isArray(resolved?.solutions) ? resolved.solutions : [];
47018
47059
  const availableActions = Array.isArray(resolved?.availableActions) ? resolved.availableActions : [];
47019
47060
  const allowDuplicates = resolved?.allowDuplicates !== false;
47061
+ const entitySlots = Array.isArray(resolved?.slots) ? resolved.slots : [];
47062
+ const entityResult = str(resolved?.result);
47063
+ const entityAttempts = num(resolved?.attempts);
47064
+ const entityCurrentStep = typeof resolved?.currentStep === "number" ? resolved.currentStep : -1;
47065
+ const slots = Array.from({ length: maxSlots }, (_, i) => {
47066
+ const entitySlot = entitySlots.find((s) => s.index === i);
47067
+ if (!entitySlot?.placedActionId) return void 0;
47068
+ return availableActions.find((a) => a.id === entitySlot.placedActionId);
47069
+ });
47070
+ const isSuccess = entityResult === "win";
47071
+ const attempts = entityAttempts;
47072
+ const isPlayingBack = entityCurrentStep >= 0 && !isSuccess;
47073
+ const currentStep = entityCurrentStep;
47020
47074
  const [headerError, setHeaderError] = React91.useState(false);
47021
- const [slots, setSlots] = React91.useState(
47022
- () => Array.from({ length: maxSlots }, () => void 0)
47023
- );
47024
- const [playState, setPlayState] = React91.useState("idle");
47025
- const [currentStep, setCurrentStep] = React91.useState(-1);
47026
- const [attempts, setAttempts] = React91.useState(0);
47027
47075
  const [slotFeedback, setSlotFeedback] = React91.useState(
47028
47076
  () => Array.from({ length: maxSlots }, () => null)
47029
47077
  );
@@ -47032,11 +47080,6 @@ function SequencerBoard({
47032
47080
  if (timerRef.current) clearTimeout(timerRef.current);
47033
47081
  }, []);
47034
47082
  const handleSlotDrop = React91.useCallback((index, item) => {
47035
- setSlots((prev) => {
47036
- const next = [...prev];
47037
- next[index] = item;
47038
- return next;
47039
- });
47040
47083
  setSlotFeedback((prev) => {
47041
47084
  const next = [...prev];
47042
47085
  next[index] = null;
@@ -47046,11 +47089,6 @@ function SequencerBoard({
47046
47089
  if (placeEvent) emit(`UI:${placeEvent}`, { slotIndex: index, actionId: item.id });
47047
47090
  }, [emit, placeEvent]);
47048
47091
  const handleSlotRemove = React91.useCallback((index) => {
47049
- setSlots((prev) => {
47050
- const next = [...prev];
47051
- next[index] = void 0;
47052
- return next;
47053
- });
47054
47092
  setSlotFeedback((prev) => {
47055
47093
  const next = [...prev];
47056
47094
  next[index] = null;
@@ -47061,48 +47099,34 @@ function SequencerBoard({
47061
47099
  }, [emit, removeEvent]);
47062
47100
  const handleReset = React91.useCallback(() => {
47063
47101
  if (timerRef.current) clearTimeout(timerRef.current);
47064
- setSlots(Array.from({ length: maxSlots }, () => void 0));
47065
- setPlayState("idle");
47066
- setCurrentStep(-1);
47067
- setAttempts(0);
47068
47102
  setSlotFeedback(Array.from({ length: maxSlots }, () => null));
47069
47103
  if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
47070
47104
  }, [maxSlots, playAgainEvent, emit]);
47071
47105
  const filledSlots = slots.filter((s) => !!s);
47072
- const canPlay = filledSlots.length > 0 && playState === "idle";
47106
+ const canPlay = filledSlots.length > 0 && !isPlayingBack && !isSuccess;
47073
47107
  const handlePlay = React91.useCallback(() => {
47074
47108
  if (!canPlay) return;
47075
47109
  setSlotFeedback(Array.from({ length: maxSlots }, () => null));
47076
47110
  emit("UI:PLAY_SOUND", { key: "confirm" });
47077
47111
  const sequence = slots.map((s) => s?.id || "");
47078
- if (playEvent) {
47079
- emit(`UI:${playEvent}`, { sequence });
47080
- }
47081
- setPlayState("playing");
47082
- setCurrentStep(0);
47112
+ const playerIds = slots.filter(Boolean).map((s) => s?.id || "");
47113
+ if (playEvent) emit(`UI:${playEvent}`, { sequence });
47083
47114
  let step = 0;
47084
47115
  const advance = () => {
47085
47116
  step++;
47117
+ emit("UI:STEP", { step });
47086
47118
  if (step >= maxSlots) {
47119
+ if (checkEvent) emit(`UI:${checkEvent}`, { sequence: playerIds });
47087
47120
  const playerSeq = slots.map((s) => s?.id);
47088
- const playerIds = slots.filter(Boolean).map((s) => s?.id || "");
47089
47121
  const success = solutions.some(
47090
47122
  (sol) => sol.length === playerIds.length && sol.every((id, i) => id === playerIds[i])
47091
47123
  );
47092
- if (checkEvent) emit(`UI:${checkEvent}`, { sequence: playerIds });
47093
47124
  if (success) {
47094
- setPlayState("success");
47095
- setCurrentStep(-1);
47096
47125
  emit("UI:PLAY_SOUND", { key: "levelComplete" });
47097
- if (completeEvent) {
47098
- emit(`UI:${completeEvent}`, { success: true, sequence: playerIds });
47099
- }
47126
+ if (completeEvent) emit(`UI:${completeEvent}`, { success: true, sequence: playerIds });
47100
47127
  } else {
47101
- setAttempts((prev) => prev + 1);
47102
47128
  const feedback = computeSlotFeedback(playerSeq, solutions);
47103
47129
  setSlotFeedback(feedback);
47104
- setPlayState("idle");
47105
- setCurrentStep(-1);
47106
47130
  emit("UI:PLAY_SOUND", { key: "fail" });
47107
47131
  const correctCount2 = feedback.filter((f3) => f3 === "correct").length;
47108
47132
  for (let ci = 0; ci < correctCount2; ci++) {
@@ -47112,7 +47136,6 @@ function SequencerBoard({
47112
47136
  }
47113
47137
  }
47114
47138
  } else {
47115
- setCurrentStep(step);
47116
47139
  timerRef.current = setTimeout(advance, stepDurationMs);
47117
47140
  }
47118
47141
  };
@@ -47162,7 +47185,7 @@ function SequencerBoard({
47162
47185
  /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "xs", children: [
47163
47186
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { className: "items-center justify-between", children: [
47164
47187
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("sequencer.yourSequence") + ":" }),
47165
- hasFeedback && playState === "idle" && /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "caption", className: "text-muted-foreground", children: [
47188
+ hasFeedback && !isPlayingBack && !isSuccess && /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "caption", className: "text-muted-foreground", children: [
47166
47189
  `${correctCount}/${maxSlots} `,
47167
47190
  "\u2705"
47168
47191
  ] })
@@ -47174,7 +47197,7 @@ function SequencerBoard({
47174
47197
  maxSlots,
47175
47198
  onSlotDrop: handleSlotDrop,
47176
47199
  onSlotRemove: handleSlotRemove,
47177
- playing: playState === "playing",
47200
+ playing: isPlayingBack,
47178
47201
  currentStep,
47179
47202
  categoryColors,
47180
47203
  slotFeedback,
@@ -47182,7 +47205,7 @@ function SequencerBoard({
47182
47205
  }
47183
47206
  )
47184
47207
  ] }),
47185
- playState !== "playing" && /* @__PURE__ */ jsxRuntime.jsx(
47208
+ !isPlayingBack && /* @__PURE__ */ jsxRuntime.jsx(
47186
47209
  ActionPalette,
47187
47210
  {
47188
47211
  actions: availableActions,
@@ -47192,8 +47215,8 @@ function SequencerBoard({
47192
47215
  label: t("sequencer.dragActions")
47193
47216
  }
47194
47217
  ),
47195
- 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) }) }),
47196
- 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") }) }),
47218
+ 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) }) }),
47219
+ 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") }) }),
47197
47220
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", children: [
47198
47221
  /* @__PURE__ */ jsxRuntime.jsx(
47199
47222
  Button,
@@ -48243,6 +48266,9 @@ function StateArchitectBoard({
48243
48266
  stepDurationMs = 600,
48244
48267
  testEvent,
48245
48268
  completeEvent,
48269
+ addTransitionEvent,
48270
+ removeTransitionEvent,
48271
+ playAgainEvent,
48246
48272
  className
48247
48273
  }) {
48248
48274
  const { emit } = useEventBus();
@@ -48255,14 +48281,16 @@ function StateArchitectBoard({
48255
48281
  const testCases = Array.isArray(resolved?.testCases) ? resolved.testCases : [];
48256
48282
  const entityTransitions = Array.isArray(resolved?.transitions) ? resolved.transitions : [];
48257
48283
  const entityVariables = rows(resolved?.variables);
48258
- const [transitions, setTransitions] = React91.useState(entityTransitions);
48284
+ const transitions = entityTransitions;
48285
+ const attempts = typeof resolved?.attempts === "number" ? resolved.attempts : 0;
48286
+ const entityResult = str(resolved?.result) || "none";
48287
+ const isSuccess = entityResult === "win";
48288
+ const [isTesting, setIsTesting] = React91.useState(false);
48259
48289
  const [headerError, setHeaderError] = React91.useState(false);
48260
- const [playState, setPlayState] = React91.useState("editing");
48261
48290
  const [currentState, setCurrentState] = React91.useState(initialState);
48262
48291
  const [selectedState, setSelectedState] = React91.useState(null);
48263
48292
  const [testResults, setTestResults] = React91.useState([]);
48264
48293
  const [variables, setVariables] = React91.useState(() => [...entityVariables]);
48265
- const [attempts, setAttempts] = React91.useState(0);
48266
48294
  const timerRef = React91.useRef(null);
48267
48295
  const [addingFrom, setAddingFrom] = React91.useState(null);
48268
48296
  React91.useEffect(() => () => {
@@ -48272,7 +48300,7 @@ function StateArchitectBoard({
48272
48300
  const GRAPH_H = 400;
48273
48301
  const positions = React91.useMemo(() => layoutStates(entityStates, GRAPH_W, GRAPH_H), [entityStates]);
48274
48302
  const handleStateClick = React91.useCallback((state) => {
48275
- if (playState !== "editing") return;
48303
+ if (isTesting) return;
48276
48304
  if (addingFrom) {
48277
48305
  if (addingFrom !== state) {
48278
48306
  const event = availableEvents[0] || "EVENT";
@@ -48282,20 +48310,20 @@ function StateArchitectBoard({
48282
48310
  to: state,
48283
48311
  event
48284
48312
  };
48285
- setTransitions((prev) => [...prev, newTrans]);
48313
+ if (addTransitionEvent) emit(`UI:${addTransitionEvent}`, { id: newTrans.id, from: newTrans.from, to: newTrans.to, event: newTrans.event });
48286
48314
  }
48287
48315
  setAddingFrom(null);
48288
48316
  } else {
48289
48317
  setSelectedState(state);
48290
48318
  }
48291
- }, [playState, addingFrom, availableEvents]);
48319
+ }, [isTesting, addingFrom, availableEvents, addTransitionEvent, emit]);
48292
48320
  const handleStartAddTransition = React91.useCallback(() => {
48293
48321
  if (!selectedState) return;
48294
48322
  setAddingFrom(selectedState);
48295
48323
  }, [selectedState]);
48296
48324
  const handleRemoveTransition = React91.useCallback((transId) => {
48297
- setTransitions((prev) => prev.filter((t2) => t2.id !== transId));
48298
- }, []);
48325
+ if (removeTransitionEvent) emit(`UI:${removeTransitionEvent}`, { id: transId });
48326
+ }, [removeTransitionEvent, emit]);
48299
48327
  const machine = React91.useMemo(() => ({
48300
48328
  name: entityName,
48301
48329
  description: str(resolved?.description),
@@ -48309,16 +48337,16 @@ function StateArchitectBoard({
48309
48337
  }))
48310
48338
  }), [entityName, resolved, entityStates, currentState, transitions]);
48311
48339
  const handleTest = React91.useCallback(() => {
48312
- if (playState !== "editing") return;
48340
+ if (isTesting) return;
48313
48341
  if (testEvent) emit(`UI:${testEvent}`, {});
48314
- setPlayState("testing");
48342
+ setIsTesting(true);
48315
48343
  setTestResults([]);
48316
48344
  const results = [];
48317
48345
  let testIdx = 0;
48318
48346
  const runNextTest = () => {
48319
48347
  if (testIdx >= testCases.length) {
48320
48348
  const allPassed = results.every((r2) => r2.passed);
48321
- setPlayState(allPassed ? "success" : "fail");
48349
+ setIsTesting(false);
48322
48350
  setTestResults(results);
48323
48351
  if (allPassed && completeEvent) {
48324
48352
  emit(`UI:${completeEvent}`, {
@@ -48326,9 +48354,6 @@ function StateArchitectBoard({
48326
48354
  passedTests: results.filter((r2) => r2.passed).length
48327
48355
  });
48328
48356
  }
48329
- if (!allPassed) {
48330
- setAttempts((prev) => prev + 1);
48331
- }
48332
48357
  return;
48333
48358
  }
48334
48359
  const testCase = testCases[testIdx];
@@ -48351,24 +48376,23 @@ function StateArchitectBoard({
48351
48376
  timerRef.current = setTimeout(runNextTest, stepDurationMs);
48352
48377
  };
48353
48378
  timerRef.current = setTimeout(runNextTest, stepDurationMs);
48354
- }, [playState, transitions, testCases, initialState, stepDurationMs, testEvent, completeEvent, emit]);
48379
+ }, [isTesting, transitions, testCases, initialState, stepDurationMs, testEvent, completeEvent, emit]);
48355
48380
  const handleTryAgain = React91.useCallback(() => {
48356
48381
  if (timerRef.current) clearTimeout(timerRef.current);
48357
- setPlayState("editing");
48382
+ setIsTesting(false);
48358
48383
  setCurrentState(initialState);
48359
48384
  setTestResults([]);
48360
48385
  }, [initialState]);
48361
48386
  const handleReset = React91.useCallback(() => {
48362
48387
  if (timerRef.current) clearTimeout(timerRef.current);
48363
- setTransitions(entityTransitions);
48364
- setPlayState("editing");
48388
+ setIsTesting(false);
48365
48389
  setCurrentState(initialState);
48366
48390
  setTestResults([]);
48367
48391
  setVariables([...entityVariables]);
48368
48392
  setSelectedState(null);
48369
48393
  setAddingFrom(null);
48370
- setAttempts(0);
48371
- }, [entityTransitions, initialState, entityVariables]);
48394
+ if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
48395
+ }, [initialState, entityVariables, playAgainEvent, emit]);
48372
48396
  const codeData = React91.useMemo(() => ({
48373
48397
  name: entityName,
48374
48398
  states: entityStates,
@@ -48464,7 +48488,7 @@ function StateArchitectBoard({
48464
48488
  ]
48465
48489
  }
48466
48490
  ),
48467
- playState === "editing" && /* @__PURE__ */ jsxRuntime.jsx(HStack, { gap: "sm", children: /* @__PURE__ */ jsxRuntime.jsx(
48491
+ !isTesting && /* @__PURE__ */ jsxRuntime.jsx(HStack, { gap: "sm", children: /* @__PURE__ */ jsxRuntime.jsx(
48468
48492
  Button,
48469
48493
  {
48470
48494
  variant: "ghost",
@@ -48486,7 +48510,7 @@ function StateArchitectBoard({
48486
48510
  t2.guardHint,
48487
48511
  ")"
48488
48512
  ] }),
48489
- playState === "editing" && /* @__PURE__ */ jsxRuntime.jsx(
48513
+ !isTesting && /* @__PURE__ */ jsxRuntime.jsx(
48490
48514
  Button,
48491
48515
  {
48492
48516
  variant: "ghost",
@@ -48518,21 +48542,21 @@ function StateArchitectBoard({
48518
48542
  resolved.showCodeView !== false && /* @__PURE__ */ jsxRuntime.jsx(StateJsonView, { data: codeData, label: "View Code" })
48519
48543
  ] })
48520
48544
  ] }),
48521
- 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") }) }),
48522
- playState === "fail" && /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
48545
+ 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") }) }),
48546
+ !isTesting && !isSuccess && testResults.some((r2) => !r2.passed) && /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
48523
48547
  /* @__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]) }) }),
48524
- 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: [
48548
+ !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: [
48525
48549
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-accent font-bold shrink-0", children: "\u{1F4A1} " + t("game.hint") + ":" }),
48526
48550
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", className: "text-foreground", children: hint })
48527
48551
  ] }) })
48528
48552
  ] }),
48529
48553
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", children: [
48530
- playState === "fail" ? /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "primary", onClick: handleTryAgain, children: "\u{1F504} " + t("puzzle.tryAgainButton") }) : /* @__PURE__ */ jsxRuntime.jsx(
48554
+ !isTesting && !isSuccess && testResults.some((r2) => !r2.passed) ? /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "primary", onClick: handleTryAgain, children: "\u{1F504} " + t("puzzle.tryAgainButton") }) : /* @__PURE__ */ jsxRuntime.jsx(
48531
48555
  Button,
48532
48556
  {
48533
48557
  variant: "primary",
48534
48558
  onClick: handleTest,
48535
- disabled: playState !== "editing",
48559
+ disabled: isTesting,
48536
48560
  children: "\u25B6 " + t("game.runTests")
48537
48561
  }
48538
48562
  ),
@@ -49236,17 +49260,12 @@ var init_UncontrolledBattleBoard = __esm({
49236
49260
  }
49237
49261
  });
49238
49262
  function heroPosition(h) {
49263
+ if ("position" in h && h.position != null) return h.position;
49239
49264
  return vec2(h.position);
49240
49265
  }
49241
- function heroOwner(h) {
49242
- return str(h.owner);
49243
- }
49244
49266
  function heroMovement(h) {
49245
49267
  return num(h.movement);
49246
49268
  }
49247
- function hexPassable(h) {
49248
- return h.passable !== false;
49249
- }
49250
49269
  function defaultIsInRange(from, to, range) {
49251
49270
  return Math.abs(from.x - to.x) + Math.abs(from.y - to.y) <= range;
49252
49271
  }
@@ -49282,38 +49301,41 @@ function WorldMapBoard({
49282
49301
  }) {
49283
49302
  const eventBus = useEventBus();
49284
49303
  const resolved = boardEntity(entity);
49285
- const hexes = rows(resolved?.hexes);
49286
- const heroes = rows(resolved?.heroes);
49304
+ const entityUnits = rows(resolved?.units);
49305
+ const entityTiles = rows(resolved?.tiles);
49287
49306
  const features = propFeatures ?? (Array.isArray(resolved?.features) ? resolved.features : []);
49288
49307
  const selectedHeroId = resolved?.selectedHeroId ?? null;
49289
49308
  const assetManifest = propAssetManifest ?? resolved?.assetManifest;
49290
49309
  const backgroundImage = resolved?.backgroundImage;
49291
49310
  const [hoveredTile, setHoveredTile] = React91.useState(null);
49292
- const selectedHero = React91.useMemo(
49293
- () => heroes.find((h) => str(h.id) === selectedHeroId) ?? null,
49294
- [heroes, selectedHeroId]
49295
- );
49296
49311
  const derivedTiles = React91.useMemo(
49297
- () => hexes.map((hex) => ({
49298
- x: num(hex.x),
49299
- y: num(hex.y),
49300
- terrain: str(hex.terrain),
49301
- terrainSprite: hex.terrainSprite == null ? void 0 : str(hex.terrainSprite)
49312
+ () => entityTiles.map((t) => ({
49313
+ x: num(t.x),
49314
+ y: num(t.y),
49315
+ terrain: str(t.terrain),
49316
+ terrainSprite: t.terrainSprite == null ? void 0 : str(t.terrainSprite),
49317
+ passable: t.passable !== false
49302
49318
  })),
49303
- [hexes]
49319
+ [entityTiles]
49304
49320
  );
49305
49321
  const tiles = propTiles ?? derivedTiles;
49306
49322
  const baseUnits = React91.useMemo(
49307
- () => propUnits ?? heroes.map((hero) => ({
49308
- id: str(hero.id),
49309
- position: heroPosition(hero),
49310
- name: str(hero.name),
49311
- team: heroOwner(hero) === "enemy" ? "enemy" : "player",
49312
- health: 100,
49313
- maxHealth: 100,
49314
- sprite: hero.sprite == null ? void 0 : str(hero.sprite)
49323
+ () => propUnits ?? entityUnits.map((u) => ({
49324
+ id: str(u.id),
49325
+ position: heroPosition(u),
49326
+ name: str(u.name),
49327
+ // lolo uses `team` field (not `owner`)
49328
+ team: str(u.team) === "enemy" ? "enemy" : "player",
49329
+ health: num(u.health) || 100,
49330
+ maxHealth: num(u.maxHealth) || 100,
49331
+ sprite: u.sprite == null ? void 0 : str(u.sprite)
49315
49332
  })),
49316
- [heroes, propUnits]
49333
+ [entityUnits, propUnits]
49334
+ );
49335
+ const gameUnits = baseUnits;
49336
+ const selectedHero = React91.useMemo(
49337
+ () => gameUnits.find((u) => u.id === selectedHeroId) ?? null,
49338
+ [gameUnits, selectedHeroId]
49317
49339
  );
49318
49340
  const MOVE_SPEED_MS_PER_TILE = 300;
49319
49341
  const movementAnimRef = React91.useRef(null);
@@ -49359,51 +49381,50 @@ function WorldMapBoard({
49359
49381
  const validMoves = React91.useMemo(() => {
49360
49382
  if (!selectedHero || heroMovement(selectedHero) <= 0) return [];
49361
49383
  const sp = heroPosition(selectedHero);
49362
- const sOwner = heroOwner(selectedHero);
49384
+ const sTeam = str(selectedHero.team);
49363
49385
  const range = heroMovement(selectedHero);
49364
49386
  const moves = [];
49365
- hexes.forEach((hex) => {
49366
- const hx = num(hex.x);
49367
- const hy = num(hex.y);
49368
- if (!hexPassable(hex)) return;
49369
- if (hx === sp.x && hy === sp.y) return;
49370
- if (!isInRange(sp, { x: hx, y: hy }, range)) return;
49371
- if (heroes.some((h) => {
49372
- const hp = heroPosition(h);
49373
- return hp.x === hx && hp.y === hy && heroOwner(h) === sOwner;
49387
+ tiles.forEach((t) => {
49388
+ const tx = t.x;
49389
+ const ty = t.y;
49390
+ if (t.passable === false) return;
49391
+ if (tx === sp.x && ty === sp.y) return;
49392
+ if (!isInRange(sp, { x: tx, y: ty }, range)) return;
49393
+ if (gameUnits.some((u) => {
49394
+ const up = u.position ?? { x: u.x ?? -1, y: u.y ?? -1 };
49395
+ return up.x === tx && up.y === ty && str(u.team) === sTeam;
49374
49396
  })) return;
49375
- moves.push({ x: hx, y: hy });
49397
+ moves.push({ x: tx, y: ty });
49376
49398
  });
49377
49399
  return moves;
49378
- }, [selectedHero, hexes, heroes, isInRange]);
49400
+ }, [selectedHero, tiles, gameUnits, isInRange]);
49379
49401
  const attackTargets = React91.useMemo(() => {
49380
49402
  if (!selectedHero || heroMovement(selectedHero) <= 0) return [];
49381
49403
  const sp = heroPosition(selectedHero);
49382
- const sOwner = heroOwner(selectedHero);
49404
+ const sTeam = str(selectedHero.team);
49383
49405
  const range = heroMovement(selectedHero);
49384
- return heroes.filter((h) => heroOwner(h) !== sOwner).filter((h) => isInRange(sp, heroPosition(h), range)).map((h) => heroPosition(h));
49385
- }, [selectedHero, heroes, isInRange]);
49386
- const maxY = Math.max(...hexes.map((h) => num(h.y)), 0);
49406
+ 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 });
49407
+ }, [selectedHero, gameUnits, isInRange]);
49408
+ const maxY = Math.max(...tiles.map((t) => t.y), 0);
49387
49409
  const baseOffsetX = (maxY + 1) * (TILE_WIDTH * scale / 2);
49388
49410
  const tileToScreen = React91.useCallback(
49389
49411
  (tx, ty) => isoToScreen(tx, ty, scale, baseOffsetX),
49390
49412
  [scale, baseOffsetX]
49391
49413
  );
49392
49414
  const hoveredHex = React91.useMemo(
49393
- () => hoveredTile ? hexes.find((h) => num(h.x) === hoveredTile.x && num(h.y) === hoveredTile.y) ?? null : null,
49394
- [hoveredTile, hexes]
49415
+ () => hoveredTile ? tiles.find((t) => t.x === hoveredTile.x && t.y === hoveredTile.y) ?? null : null,
49416
+ [hoveredTile, tiles]
49395
49417
  );
49396
49418
  const hoveredHero = React91.useMemo(
49397
- () => hoveredTile ? heroes.find((h) => {
49398
- const hp = heroPosition(h);
49399
- return hp.x === hoveredTile.x && hp.y === hoveredTile.y;
49419
+ () => hoveredTile ? gameUnits.find((u) => {
49420
+ const up = u.position ?? { x: u.x ?? -1, y: u.y ?? -1 };
49421
+ return up.x === hoveredTile.x && up.y === hoveredTile.y;
49400
49422
  }) ?? null : null,
49401
- [hoveredTile, heroes]
49423
+ [hoveredTile, gameUnits]
49402
49424
  );
49403
49425
  const handleTileClick = React91.useCallback((x, y) => {
49404
49426
  if (movementAnimRef.current) return;
49405
- const hex = hexes.find((h) => num(h.x) === x && num(h.y) === y);
49406
- if (!hex) return;
49427
+ const tile = tiles.find((t) => t.x === x && t.y === y);
49407
49428
  if (tileClickEvent) {
49408
49429
  eventBus.emit(`UI:${tileClickEvent}`, { x, y });
49409
49430
  }
@@ -49414,38 +49435,39 @@ function WorldMapBoard({
49414
49435
  if (heroMoveEvent) {
49415
49436
  eventBus.emit(`UI:${heroMoveEvent}`, { heroId, toX: x, toY: y });
49416
49437
  }
49417
- const feature = str(hex.feature);
49438
+ const feature = tile ? str(tile.feature) : "";
49418
49439
  if (feature && feature !== "none") {
49419
- onFeatureEnter?.(heroId, hex);
49440
+ const tileRow = tile;
49441
+ onFeatureEnter?.(heroId, tileRow);
49420
49442
  if (featureEnterEvent) {
49421
- eventBus.emit(`UI:${featureEnterEvent}`, { heroId, feature, hex });
49443
+ eventBus.emit(`UI:${featureEnterEvent}`, { heroId, feature, hex: tileRow });
49422
49444
  }
49423
49445
  }
49424
49446
  });
49425
49447
  return;
49426
49448
  }
49427
- const enemy = heroes.find((h) => {
49428
- const hp = heroPosition(h);
49429
- return hp.x === x && hp.y === y && heroOwner(h) === "enemy";
49449
+ const enemy = gameUnits.find((u) => {
49450
+ const up = u.position ?? { x: u.x ?? -1, y: u.y ?? -1 };
49451
+ return up.x === x && up.y === y && str(u.team) === "enemy";
49430
49452
  });
49431
49453
  if (selectedHero && enemy && attackTargets.some((t) => t.x === x && t.y === y)) {
49432
49454
  const attackerId = str(selectedHero.id);
49433
- const defenderId = str(enemy.id);
49455
+ const defenderId = enemy.id;
49434
49456
  onBattleEncounter?.(attackerId, defenderId);
49435
49457
  if (battleEncounterEvent) {
49436
49458
  eventBus.emit(`UI:${battleEncounterEvent}`, { attackerId, defenderId });
49437
49459
  }
49438
49460
  }
49439
- }, [hexes, heroes, selectedHero, validMoves, attackTargets, startMoveAnimation, onHeroMove, onFeatureEnter, onBattleEncounter, eventBus, tileClickEvent, heroMoveEvent, featureEnterEvent, battleEncounterEvent]);
49461
+ }, [tiles, gameUnits, selectedHero, validMoves, attackTargets, startMoveAnimation, onHeroMove, onFeatureEnter, onBattleEncounter, eventBus, tileClickEvent, heroMoveEvent, featureEnterEvent, battleEncounterEvent]);
49440
49462
  const handleUnitClick = React91.useCallback((unitId) => {
49441
- const hero = heroes.find((h) => str(h.id) === unitId);
49442
- if (hero && (heroOwner(hero) === "player" || allowMoveAllHeroes)) {
49463
+ const unit = gameUnits.find((u) => u.id === unitId);
49464
+ if (unit && (str(unit.team) === "player" || allowMoveAllHeroes)) {
49443
49465
  onHeroSelect?.(unitId);
49444
49466
  if (heroSelectEvent) {
49445
49467
  eventBus.emit(`UI:${heroSelectEvent}`, { heroId: unitId });
49446
49468
  }
49447
49469
  }
49448
- }, [heroes, onHeroSelect, allowMoveAllHeroes, eventBus, heroSelectEvent]);
49470
+ }, [gameUnits, onHeroSelect, allowMoveAllHeroes, eventBus, heroSelectEvent]);
49449
49471
  const selectHero = React91.useCallback((id) => {
49450
49472
  onHeroSelect?.(id);
49451
49473
  if (heroSelectEvent) {