@almadar/ui 5.47.0 → 5.48.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7278,6 +7278,7 @@ var init_Sprite = __esm({
7278
7278
  }
7279
7279
  });
7280
7280
  function StateIndicator({
7281
+ assetUrl = DEFAULT_ASSET_URL,
7281
7282
  state = "idle",
7282
7283
  label,
7283
7284
  size = "md",
@@ -7300,18 +7301,29 @@ function StateIndicator({
7300
7301
  className
7301
7302
  ),
7302
7303
  children: [
7303
- /* @__PURE__ */ jsx(Box, { as: "span", children: typeof config.icon === "string" ? /^[a-zA-Z0-9-]+$/.test(config.icon) ? /* @__PURE__ */ jsx(Icon, { name: config.icon }) : config.icon : /* @__PURE__ */ jsx(Icon, { icon: config.icon }) }),
7304
+ /* @__PURE__ */ jsx(Box, { as: "span", children: assetUrl ? /* @__PURE__ */ jsx(
7305
+ "img",
7306
+ {
7307
+ src: assetUrl,
7308
+ alt: displayLabel,
7309
+ width: 16,
7310
+ height: 16,
7311
+ style: { imageRendering: "pixelated", objectFit: "contain" },
7312
+ className: "flex-shrink-0"
7313
+ }
7314
+ ) : typeof config.icon === "string" ? /^[a-zA-Z0-9-]+$/.test(config.icon) ? /* @__PURE__ */ jsx(Icon, { name: config.icon }) : config.icon : /* @__PURE__ */ jsx(Icon, { icon: config.icon }) }),
7304
7315
  /* @__PURE__ */ jsx(Box, { as: "span", children: displayLabel })
7305
7316
  ]
7306
7317
  }
7307
7318
  );
7308
7319
  }
7309
- var DEFAULT_STATE_STYLES, DEFAULT_STYLE, SIZE_CLASSES;
7320
+ var DEFAULT_ASSET_URL, DEFAULT_STATE_STYLES, DEFAULT_STYLE, SIZE_CLASSES;
7310
7321
  var init_StateIndicator = __esm({
7311
7322
  "components/game/atoms/StateIndicator.tsx"() {
7312
7323
  init_Box();
7313
7324
  init_Icon();
7314
7325
  init_cn();
7326
+ DEFAULT_ASSET_URL = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
7315
7327
  DEFAULT_STATE_STYLES = {
7316
7328
  idle: { icon: "\u23F8", bgClass: "bg-muted" },
7317
7329
  active: { icon: "\u25B6", bgClass: "bg-success" },
@@ -7386,6 +7398,7 @@ var init_TimerDisplay = __esm({
7386
7398
  }
7387
7399
  });
7388
7400
  function ResourceCounter({
7401
+ assetUrl = DEFAULT_ASSET_URL2,
7389
7402
  icon,
7390
7403
  label = "Gold",
7391
7404
  value = 250,
@@ -7405,7 +7418,17 @@ function ResourceCounter({
7405
7418
  className
7406
7419
  ),
7407
7420
  children: [
7408
- icon && /* @__PURE__ */ jsx("span", { className: cn("flex-shrink-0", sizes.icon), children: typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon }) : /* @__PURE__ */ jsx(Icon, { icon }) }),
7421
+ assetUrl ? /* @__PURE__ */ jsx(
7422
+ "img",
7423
+ {
7424
+ src: assetUrl,
7425
+ alt: label,
7426
+ width: sizes.img,
7427
+ height: sizes.img,
7428
+ style: { imageRendering: "pixelated", objectFit: "contain" },
7429
+ className: "flex-shrink-0"
7430
+ }
7431
+ ) : icon ? /* @__PURE__ */ jsx("span", { className: cn("flex-shrink-0", sizes.icon), children: typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon }) : /* @__PURE__ */ jsx(Icon, { icon }) }) : null,
7409
7432
  /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: label }),
7410
7433
  /* @__PURE__ */ jsxs("span", { className: cn("font-bold tabular-nums", color && (color in colorTokenClasses2 ? colorTokenClasses2[color] : color)), children: [
7411
7434
  value,
@@ -7418,7 +7441,7 @@ function ResourceCounter({
7418
7441
  }
7419
7442
  );
7420
7443
  }
7421
- var colorTokenClasses2, sizeMap5;
7444
+ var colorTokenClasses2, DEFAULT_ASSET_URL2, sizeMap5;
7422
7445
  var init_ResourceCounter = __esm({
7423
7446
  "components/game/atoms/ResourceCounter.tsx"() {
7424
7447
  init_cn();
@@ -7431,16 +7454,17 @@ var init_ResourceCounter = __esm({
7431
7454
  error: "text-error",
7432
7455
  muted: "text-muted-foreground"
7433
7456
  };
7457
+ DEFAULT_ASSET_URL2 = "https://almadar-kflow-assets.web.app/shared/world-map/gold_mine.png";
7434
7458
  sizeMap5 = {
7435
- sm: { wrapper: "text-xs gap-1 px-1.5 py-0.5", icon: "text-sm" },
7436
- md: { wrapper: "text-sm gap-1.5 px-2 py-1", icon: "text-base" },
7437
- lg: { wrapper: "text-base gap-2 px-3 py-1.5", icon: "text-lg" }
7459
+ sm: { wrapper: "text-xs gap-1 px-1.5 py-0.5", icon: "text-sm", img: 16 },
7460
+ md: { wrapper: "text-sm gap-1.5 px-2 py-1", icon: "text-base", img: 20 },
7461
+ lg: { wrapper: "text-base gap-2 px-3 py-1.5", icon: "text-lg", img: 28 }
7438
7462
  };
7439
7463
  ResourceCounter.displayName = "ResourceCounter";
7440
7464
  }
7441
7465
  });
7442
7466
  function ItemSlot({
7443
- assetUrl = DEFAULT_ASSET_URL,
7467
+ assetUrl = DEFAULT_ASSET_URL3,
7444
7468
  icon = "sword",
7445
7469
  label = "Iron Sword",
7446
7470
  quantity,
@@ -7499,7 +7523,7 @@ function ItemSlot({
7499
7523
  }
7500
7524
  );
7501
7525
  }
7502
- var sizeMap6, rarityBorderMap, rarityGlowMap, DEFAULT_ASSET_URL, assetSizeMap;
7526
+ var sizeMap6, rarityBorderMap, rarityGlowMap, DEFAULT_ASSET_URL3, assetSizeMap;
7503
7527
  var init_ItemSlot = __esm({
7504
7528
  "components/game/atoms/ItemSlot.tsx"() {
7505
7529
  "use client";
@@ -7524,7 +7548,7 @@ var init_ItemSlot = __esm({
7524
7548
  epic: "shadow-lg",
7525
7549
  legendary: "shadow-lg"
7526
7550
  };
7527
- DEFAULT_ASSET_URL = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
7551
+ DEFAULT_ASSET_URL3 = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
7528
7552
  assetSizeMap = {
7529
7553
  sm: 28,
7530
7554
  md: 40,
@@ -7597,6 +7621,7 @@ function getComboScale(combo) {
7597
7621
  return "";
7598
7622
  }
7599
7623
  function ComboCounter({
7624
+ assetUrl = DEFAULT_ASSET_URL4,
7600
7625
  combo = 5,
7601
7626
  multiplier,
7602
7627
  streak,
@@ -7616,6 +7641,17 @@ function ComboCounter({
7616
7641
  className
7617
7642
  ),
7618
7643
  children: [
7644
+ assetUrl && /* @__PURE__ */ jsx(
7645
+ "img",
7646
+ {
7647
+ src: assetUrl,
7648
+ alt: "combo",
7649
+ width: 24,
7650
+ height: 24,
7651
+ style: { imageRendering: "pixelated", objectFit: "contain" },
7652
+ className: "flex-shrink-0 mb-0.5"
7653
+ }
7654
+ ),
7619
7655
  /* @__PURE__ */ jsx("span", { className: cn("font-black tabular-nums leading-none", sizes.combo, getComboIntensity(combo)), children: combo }),
7620
7656
  /* @__PURE__ */ jsx("span", { className: cn("font-bold uppercase tracking-wider text-muted-foreground", sizes.label), children: "combo" }),
7621
7657
  multiplier != null && multiplier > 1 && /* @__PURE__ */ jsxs("span", { className: cn("font-bold text-warning tabular-nums", sizes.multiplier), children: [
@@ -7630,10 +7666,11 @@ function ComboCounter({
7630
7666
  }
7631
7667
  );
7632
7668
  }
7633
- var sizeMap8;
7669
+ var DEFAULT_ASSET_URL4, sizeMap8;
7634
7670
  var init_ComboCounter = __esm({
7635
7671
  "components/game/atoms/ComboCounter.tsx"() {
7636
7672
  init_cn();
7673
+ DEFAULT_ASSET_URL4 = "https://almadar-kflow-assets.web.app/shared/effects/flash/flash00.png";
7637
7674
  sizeMap8 = {
7638
7675
  sm: { combo: "text-lg", label: "text-xs", multiplier: "text-xs" },
7639
7676
  md: { combo: "text-2xl", label: "text-xs", multiplier: "text-sm" },
@@ -7727,6 +7764,7 @@ var init_XPBar = __esm({
7727
7764
  }
7728
7765
  });
7729
7766
  function WaypointMarker({
7767
+ assetUrl = DEFAULT_ASSET_URL5,
7730
7768
  label,
7731
7769
  icon,
7732
7770
  active = true,
@@ -7765,7 +7803,17 @@ function WaypointMarker({
7765
7803
  active && !completed && "bg-info text-foreground",
7766
7804
  !active && !completed && "bg-muted"
7767
7805
  ),
7768
- children: completed ? checkIcon : icon && (typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon }) : /* @__PURE__ */ jsx(Icon, { icon }))
7806
+ children: completed ? checkIcon : assetUrl ? /* @__PURE__ */ jsx(
7807
+ "img",
7808
+ {
7809
+ src: assetUrl,
7810
+ alt: label,
7811
+ width: sizes.img,
7812
+ height: sizes.img,
7813
+ style: { imageRendering: "pixelated", objectFit: "contain" },
7814
+ className: "flex-shrink-0"
7815
+ }
7816
+ ) : icon ? typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon }) : /* @__PURE__ */ jsx(Icon, { icon }) : null
7769
7817
  }
7770
7818
  )
7771
7819
  ] }),
@@ -7782,15 +7830,16 @@ function WaypointMarker({
7782
7830
  )
7783
7831
  ] });
7784
7832
  }
7785
- var sizeMap10, checkIcon;
7833
+ var DEFAULT_ASSET_URL5, sizeMap10, checkIcon;
7786
7834
  var init_WaypointMarker = __esm({
7787
7835
  "components/game/atoms/WaypointMarker.tsx"() {
7788
7836
  init_cn();
7789
7837
  init_Icon();
7838
+ DEFAULT_ASSET_URL5 = "https://almadar-kflow-assets.web.app/shared/world-map/battle_marker.png";
7790
7839
  sizeMap10 = {
7791
- sm: { dot: "w-4 h-4", ring: "w-6 h-6", label: "text-xs mt-1" },
7792
- md: { dot: "w-6 h-6", ring: "w-8 h-8", label: "text-sm mt-1.5" },
7793
- lg: { dot: "w-8 h-8", ring: "w-10 h-10", label: "text-base mt-2" }
7840
+ sm: { dot: "w-4 h-4", ring: "w-6 h-6", label: "text-xs mt-1", img: 12 },
7841
+ md: { dot: "w-6 h-6", ring: "w-8 h-8", label: "text-sm mt-1.5", img: 18 },
7842
+ lg: { dot: "w-8 h-8", ring: "w-10 h-10", label: "text-base mt-2", img: 24 }
7794
7843
  };
7795
7844
  checkIcon = /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 3, className: "w-3/5 h-3/5", children: /* @__PURE__ */ jsx("path", { d: "M5 13l4 4L19 7", strokeLinecap: "round", strokeLinejoin: "round" }) });
7796
7845
  WaypointMarker.displayName = "WaypointMarker";
@@ -7803,6 +7852,7 @@ function formatDuration(seconds) {
7803
7852
  return `${Math.round(seconds)}s`;
7804
7853
  }
7805
7854
  function StatusEffect({
7855
+ assetUrl = DEFAULT_ASSET_URL6,
7806
7856
  icon = "shield",
7807
7857
  label = "Shield",
7808
7858
  duration = 30,
@@ -7823,7 +7873,17 @@ function StatusEffect({
7823
7873
  ),
7824
7874
  title: label,
7825
7875
  children: [
7826
- /* @__PURE__ */ jsx("span", { className: cn("flex items-center justify-center", sizes.icon), children: typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon, size: "sm" }) : /* @__PURE__ */ jsx(Icon, { icon, size: "sm" }) }),
7876
+ /* @__PURE__ */ jsx("span", { className: cn("flex items-center justify-center", sizes.icon), children: assetUrl ? /* @__PURE__ */ jsx(
7877
+ "img",
7878
+ {
7879
+ src: assetUrl,
7880
+ alt: label,
7881
+ width: sizes.img,
7882
+ height: sizes.img,
7883
+ style: { imageRendering: "pixelated", objectFit: "contain" },
7884
+ className: "flex-shrink-0"
7885
+ }
7886
+ ) : typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon, size: "sm" }) : /* @__PURE__ */ jsx(Icon, { icon, size: "sm" }) }),
7827
7887
  duration !== void 0 && /* @__PURE__ */ jsx(
7828
7888
  "span",
7829
7889
  {
@@ -7850,15 +7910,16 @@ function StatusEffect({
7850
7910
  label && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground mt-0.5 text-center whitespace-nowrap", children: label })
7851
7911
  ] });
7852
7912
  }
7853
- var sizeMap11, variantStyles7;
7913
+ var DEFAULT_ASSET_URL6, sizeMap11, variantStyles7;
7854
7914
  var init_StatusEffect = __esm({
7855
7915
  "components/game/atoms/StatusEffect.tsx"() {
7856
7916
  init_cn();
7857
7917
  init_Icon();
7918
+ DEFAULT_ASSET_URL6 = "https://almadar-kflow-assets.web.app/shared/effects/particles/flame_01.png";
7858
7919
  sizeMap11 = {
7859
- sm: { container: "w-8 h-8", icon: "text-sm", badge: "text-xs -top-1 -right-1 w-4 h-4", timer: "text-[9px]" },
7860
- md: { container: "w-10 h-10", icon: "text-base", badge: "text-xs -top-1 -right-1 w-5 h-5", timer: "text-xs" },
7861
- 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" }
7920
+ 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 },
7921
+ 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 },
7922
+ 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 }
7862
7923
  };
7863
7924
  variantStyles7 = {
7864
7925
  buff: "border-success bg-success/20",
@@ -11427,15 +11488,15 @@ function BattleBoard({
11427
11488
  const board = boardEntity(entity) ?? {};
11428
11489
  const tiles = propTiles ?? (Array.isArray(board.tiles) ? board.tiles : []);
11429
11490
  const features = propFeatures ?? (Array.isArray(board.features) ? board.features : []);
11430
- const boardWidth = num(board.boardWidth, 8);
11431
- const boardHeight = num(board.boardHeight, 6);
11491
+ const boardWidth = num(board.gridWidth ?? board.boardWidth, 8);
11492
+ const boardHeight = num(board.gridHeight ?? board.boardHeight, 6);
11432
11493
  const assetManifest = propAssetManifest ?? board.assetManifest;
11433
11494
  const backgroundImage = board.backgroundImage;
11434
11495
  const units = rows(board.units);
11435
11496
  const selectedUnitId = board.selectedUnitId ?? null;
11436
11497
  const currentPhase = str(board.phase) || "observation";
11437
11498
  const currentTurn = num(board.turn, 1);
11438
- const gameResult = board.gameResult ?? null;
11499
+ const gameResult = board.result ?? null;
11439
11500
  const eventBus = useEventBus();
11440
11501
  const { t } = useTranslate();
11441
11502
  const [hoveredTile, setHoveredTile] = useState(null);
@@ -11456,7 +11517,7 @@ function BattleBoard({
11456
11517
  const validMoves = useMemo(() => {
11457
11518
  if (!selectedUnit || currentPhase !== "movement") return [];
11458
11519
  const moves = [];
11459
- const range = num(selectedUnit.movement);
11520
+ const range = num(board.movementRange, 2);
11460
11521
  const origin = unitPosition(selectedUnit);
11461
11522
  for (let dy = -range; dy <= range; dy++) {
11462
11523
  for (let dx = -range; dx <= range; dx++) {
@@ -19741,15 +19802,24 @@ function CastleBoard({
19741
19802
  featureClickEvent,
19742
19803
  unitClickEvent,
19743
19804
  tileClickEvent,
19805
+ playAgainEvent,
19744
19806
  className
19745
19807
  }) {
19746
19808
  const eventBus = useEventBus();
19809
+ const { t } = useTranslate();
19747
19810
  const resolved = boardEntity(entity);
19748
19811
  const tiles = propTiles ?? (Array.isArray(resolved?.tiles) ? resolved.tiles : []);
19749
19812
  const features = propFeatures ?? (Array.isArray(resolved?.features) ? resolved.features : []);
19750
19813
  const units = propUnits ?? (Array.isArray(resolved?.units) ? resolved.units : []);
19751
19814
  const assetManifest = propAssetManifest ?? resolved?.assetManifest;
19752
19815
  const backgroundImage = resolved?.backgroundImage;
19816
+ const gold = num(resolved?.gold);
19817
+ const health = num(resolved?.health);
19818
+ const maxHealth = num(resolved?.maxHealth);
19819
+ const wave = num(resolved?.wave);
19820
+ const tickCount = num(resolved?.tickCount);
19821
+ const buildings = rows(resolved?.buildings);
19822
+ const result = str(resolved?.result) || "none";
19753
19823
  const [hoveredTile, setHoveredTile] = useState(null);
19754
19824
  const [selectedFeature, setSelectedFeature] = useState(null);
19755
19825
  const hoveredFeature = useMemo(() => {
@@ -19762,7 +19832,7 @@ function CastleBoard({
19762
19832
  (u) => u.position?.x === hoveredTile.x && u.position?.y === hoveredTile.y
19763
19833
  ) ?? null;
19764
19834
  }, [hoveredTile, units]);
19765
- const maxY = Math.max(...tiles.map((t) => t.y), 0);
19835
+ const maxY = Math.max(...tiles.map((t2) => t2.y), 0);
19766
19836
  const baseOffsetX = (maxY + 1) * (TILE_WIDTH * scale / 2);
19767
19837
  const tileToScreen = useCallback(
19768
19838
  (tx, ty) => isoToScreen(tx, ty, scale, baseOffsetX),
@@ -19797,6 +19867,11 @@ function CastleBoard({
19797
19867
  }
19798
19868
  }, [units, onUnitClick, unitClickEvent, eventBus]);
19799
19869
  const clearSelection = useCallback(() => setSelectedFeature(null), []);
19870
+ const handlePlayAgain = useCallback(() => {
19871
+ if (playAgainEvent) {
19872
+ eventBus.emit(`UI:${playAgainEvent}`, {});
19873
+ }
19874
+ }, [playAgainEvent, eventBus]);
19800
19875
  const ctx = useMemo(
19801
19876
  () => ({
19802
19877
  hoveredTile,
@@ -19805,11 +19880,18 @@ function CastleBoard({
19805
19880
  selectedFeature,
19806
19881
  clearSelection,
19807
19882
  tileToScreen,
19808
- scale
19883
+ scale,
19884
+ gold,
19885
+ health,
19886
+ maxHealth,
19887
+ wave,
19888
+ tickCount,
19889
+ buildings,
19890
+ result
19809
19891
  }),
19810
- [hoveredTile, hoveredFeature, hoveredUnit, selectedFeature, clearSelection, tileToScreen, scale]
19892
+ [hoveredTile, hoveredFeature, hoveredUnit, selectedFeature, clearSelection, tileToScreen, scale, gold, health, maxHealth, wave, tickCount, buildings, result]
19811
19893
  );
19812
- return /* @__PURE__ */ jsxs("div", { className: cn("castle-board min-h-screen flex flex-col bg-background", className), children: [
19894
+ return /* @__PURE__ */ jsxs("div", { className: cn("castle-board relative min-h-screen flex flex-col bg-background", className), children: [
19813
19895
  header && header(ctx),
19814
19896
  /* @__PURE__ */ jsxs("div", { className: "flex flex-1 overflow-hidden", children: [
19815
19897
  /* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-auto p-4 relative", children: [
@@ -19834,7 +19916,29 @@ function CastleBoard({
19834
19916
  ] }),
19835
19917
  sidePanel && /* @__PURE__ */ jsx("div", { className: "w-96 shrink-0 border-l border-border bg-surface overflow-y-auto", children: sidePanel(ctx) })
19836
19918
  ] }),
19837
- footer && footer(ctx)
19919
+ footer && footer(ctx),
19920
+ result !== "none" && /* @__PURE__ */ jsx(Box, { className: "absolute inset-0 z-50 flex items-center justify-center bg-background/70 backdrop-blur-sm rounded-container", children: /* @__PURE__ */ jsxs(VStack, { className: "text-center p-8", gap: "lg", children: [
19921
+ /* @__PURE__ */ jsx(
19922
+ Typography,
19923
+ {
19924
+ variant: "h2",
19925
+ className: cn(
19926
+ "text-4xl font-black tracking-widest uppercase",
19927
+ result === "victory" ? "text-warning" : "text-error"
19928
+ ),
19929
+ children: result === "victory" ? t("battle.victory") : t("battle.defeat")
19930
+ }
19931
+ ),
19932
+ /* @__PURE__ */ jsx(
19933
+ Button,
19934
+ {
19935
+ variant: "primary",
19936
+ className: "px-8 py-3 font-semibold",
19937
+ onClick: handlePlayAgain,
19938
+ children: t("battle.playAgain")
19939
+ }
19940
+ )
19941
+ ] }) })
19838
19942
  ] });
19839
19943
  }
19840
19944
  var init_CastleBoard = __esm({
@@ -19842,6 +19946,10 @@ var init_CastleBoard = __esm({
19842
19946
  "use client";
19843
19947
  init_cn();
19844
19948
  init_useEventBus();
19949
+ init_Box();
19950
+ init_Button();
19951
+ init_Typography();
19952
+ init_Stack();
19845
19953
  init_IsometricCanvas();
19846
19954
  init_boardEntity();
19847
19955
  init_isometric();
@@ -20718,22 +20826,16 @@ function ClassifierBoard({
20718
20826
  const { emit } = useEventBus();
20719
20827
  const { t } = useTranslate();
20720
20828
  const resolved = boardEntity(entity);
20721
- const [localAssignments, setLocalAssignments] = useState({});
20722
20829
  const [headerError, setHeaderError] = useState(false);
20723
- const [localSubmitted, setLocalSubmitted] = useState(false);
20724
- const [localAttempts, setLocalAttempts] = useState(0);
20725
- const [showHint, setShowHint] = useState(false);
20726
20830
  const items = Array.isArray(resolved?.items) ? resolved.items : [];
20727
20831
  const categories = Array.isArray(resolved?.categories) ? resolved.categories : [];
20728
- const entityResult = str(resolved?.result);
20729
- const entityDrivesResult = entityResult.length > 0;
20730
- const entityHasAssignments = items.some((item) => item.assignedCategory != null);
20731
- const assignments = entityHasAssignments ? items.reduce((acc, item) => {
20732
- if (item.assignedCategory != null) acc[item.id] = item.assignedCategory;
20832
+ const result = str(resolved?.result);
20833
+ const submitted = result === "win";
20834
+ const attempts = num(resolved?.attempts);
20835
+ const assignments = items.reduce((acc, item) => {
20836
+ if (item.assignedCategory != null && item.assignedCategory !== "") acc[item.id] = item.assignedCategory;
20733
20837
  return acc;
20734
- }, {}) : localAssignments;
20735
- const attempts = entityDrivesResult ? num(resolved?.attempts) : localAttempts;
20736
- const submitted = entityDrivesResult || localSubmitted;
20838
+ }, {});
20737
20839
  const unassignedItems = items.filter((item) => !assignments[item.id]);
20738
20840
  const allAssigned = items.length > 0 && Object.keys(assignments).length === items.length;
20739
20841
  const results = submitted ? items.map((item) => ({
@@ -20741,54 +20843,25 @@ function ClassifierBoard({
20741
20843
  assigned: assignments[item.id],
20742
20844
  correct: assignments[item.id] === item.correctCategory
20743
20845
  })) : [];
20744
- const allCorrect = entityDrivesResult ? entityResult === "success" : results.length > 0 && results.every((r) => r.correct);
20846
+ const allCorrect = result === "win";
20745
20847
  const correctCount = results.filter((r) => r.correct).length;
20848
+ const showHint = attempts >= 2 && Boolean(str(resolved?.hint));
20746
20849
  const handleAssign = (itemId, categoryId) => {
20747
20850
  if (submitted) return;
20748
- if (assignEvent) {
20749
- emit(`UI:${assignEvent}`, { itemId, categoryId });
20750
- }
20751
- if (!entityHasAssignments) {
20752
- setLocalAssignments((prev) => ({ ...prev, [itemId]: categoryId }));
20753
- }
20851
+ if (assignEvent) emit(`UI:${assignEvent}`, { itemId, categoryId });
20754
20852
  };
20755
20853
  const handleUnassign = (itemId) => {
20756
20854
  if (submitted) return;
20757
- if (!entityHasAssignments) {
20758
- setLocalAssignments((prev) => {
20759
- const next = { ...prev };
20760
- delete next[itemId];
20761
- return next;
20762
- });
20763
- }
20855
+ if (assignEvent) emit(`UI:${assignEvent}`, { itemId, categoryId: "" });
20764
20856
  };
20765
- const handleSubmit = useCallback(() => {
20766
- if (checkEvent) {
20767
- emit(`UI:${checkEvent}`, {});
20768
- }
20769
- if (!entityDrivesResult) {
20770
- setLocalSubmitted(true);
20771
- setLocalAttempts((a) => a + 1);
20772
- const correct = items.every((item) => assignments[item.id] === item.correctCategory);
20773
- if (correct) {
20774
- emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
20775
- }
20776
- }
20777
- }, [checkEvent, entityDrivesResult, items, assignments, attempts, completeEvent, emit]);
20778
- const handleReset = () => {
20779
- if (!entityDrivesResult) setLocalSubmitted(false);
20780
- if (attempts >= 2 && str(resolved?.hint)) {
20781
- setShowHint(true);
20857
+ const handleSubmit = () => {
20858
+ if (checkEvent) emit(`UI:${checkEvent}`, {});
20859
+ if (allAssigned && items.every((item) => assignments[item.id] === item.correctCategory)) {
20860
+ emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
20782
20861
  }
20783
20862
  };
20784
20863
  const handleFullReset = () => {
20785
- if (playAgainEvent) {
20786
- emit(`UI:${playAgainEvent}`, {});
20787
- }
20788
- setLocalAssignments({});
20789
- setLocalSubmitted(false);
20790
- setLocalAttempts(0);
20791
- setShowHint(false);
20864
+ if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
20792
20865
  };
20793
20866
  if (!resolved) return null;
20794
20867
  const theme = resolved.theme ?? void 0;
@@ -20827,17 +20900,17 @@ function ClassifierBoard({
20827
20900
  /* @__PURE__ */ jsx(Badge, { size: "sm", children: catItems.length })
20828
20901
  ] }),
20829
20902
  /* @__PURE__ */ jsx(HStack, { gap: "xs", className: "flex-wrap min-h-[2rem]", children: catItems.map((item) => {
20830
- const result = results.find((r) => r.item.id === item.id);
20903
+ const result2 = results.find((r) => r.item.id === item.id);
20831
20904
  return /* @__PURE__ */ jsxs(
20832
20905
  Badge,
20833
20906
  {
20834
20907
  size: "sm",
20835
- className: `cursor-pointer ${result ? result.correct ? "border-success bg-success/10" : "border-error bg-error/10" : ""}`,
20908
+ className: `cursor-pointer ${result2 ? result2.correct ? "border-success bg-success/10" : "border-error bg-error/10" : ""}`,
20836
20909
  onClick: () => handleUnassign(item.id),
20837
20910
  children: [
20838
20911
  item.iconUrl && /* @__PURE__ */ jsx("img", { src: item.iconUrl, alt: "", className: "w-3 h-3 object-contain inline-block" }),
20839
20912
  item.label,
20840
- result && /* @__PURE__ */ jsx(Icon, { icon: result.correct ? CheckCircle : XCircle, size: "xs", className: result.correct ? "text-success" : "text-error" })
20913
+ result2 && /* @__PURE__ */ jsx(Icon, { icon: result2.correct ? CheckCircle : XCircle, size: "xs", className: result2.correct ? "text-success" : "text-error" })
20841
20914
  ]
20842
20915
  },
20843
20916
  item.id
@@ -20866,10 +20939,10 @@ function ClassifierBoard({
20866
20939
  ] }) }),
20867
20940
  showHint && hint && /* @__PURE__ */ jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsx(Typography, { variant: "body", children: hint }) }),
20868
20941
  /* @__PURE__ */ jsxs(HStack, { gap: "sm", justify: "center", children: [
20869
- !submitted ? /* @__PURE__ */ jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: !allAssigned, children: [
20942
+ !submitted && /* @__PURE__ */ jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: !allAssigned, children: [
20870
20943
  /* @__PURE__ */ jsx(Icon, { icon: Send, size: "sm" }),
20871
20944
  t("classifier.check")
20872
- ] }) : !allCorrect ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleReset, children: t("classifier.tryAgain") }) : null,
20945
+ ] }),
20873
20946
  /* @__PURE__ */ jsxs(Button, { variant: "secondary", onClick: handleFullReset, children: [
20874
20947
  /* @__PURE__ */ jsx(Icon, { icon: RotateCcw, size: "sm" }),
20875
20948
  t("classifier.reset")
@@ -28898,7 +28971,7 @@ var init_MapView = __esm({
28898
28971
  shadowSize: [41, 41]
28899
28972
  });
28900
28973
  L.Marker.prototype.options.icon = defaultIcon;
28901
- const { useEffect: useEffect74, useRef: useRef68, useCallback: useCallback112, useState: useState107 } = React82__default;
28974
+ const { useEffect: useEffect74, useRef: useRef68, useCallback: useCallback111, useState: useState107 } = React82__default;
28902
28975
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
28903
28976
  const { useEventBus: useEventBus3 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
28904
28977
  function MapUpdater({ centerLat, centerLng, zoom }) {
@@ -28944,7 +29017,7 @@ var init_MapView = __esm({
28944
29017
  }) {
28945
29018
  const eventBus = useEventBus3();
28946
29019
  const [clickedPosition, setClickedPosition] = useState107(null);
28947
- const handleMapClick = useCallback112((lat, lng) => {
29020
+ const handleMapClick = useCallback111((lat, lng) => {
28948
29021
  if (showClickedPin) {
28949
29022
  setClickedPosition({ lat, lng });
28950
29023
  }
@@ -28953,7 +29026,7 @@ var init_MapView = __esm({
28953
29026
  eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
28954
29027
  }
28955
29028
  }, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
28956
- const handleMarkerClick = useCallback112((marker) => {
29029
+ const handleMarkerClick = useCallback111((marker) => {
28957
29030
  onMarkerClick?.(marker);
28958
29031
  if (markerClickEvent) {
28959
29032
  eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
@@ -38184,44 +38257,27 @@ function DebuggerBoard({
38184
38257
  const { t } = useTranslate();
38185
38258
  const resolved = boardEntity(entity);
38186
38259
  const [headerError, setHeaderError] = useState(false);
38187
- const [showHint, setShowHint] = useState(false);
38188
38260
  const lines = Array.isArray(resolved?.lines) ? resolved.lines : [];
38189
- const result = resolved?.result ?? null;
38261
+ const result = str(resolved?.result) || "none";
38190
38262
  const attempts = num(resolved?.attempts);
38191
- const submitted = result != null;
38263
+ const submitted = result === "win";
38264
+ const showHint = !submitted && attempts >= 2 && Boolean(str(resolved?.hint));
38192
38265
  const bugLines = lines.filter((l) => l.isBug);
38193
38266
  const flaggedLines = lines.filter((l) => l.isFlagged);
38194
38267
  const correctFlags = lines.filter((l) => l.isBug && l.isFlagged);
38195
38268
  const falseFlags = lines.filter((l) => !l.isBug && l.isFlagged);
38196
- const allCorrect = result === "win";
38269
+ const allCorrect = submitted;
38197
38270
  const toggleLine = (lineId) => {
38198
38271
  if (submitted) return;
38199
- if (toggleFlagEvent) {
38200
- emit(`UI:${toggleFlagEvent}`, { lineId });
38201
- }
38272
+ if (toggleFlagEvent) emit(`UI:${toggleFlagEvent}`, { lineId });
38202
38273
  };
38203
38274
  const handleSubmit = useCallback(() => {
38204
- if (checkEvent) {
38205
- emit(`UI:${checkEvent}`, {});
38206
- }
38275
+ if (checkEvent) emit(`UI:${checkEvent}`, {});
38207
38276
  const correct = correctFlags.length === bugLines.length && falseFlags.length === 0;
38208
- if (correct) {
38209
- emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
38210
- }
38277
+ if (correct) emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
38211
38278
  }, [checkEvent, correctFlags.length, bugLines.length, falseFlags.length, attempts, completeEvent, emit]);
38212
38279
  const handleReset = () => {
38213
- if (playAgainEvent) {
38214
- emit(`UI:${playAgainEvent}`, {});
38215
- }
38216
- if (attempts >= 2 && str(resolved?.hint)) {
38217
- setShowHint(true);
38218
- }
38219
- };
38220
- const handleFullReset = () => {
38221
- if (playAgainEvent) {
38222
- emit(`UI:${playAgainEvent}`, {});
38223
- }
38224
- setShowHint(false);
38280
+ if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
38225
38281
  };
38226
38282
  if (!resolved) return null;
38227
38283
  const theme = resolved.theme ?? void 0;
@@ -38245,7 +38301,7 @@ function DebuggerBoard({
38245
38301
  /* @__PURE__ */ jsx(Typography, { variant: "h4", weight: "bold", children: str(resolved.title) })
38246
38302
  ] }),
38247
38303
  /* @__PURE__ */ jsx(Typography, { variant: "body", children: str(resolved.description) }),
38248
- /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-muted-foreground", children: t("debugger.findBugs", { count: String(num(resolved.bugCount)) }) })
38304
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-muted-foreground", children: t("debugger.findBugs", { count: String(lines.filter((l) => l.isBug).length) }) })
38249
38305
  ] }) }),
38250
38306
  /* @__PURE__ */ jsx(Card, { className: "p-0 overflow-hidden", children: /* @__PURE__ */ jsx(VStack, { gap: "none", children: lines.map((line, i) => {
38251
38307
  const isFlagged = !!line.isFlagged;
@@ -38295,11 +38351,11 @@ function DebuggerBoard({
38295
38351
  ] }) }),
38296
38352
  showHint && hint && /* @__PURE__ */ jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsx(Typography, { variant: "body", children: hint }) }),
38297
38353
  /* @__PURE__ */ jsxs(HStack, { gap: "sm", justify: "center", children: [
38298
- !submitted ? /* @__PURE__ */ jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: flaggedLines.length === 0, children: [
38354
+ !submitted && /* @__PURE__ */ jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: flaggedLines.length === 0, children: [
38299
38355
  /* @__PURE__ */ jsx(Icon, { icon: Send, size: "sm" }),
38300
38356
  t("debugger.submit")
38301
- ] }) : !allCorrect ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleReset, children: t("debugger.tryAgain") }) : null,
38302
- /* @__PURE__ */ jsxs(Button, { variant: "secondary", onClick: handleFullReset, children: [
38357
+ ] }),
38358
+ /* @__PURE__ */ jsxs(Button, { variant: "secondary", onClick: handleReset, children: [
38303
38359
  /* @__PURE__ */ jsx(Icon, { icon: RotateCcw, size: "sm" }),
38304
38360
  t("debugger.reset")
38305
38361
  ] })
@@ -39300,39 +39356,40 @@ function EventHandlerBoard({
39300
39356
  stepDurationMs = 800,
39301
39357
  playEvent,
39302
39358
  completeEvent,
39359
+ editRuleEvent,
39360
+ playAgainEvent,
39303
39361
  className
39304
39362
  }) {
39305
39363
  const { emit } = useEventBus();
39306
39364
  const { t } = useTranslate();
39307
39365
  const resolved = boardEntity(entity);
39308
- const entityObjects = rows(resolved?.objects);
39309
- const [objects, setObjects] = useState(() => [...entityObjects]);
39366
+ const objects = rows(resolved?.objects);
39367
+ const entityResult = str(resolved?.result) || "none";
39368
+ const isSuccess = entityResult === "win";
39369
+ const attempts = typeof resolved?.attempts === "number" ? resolved.attempts : 0;
39310
39370
  const [selectedObjectId, setSelectedObjectId] = useState(
39311
- entityObjects[0] ? objId(entityObjects[0]) : null
39371
+ objects[0] ? objId(objects[0]) : null
39312
39372
  );
39313
39373
  const [headerError, setHeaderError] = useState(false);
39314
- const [playState, setPlayState] = useState("editing");
39374
+ const [isPlaying, setIsPlaying] = useState(false);
39315
39375
  const [eventLog, setEventLog] = useState([]);
39316
- const [attempts, setAttempts] = useState(0);
39317
39376
  const timerRef = useRef(null);
39318
39377
  const logIdCounter = useRef(0);
39319
39378
  useEffect(() => () => {
39320
39379
  if (timerRef.current) clearTimeout(timerRef.current);
39321
39380
  }, []);
39322
- const selectedObject = objects.find((o) => objId(o) === selectedObjectId) || null;
39381
+ const selectedObject = objects.find((o) => objId(o) === selectedObjectId) ?? null;
39323
39382
  const handleRulesChange = useCallback((objectId, rules) => {
39324
- setObjects((prev) => prev.map(
39325
- (o) => objId(o) === objectId ? { ...o, rules } : o
39326
- ));
39327
- }, []);
39383
+ if (editRuleEvent) emit(`UI:${editRuleEvent}`, { objectId, rules });
39384
+ }, [editRuleEvent, emit]);
39328
39385
  const addLogEntry = useCallback((icon, message, status = "done") => {
39329
39386
  const id = `log-${logIdCounter.current++}`;
39330
39387
  setEventLog((prev) => [...prev, { id, timestamp: Date.now(), icon, message, status }]);
39331
39388
  }, []);
39332
39389
  const handlePlay = useCallback(() => {
39333
- if (playState !== "editing") return;
39390
+ if (isPlaying || isSuccess) return;
39334
39391
  if (playEvent) emit(`UI:${playEvent}`, {});
39335
- setPlayState("playing");
39392
+ setIsPlaying(true);
39336
39393
  setEventLog([]);
39337
39394
  const allRules = [];
39338
39395
  objects.forEach((obj) => {
@@ -39348,15 +39405,8 @@ function EventHandlerBoard({
39348
39405
  let goalReached = false;
39349
39406
  const processNext = () => {
39350
39407
  if (eventQueue.length === 0 || stepIdx > 20) {
39351
- if (goalReached) {
39352
- setPlayState("success");
39353
- if (completeEvent) {
39354
- emit(`UI:${completeEvent}`, { success: true });
39355
- }
39356
- } else {
39357
- setAttempts((prev) => prev + 1);
39358
- setPlayState("fail");
39359
- }
39408
+ setIsPlaying(false);
39409
+ if (goalReached && completeEvent) emit(`UI:${completeEvent}`, { success: true });
39360
39410
  return;
39361
39411
  }
39362
39412
  const currentEvent = eventQueue.shift();
@@ -39387,21 +39437,19 @@ function EventHandlerBoard({
39387
39437
  addLogEntry("\u{1F3AC}", t("eventHandler.simulationStarted", { events: triggers.join(", ") }), "active");
39388
39438
  }
39389
39439
  timerRef.current = setTimeout(processNext, stepDurationMs);
39390
- }, [playState, objects, resolved, stepDurationMs, playEvent, completeEvent, emit, addLogEntry, t]);
39440
+ }, [isPlaying, isSuccess, objects, resolved, stepDurationMs, playEvent, completeEvent, emit, addLogEntry, t]);
39391
39441
  const handleTryAgain = useCallback(() => {
39392
39442
  if (timerRef.current) clearTimeout(timerRef.current);
39393
- setPlayState("editing");
39443
+ setIsPlaying(false);
39394
39444
  setEventLog([]);
39395
39445
  }, []);
39396
39446
  const handleReset = useCallback(() => {
39397
39447
  if (timerRef.current) clearTimeout(timerRef.current);
39398
- const resetObjects = rows(resolved?.objects);
39399
- setObjects([...resetObjects]);
39400
- setPlayState("editing");
39448
+ setIsPlaying(false);
39401
39449
  setEventLog([]);
39402
- setSelectedObjectId(resetObjects[0] ? objId(resetObjects[0]) : null);
39403
- setAttempts(0);
39404
- }, [resolved?.objects]);
39450
+ setSelectedObjectId(objects[0] ? objId(objects[0]) : null);
39451
+ if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
39452
+ }, [objects, playAgainEvent, emit]);
39405
39453
  if (!resolved) return null;
39406
39454
  const objectViewers = objects.map((obj) => {
39407
39455
  const states = objStates(obj);
@@ -39470,12 +39518,12 @@ function EventHandlerBoard({
39470
39518
  {
39471
39519
  object: selectedObject,
39472
39520
  onRulesChange: handleRulesChange,
39473
- disabled: playState !== "editing"
39521
+ disabled: isPlaying
39474
39522
  }
39475
39523
  ),
39476
39524
  eventLog.length > 0 && /* @__PURE__ */ jsx(EventLog, { entries: eventLog }),
39477
- playState === "success" && /* @__PURE__ */ jsx(Box, { className: "p-4 rounded-container bg-success/20 border border-success text-center", children: /* @__PURE__ */ jsx(Typography, { variant: "h5", className: "text-success", children: str(resolved.successMessage) || t("eventHandler.chainComplete") }) }),
39478
- playState === "fail" && /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
39525
+ isSuccess && /* @__PURE__ */ jsx(Box, { className: "p-4 rounded-container bg-success/20 border border-success text-center", children: /* @__PURE__ */ jsx(Typography, { variant: "h5", className: "text-success", children: str(resolved.successMessage) || t("eventHandler.chainComplete") }) }),
39526
+ !isPlaying && !isSuccess && attempts > 0 && /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
39479
39527
  /* @__PURE__ */ jsx(Box, { className: "p-4 rounded-container bg-warning/10 border border-warning/30 text-center", children: /* @__PURE__ */ jsx(Typography, { variant: "body1", className: "text-foreground font-medium", children: t(encourageKey) }) }),
39480
39528
  showHint && /* @__PURE__ */ jsx(Box, { className: "p-3 rounded-container bg-accent/10 border border-accent/30", children: /* @__PURE__ */ jsxs(HStack, { className: "items-start", gap: "xs", children: [
39481
39529
  /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-accent font-bold shrink-0", children: "\u{1F4A1} " + t("game.hint") + ":" }),
@@ -39483,12 +39531,12 @@ function EventHandlerBoard({
39483
39531
  ] }) })
39484
39532
  ] }),
39485
39533
  /* @__PURE__ */ jsxs(HStack, { gap: "sm", children: [
39486
- playState === "fail" ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleTryAgain, children: "\u{1F504} " + t("puzzle.tryAgainButton") }) : /* @__PURE__ */ jsx(
39534
+ !isPlaying && !isSuccess && attempts > 0 ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleTryAgain, children: "\u{1F504} " + t("puzzle.tryAgainButton") }) : /* @__PURE__ */ jsx(
39487
39535
  Button,
39488
39536
  {
39489
39537
  variant: "primary",
39490
39538
  onClick: handlePlay,
39491
- disabled: playState !== "editing",
39539
+ disabled: isPlaying || isSuccess,
39492
39540
  children: "\u25B6 " + t("game.play")
39493
39541
  }
39494
39542
  ),
@@ -42041,21 +42089,6 @@ var init_ModalSlot = __esm({
42041
42089
  ModalSlot.displayName = "ModalSlot";
42042
42090
  }
42043
42091
  });
42044
- function getOpponentAction(strategy, actions, history) {
42045
- const actionIds = actions.map((a) => a.id);
42046
- switch (strategy) {
42047
- case "always-cooperate":
42048
- return actionIds[0];
42049
- case "always-defect":
42050
- return actionIds[actionIds.length - 1];
42051
- case "tit-for-tat":
42052
- if (history.length === 0) return actionIds[0];
42053
- return history[history.length - 1].playerAction;
42054
- case "random":
42055
- default:
42056
- return actionIds[Math.floor(Math.random() * actionIds.length)];
42057
- }
42058
- }
42059
42092
  function NegotiatorBoard({
42060
42093
  entity,
42061
42094
  completeEvent = "PUZZLE_COMPLETE",
@@ -42077,28 +42110,35 @@ function NegotiatorBoard({
42077
42110
  const playerTotal = num(resolved?.score);
42078
42111
  const isComplete = result !== "none" || totalRounds > 0 && currentRound >= totalRounds;
42079
42112
  const won = result === "win";
42080
- const opponentTotal = history.reduce((s, r) => s + r.opponentPayoff, 0);
42113
+ const lastPlayerAction = str(resolved?.lastPlayerAction);
42114
+ const lastOpponentAction = str(resolved?.lastOpponentAction);
42115
+ const lastPayoff = num(resolved?.lastPayoff);
42081
42116
  const actions = Array.isArray(resolved?.actions) ? resolved.actions : [];
42082
42117
  const payoffMatrix = Array.isArray(resolved?.payoffMatrix) ? resolved.payoffMatrix : [];
42118
+ const prevRoundRef = useRef(currentRound);
42119
+ useEffect(() => {
42120
+ if (currentRound > prevRoundRef.current && lastPlayerAction) {
42121
+ const opponentPayoffEntry = payoffMatrix.find(
42122
+ (p2) => p2.playerAction === lastPlayerAction && p2.opponentAction === lastOpponentAction
42123
+ );
42124
+ setHistory((prev) => [
42125
+ ...prev,
42126
+ {
42127
+ round: currentRound,
42128
+ playerAction: lastPlayerAction,
42129
+ opponentAction: lastOpponentAction,
42130
+ playerPayoff: lastPayoff,
42131
+ opponentPayoff: opponentPayoffEntry?.opponentPayoff ?? 0
42132
+ }
42133
+ ]);
42134
+ }
42135
+ prevRoundRef.current = currentRound;
42136
+ }, [currentRound, lastPlayerAction, lastOpponentAction, lastPayoff, payoffMatrix]);
42137
+ const opponentTotal = useMemo(() => history.reduce((s, r) => s + r.opponentPayoff, 0), [history]);
42083
42138
  const handleAction = useCallback((actionId) => {
42084
42139
  if (isComplete) return;
42085
- const opponentAction = getOpponentAction(str(resolved?.opponentStrategy) || "random", actions, history);
42086
- const payoff = payoffMatrix.find(
42087
- (p2) => p2.playerAction === actionId && p2.opponentAction === opponentAction
42088
- );
42089
- const playerPayoff = payoff?.playerPayoff ?? 0;
42090
- setHistory((prev) => [
42091
- ...prev,
42092
- {
42093
- round: prev.length + 1,
42094
- playerAction: actionId,
42095
- opponentAction,
42096
- playerPayoff,
42097
- opponentPayoff: payoff?.opponentPayoff ?? 0
42098
- }
42099
- ]);
42100
42140
  if (playRoundEvent) {
42101
- emit(`UI:${playRoundEvent}`, { playerAction: actionId, payoff: playerPayoff });
42141
+ emit(`UI:${playRoundEvent}`, { playerAction: actionId, payoff: 0 });
42102
42142
  }
42103
42143
  if (totalRounds > 0 && currentRound + 1 >= totalRounds) {
42104
42144
  if (finishEvent) {
@@ -42108,10 +42148,11 @@ function NegotiatorBoard({
42108
42148
  setShowHint(true);
42109
42149
  }
42110
42150
  }
42111
- }, [isComplete, resolved, totalRounds, currentRound, actions, payoffMatrix, history, playRoundEvent, finishEvent, emit]);
42151
+ }, [isComplete, resolved, totalRounds, currentRound, playRoundEvent, finishEvent, emit]);
42112
42152
  const handleReset = useCallback(() => {
42113
42153
  setHistory([]);
42114
42154
  setShowHint(false);
42155
+ prevRoundRef.current = 0;
42115
42156
  if (playAgainEvent) {
42116
42157
  emit(`UI:${playAgainEvent}`, {});
42117
42158
  }
@@ -44466,17 +44507,24 @@ function SequencerBoard({
44466
44507
  const { emit } = useEventBus();
44467
44508
  const { t } = useTranslate();
44468
44509
  const resolved = boardEntity(entity);
44469
- const maxSlots = num(resolved?.maxSlots);
44510
+ const maxSlots = num(resolved?.maxSlots) || 3;
44470
44511
  const solutions = Array.isArray(resolved?.solutions) ? resolved.solutions : [];
44471
44512
  const availableActions = Array.isArray(resolved?.availableActions) ? resolved.availableActions : [];
44472
44513
  const allowDuplicates = resolved?.allowDuplicates !== false;
44514
+ const entitySlots = Array.isArray(resolved?.slots) ? resolved.slots : [];
44515
+ const entityResult = str(resolved?.result);
44516
+ const entityAttempts = num(resolved?.attempts);
44517
+ const entityCurrentStep = typeof resolved?.currentStep === "number" ? resolved.currentStep : -1;
44518
+ const slots = Array.from({ length: maxSlots }, (_, i) => {
44519
+ const entitySlot = entitySlots.find((s) => s.index === i);
44520
+ if (!entitySlot?.placedActionId) return void 0;
44521
+ return availableActions.find((a) => a.id === entitySlot.placedActionId);
44522
+ });
44523
+ const isSuccess = entityResult === "win";
44524
+ const attempts = entityAttempts;
44525
+ const isPlayingBack = entityCurrentStep >= 0 && !isSuccess;
44526
+ const currentStep = entityCurrentStep;
44473
44527
  const [headerError, setHeaderError] = useState(false);
44474
- const [slots, setSlots] = useState(
44475
- () => Array.from({ length: maxSlots }, () => void 0)
44476
- );
44477
- const [playState, setPlayState] = useState("idle");
44478
- const [currentStep, setCurrentStep] = useState(-1);
44479
- const [attempts, setAttempts] = useState(0);
44480
44528
  const [slotFeedback, setSlotFeedback] = useState(
44481
44529
  () => Array.from({ length: maxSlots }, () => null)
44482
44530
  );
@@ -44485,11 +44533,6 @@ function SequencerBoard({
44485
44533
  if (timerRef.current) clearTimeout(timerRef.current);
44486
44534
  }, []);
44487
44535
  const handleSlotDrop = useCallback((index, item) => {
44488
- setSlots((prev) => {
44489
- const next = [...prev];
44490
- next[index] = item;
44491
- return next;
44492
- });
44493
44536
  setSlotFeedback((prev) => {
44494
44537
  const next = [...prev];
44495
44538
  next[index] = null;
@@ -44499,11 +44542,6 @@ function SequencerBoard({
44499
44542
  if (placeEvent) emit(`UI:${placeEvent}`, { slotIndex: index, actionId: item.id });
44500
44543
  }, [emit, placeEvent]);
44501
44544
  const handleSlotRemove = useCallback((index) => {
44502
- setSlots((prev) => {
44503
- const next = [...prev];
44504
- next[index] = void 0;
44505
- return next;
44506
- });
44507
44545
  setSlotFeedback((prev) => {
44508
44546
  const next = [...prev];
44509
44547
  next[index] = null;
@@ -44514,48 +44552,34 @@ function SequencerBoard({
44514
44552
  }, [emit, removeEvent]);
44515
44553
  const handleReset = useCallback(() => {
44516
44554
  if (timerRef.current) clearTimeout(timerRef.current);
44517
- setSlots(Array.from({ length: maxSlots }, () => void 0));
44518
- setPlayState("idle");
44519
- setCurrentStep(-1);
44520
- setAttempts(0);
44521
44555
  setSlotFeedback(Array.from({ length: maxSlots }, () => null));
44522
44556
  if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
44523
44557
  }, [maxSlots, playAgainEvent, emit]);
44524
44558
  const filledSlots = slots.filter((s) => !!s);
44525
- const canPlay = filledSlots.length > 0 && playState === "idle";
44559
+ const canPlay = filledSlots.length > 0 && !isPlayingBack && !isSuccess;
44526
44560
  const handlePlay = useCallback(() => {
44527
44561
  if (!canPlay) return;
44528
44562
  setSlotFeedback(Array.from({ length: maxSlots }, () => null));
44529
44563
  emit("UI:PLAY_SOUND", { key: "confirm" });
44530
44564
  const sequence = slots.map((s) => s?.id || "");
44531
- if (playEvent) {
44532
- emit(`UI:${playEvent}`, { sequence });
44533
- }
44534
- setPlayState("playing");
44535
- setCurrentStep(0);
44565
+ const playerIds = slots.filter(Boolean).map((s) => s?.id || "");
44566
+ if (playEvent) emit(`UI:${playEvent}`, { sequence });
44536
44567
  let step = 0;
44537
44568
  const advance = () => {
44538
44569
  step++;
44570
+ emit("UI:STEP", { step });
44539
44571
  if (step >= maxSlots) {
44572
+ if (checkEvent) emit(`UI:${checkEvent}`, { sequence: playerIds });
44540
44573
  const playerSeq = slots.map((s) => s?.id);
44541
- const playerIds = slots.filter(Boolean).map((s) => s?.id || "");
44542
44574
  const success = solutions.some(
44543
44575
  (sol) => sol.length === playerIds.length && sol.every((id, i) => id === playerIds[i])
44544
44576
  );
44545
- if (checkEvent) emit(`UI:${checkEvent}`, { sequence: playerIds });
44546
44577
  if (success) {
44547
- setPlayState("success");
44548
- setCurrentStep(-1);
44549
44578
  emit("UI:PLAY_SOUND", { key: "levelComplete" });
44550
- if (completeEvent) {
44551
- emit(`UI:${completeEvent}`, { success: true, sequence: playerIds });
44552
- }
44579
+ if (completeEvent) emit(`UI:${completeEvent}`, { success: true, sequence: playerIds });
44553
44580
  } else {
44554
- setAttempts((prev) => prev + 1);
44555
44581
  const feedback = computeSlotFeedback(playerSeq, solutions);
44556
44582
  setSlotFeedback(feedback);
44557
- setPlayState("idle");
44558
- setCurrentStep(-1);
44559
44583
  emit("UI:PLAY_SOUND", { key: "fail" });
44560
44584
  const correctCount2 = feedback.filter((f3) => f3 === "correct").length;
44561
44585
  for (let ci = 0; ci < correctCount2; ci++) {
@@ -44565,7 +44589,6 @@ function SequencerBoard({
44565
44589
  }
44566
44590
  }
44567
44591
  } else {
44568
- setCurrentStep(step);
44569
44592
  timerRef.current = setTimeout(advance, stepDurationMs);
44570
44593
  }
44571
44594
  };
@@ -44615,7 +44638,7 @@ function SequencerBoard({
44615
44638
  /* @__PURE__ */ jsxs(VStack, { gap: "xs", children: [
44616
44639
  /* @__PURE__ */ jsxs(HStack, { className: "items-center justify-between", children: [
44617
44640
  /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("sequencer.yourSequence") + ":" }),
44618
- hasFeedback && playState === "idle" && /* @__PURE__ */ jsxs(Typography, { variant: "caption", className: "text-muted-foreground", children: [
44641
+ hasFeedback && !isPlayingBack && !isSuccess && /* @__PURE__ */ jsxs(Typography, { variant: "caption", className: "text-muted-foreground", children: [
44619
44642
  `${correctCount}/${maxSlots} `,
44620
44643
  "\u2705"
44621
44644
  ] })
@@ -44627,7 +44650,7 @@ function SequencerBoard({
44627
44650
  maxSlots,
44628
44651
  onSlotDrop: handleSlotDrop,
44629
44652
  onSlotRemove: handleSlotRemove,
44630
- playing: playState === "playing",
44653
+ playing: isPlayingBack,
44631
44654
  currentStep,
44632
44655
  categoryColors,
44633
44656
  slotFeedback,
@@ -44635,7 +44658,7 @@ function SequencerBoard({
44635
44658
  }
44636
44659
  )
44637
44660
  ] }),
44638
- playState !== "playing" && /* @__PURE__ */ jsx(
44661
+ !isPlayingBack && /* @__PURE__ */ jsx(
44639
44662
  ActionPalette,
44640
44663
  {
44641
44664
  actions: availableActions,
@@ -44645,8 +44668,8 @@ function SequencerBoard({
44645
44668
  label: t("sequencer.dragActions")
44646
44669
  }
44647
44670
  ),
44648
- hasFeedback && playState === "idle" && attempts > 0 && /* @__PURE__ */ jsx(Box, { className: "p-3 rounded-container bg-warning/10 border border-warning/30 text-center", children: /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-foreground", children: t(encourageKey) }) }),
44649
- playState === "success" && /* @__PURE__ */ jsx(Box, { className: "p-4 rounded-container bg-success/20 border border-success text-center", children: /* @__PURE__ */ jsx(Typography, { variant: "h5", className: "text-success", children: str(resolved.successMessage) || t("sequencer.levelComplete") }) }),
44671
+ hasFeedback && !isPlayingBack && !isSuccess && attempts > 0 && /* @__PURE__ */ jsx(Box, { className: "p-3 rounded-container bg-warning/10 border border-warning/30 text-center", children: /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-foreground", children: t(encourageKey) }) }),
44672
+ isSuccess && /* @__PURE__ */ jsx(Box, { className: "p-4 rounded-container bg-success/20 border border-success text-center", children: /* @__PURE__ */ jsx(Typography, { variant: "h5", className: "text-success", children: str(resolved.successMessage) || t("sequencer.levelComplete") }) }),
44650
44673
  /* @__PURE__ */ jsxs(HStack, { gap: "sm", children: [
44651
44674
  /* @__PURE__ */ jsx(
44652
44675
  Button,
@@ -45696,6 +45719,9 @@ function StateArchitectBoard({
45696
45719
  stepDurationMs = 600,
45697
45720
  testEvent,
45698
45721
  completeEvent,
45722
+ addTransitionEvent,
45723
+ removeTransitionEvent,
45724
+ playAgainEvent,
45699
45725
  className
45700
45726
  }) {
45701
45727
  const { emit } = useEventBus();
@@ -45708,14 +45734,16 @@ function StateArchitectBoard({
45708
45734
  const testCases = Array.isArray(resolved?.testCases) ? resolved.testCases : [];
45709
45735
  const entityTransitions = Array.isArray(resolved?.transitions) ? resolved.transitions : [];
45710
45736
  const entityVariables = rows(resolved?.variables);
45711
- const [transitions, setTransitions] = useState(entityTransitions);
45737
+ const transitions = entityTransitions;
45738
+ const attempts = typeof resolved?.attempts === "number" ? resolved.attempts : 0;
45739
+ const entityResult = str(resolved?.result) || "none";
45740
+ const isSuccess = entityResult === "win";
45741
+ const [isTesting, setIsTesting] = useState(false);
45712
45742
  const [headerError, setHeaderError] = useState(false);
45713
- const [playState, setPlayState] = useState("editing");
45714
45743
  const [currentState, setCurrentState] = useState(initialState);
45715
45744
  const [selectedState, setSelectedState] = useState(null);
45716
45745
  const [testResults, setTestResults] = useState([]);
45717
45746
  const [variables, setVariables] = useState(() => [...entityVariables]);
45718
- const [attempts, setAttempts] = useState(0);
45719
45747
  const timerRef = useRef(null);
45720
45748
  const [addingFrom, setAddingFrom] = useState(null);
45721
45749
  useEffect(() => () => {
@@ -45725,7 +45753,7 @@ function StateArchitectBoard({
45725
45753
  const GRAPH_H = 400;
45726
45754
  const positions = useMemo(() => layoutStates(entityStates, GRAPH_W, GRAPH_H), [entityStates]);
45727
45755
  const handleStateClick = useCallback((state) => {
45728
- if (playState !== "editing") return;
45756
+ if (isTesting) return;
45729
45757
  if (addingFrom) {
45730
45758
  if (addingFrom !== state) {
45731
45759
  const event = availableEvents[0] || "EVENT";
@@ -45735,20 +45763,20 @@ function StateArchitectBoard({
45735
45763
  to: state,
45736
45764
  event
45737
45765
  };
45738
- setTransitions((prev) => [...prev, newTrans]);
45766
+ if (addTransitionEvent) emit(`UI:${addTransitionEvent}`, { id: newTrans.id, from: newTrans.from, to: newTrans.to, event: newTrans.event });
45739
45767
  }
45740
45768
  setAddingFrom(null);
45741
45769
  } else {
45742
45770
  setSelectedState(state);
45743
45771
  }
45744
- }, [playState, addingFrom, availableEvents]);
45772
+ }, [isTesting, addingFrom, availableEvents, addTransitionEvent, emit]);
45745
45773
  const handleStartAddTransition = useCallback(() => {
45746
45774
  if (!selectedState) return;
45747
45775
  setAddingFrom(selectedState);
45748
45776
  }, [selectedState]);
45749
45777
  const handleRemoveTransition = useCallback((transId) => {
45750
- setTransitions((prev) => prev.filter((t2) => t2.id !== transId));
45751
- }, []);
45778
+ if (removeTransitionEvent) emit(`UI:${removeTransitionEvent}`, { id: transId });
45779
+ }, [removeTransitionEvent, emit]);
45752
45780
  const machine = useMemo(() => ({
45753
45781
  name: entityName,
45754
45782
  description: str(resolved?.description),
@@ -45762,16 +45790,16 @@ function StateArchitectBoard({
45762
45790
  }))
45763
45791
  }), [entityName, resolved, entityStates, currentState, transitions]);
45764
45792
  const handleTest = useCallback(() => {
45765
- if (playState !== "editing") return;
45793
+ if (isTesting) return;
45766
45794
  if (testEvent) emit(`UI:${testEvent}`, {});
45767
- setPlayState("testing");
45795
+ setIsTesting(true);
45768
45796
  setTestResults([]);
45769
45797
  const results = [];
45770
45798
  let testIdx = 0;
45771
45799
  const runNextTest = () => {
45772
45800
  if (testIdx >= testCases.length) {
45773
45801
  const allPassed = results.every((r) => r.passed);
45774
- setPlayState(allPassed ? "success" : "fail");
45802
+ setIsTesting(false);
45775
45803
  setTestResults(results);
45776
45804
  if (allPassed && completeEvent) {
45777
45805
  emit(`UI:${completeEvent}`, {
@@ -45779,9 +45807,6 @@ function StateArchitectBoard({
45779
45807
  passedTests: results.filter((r) => r.passed).length
45780
45808
  });
45781
45809
  }
45782
- if (!allPassed) {
45783
- setAttempts((prev) => prev + 1);
45784
- }
45785
45810
  return;
45786
45811
  }
45787
45812
  const testCase = testCases[testIdx];
@@ -45804,24 +45829,23 @@ function StateArchitectBoard({
45804
45829
  timerRef.current = setTimeout(runNextTest, stepDurationMs);
45805
45830
  };
45806
45831
  timerRef.current = setTimeout(runNextTest, stepDurationMs);
45807
- }, [playState, transitions, testCases, initialState, stepDurationMs, testEvent, completeEvent, emit]);
45832
+ }, [isTesting, transitions, testCases, initialState, stepDurationMs, testEvent, completeEvent, emit]);
45808
45833
  const handleTryAgain = useCallback(() => {
45809
45834
  if (timerRef.current) clearTimeout(timerRef.current);
45810
- setPlayState("editing");
45835
+ setIsTesting(false);
45811
45836
  setCurrentState(initialState);
45812
45837
  setTestResults([]);
45813
45838
  }, [initialState]);
45814
45839
  const handleReset = useCallback(() => {
45815
45840
  if (timerRef.current) clearTimeout(timerRef.current);
45816
- setTransitions(entityTransitions);
45817
- setPlayState("editing");
45841
+ setIsTesting(false);
45818
45842
  setCurrentState(initialState);
45819
45843
  setTestResults([]);
45820
45844
  setVariables([...entityVariables]);
45821
45845
  setSelectedState(null);
45822
45846
  setAddingFrom(null);
45823
- setAttempts(0);
45824
- }, [entityTransitions, initialState, entityVariables]);
45847
+ if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
45848
+ }, [initialState, entityVariables, playAgainEvent, emit]);
45825
45849
  const codeData = useMemo(() => ({
45826
45850
  name: entityName,
45827
45851
  states: entityStates,
@@ -45917,7 +45941,7 @@ function StateArchitectBoard({
45917
45941
  ]
45918
45942
  }
45919
45943
  ),
45920
- playState === "editing" && /* @__PURE__ */ jsx(HStack, { gap: "sm", children: /* @__PURE__ */ jsx(
45944
+ !isTesting && /* @__PURE__ */ jsx(HStack, { gap: "sm", children: /* @__PURE__ */ jsx(
45921
45945
  Button,
45922
45946
  {
45923
45947
  variant: "ghost",
@@ -45939,7 +45963,7 @@ function StateArchitectBoard({
45939
45963
  t2.guardHint,
45940
45964
  ")"
45941
45965
  ] }),
45942
- playState === "editing" && /* @__PURE__ */ jsx(
45966
+ !isTesting && /* @__PURE__ */ jsx(
45943
45967
  Button,
45944
45968
  {
45945
45969
  variant: "ghost",
@@ -45971,21 +45995,21 @@ function StateArchitectBoard({
45971
45995
  resolved.showCodeView !== false && /* @__PURE__ */ jsx(StateJsonView, { data: codeData, label: "View Code" })
45972
45996
  ] })
45973
45997
  ] }),
45974
- playState === "success" && /* @__PURE__ */ jsx(Box, { className: "p-4 rounded-container bg-success/20 border border-success text-center", children: /* @__PURE__ */ jsx(Typography, { variant: "h5", className: "text-success", children: str(resolved.successMessage) || t("stateArchitect.allPassed") }) }),
45975
- playState === "fail" && /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
45998
+ isSuccess && /* @__PURE__ */ jsx(Box, { className: "p-4 rounded-container bg-success/20 border border-success text-center", children: /* @__PURE__ */ jsx(Typography, { variant: "h5", className: "text-success", children: str(resolved.successMessage) || t("stateArchitect.allPassed") }) }),
45999
+ !isTesting && !isSuccess && testResults.some((r) => !r.passed) && /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
45976
46000
  /* @__PURE__ */ jsx(Box, { className: "p-4 rounded-container bg-warning/10 border border-warning/30 text-center", children: /* @__PURE__ */ jsx(Typography, { variant: "body1", className: "text-foreground font-medium", children: t(ENCOURAGEMENT_KEYS3[Math.min(attempts - 1, ENCOURAGEMENT_KEYS3.length - 1)] ?? ENCOURAGEMENT_KEYS3[0]) }) }),
45977
- attempts >= 3 && hint && /* @__PURE__ */ jsx(Box, { className: "p-3 rounded-container bg-accent/10 border border-accent/30", children: /* @__PURE__ */ jsxs(HStack, { className: "items-start", gap: "xs", children: [
46001
+ !isSuccess && attempts >= 2 && Boolean(str(resolved?.hint)) && /* @__PURE__ */ jsx(Box, { className: "p-3 rounded-container bg-accent/10 border border-accent/30", children: /* @__PURE__ */ jsxs(HStack, { className: "items-start", gap: "xs", children: [
45978
46002
  /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-accent font-bold shrink-0", children: "\u{1F4A1} " + t("game.hint") + ":" }),
45979
46003
  /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-foreground", children: hint })
45980
46004
  ] }) })
45981
46005
  ] }),
45982
46006
  /* @__PURE__ */ jsxs(HStack, { gap: "sm", children: [
45983
- playState === "fail" ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleTryAgain, children: "\u{1F504} " + t("puzzle.tryAgainButton") }) : /* @__PURE__ */ jsx(
46007
+ !isTesting && !isSuccess && testResults.some((r) => !r.passed) ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleTryAgain, children: "\u{1F504} " + t("puzzle.tryAgainButton") }) : /* @__PURE__ */ jsx(
45984
46008
  Button,
45985
46009
  {
45986
46010
  variant: "primary",
45987
46011
  onClick: handleTest,
45988
- disabled: playState !== "editing",
46012
+ disabled: isTesting,
45989
46013
  children: "\u25B6 " + t("game.runTests")
45990
46014
  }
45991
46015
  ),
@@ -46689,17 +46713,12 @@ var init_UncontrolledBattleBoard = __esm({
46689
46713
  }
46690
46714
  });
46691
46715
  function heroPosition(h) {
46716
+ if ("position" in h && h.position != null) return h.position;
46692
46717
  return vec2(h.position);
46693
46718
  }
46694
- function heroOwner(h) {
46695
- return str(h.owner);
46696
- }
46697
46719
  function heroMovement(h) {
46698
46720
  return num(h.movement);
46699
46721
  }
46700
- function hexPassable(h) {
46701
- return h.passable !== false;
46702
- }
46703
46722
  function defaultIsInRange(from, to, range) {
46704
46723
  return Math.abs(from.x - to.x) + Math.abs(from.y - to.y) <= range;
46705
46724
  }
@@ -46735,38 +46754,41 @@ function WorldMapBoard({
46735
46754
  }) {
46736
46755
  const eventBus = useEventBus();
46737
46756
  const resolved = boardEntity(entity);
46738
- const hexes = rows(resolved?.hexes);
46739
- const heroes = rows(resolved?.heroes);
46757
+ const entityUnits = rows(resolved?.units);
46758
+ const entityTiles = rows(resolved?.tiles);
46740
46759
  const features = propFeatures ?? (Array.isArray(resolved?.features) ? resolved.features : []);
46741
46760
  const selectedHeroId = resolved?.selectedHeroId ?? null;
46742
46761
  const assetManifest = propAssetManifest ?? resolved?.assetManifest;
46743
46762
  const backgroundImage = resolved?.backgroundImage;
46744
46763
  const [hoveredTile, setHoveredTile] = useState(null);
46745
- const selectedHero = useMemo(
46746
- () => heroes.find((h) => str(h.id) === selectedHeroId) ?? null,
46747
- [heroes, selectedHeroId]
46748
- );
46749
46764
  const derivedTiles = useMemo(
46750
- () => hexes.map((hex) => ({
46751
- x: num(hex.x),
46752
- y: num(hex.y),
46753
- terrain: str(hex.terrain),
46754
- terrainSprite: hex.terrainSprite == null ? void 0 : str(hex.terrainSprite)
46765
+ () => entityTiles.map((t) => ({
46766
+ x: num(t.x),
46767
+ y: num(t.y),
46768
+ terrain: str(t.terrain),
46769
+ terrainSprite: t.terrainSprite == null ? void 0 : str(t.terrainSprite),
46770
+ passable: t.passable !== false
46755
46771
  })),
46756
- [hexes]
46772
+ [entityTiles]
46757
46773
  );
46758
46774
  const tiles = propTiles ?? derivedTiles;
46759
46775
  const baseUnits = useMemo(
46760
- () => propUnits ?? heroes.map((hero) => ({
46761
- id: str(hero.id),
46762
- position: heroPosition(hero),
46763
- name: str(hero.name),
46764
- team: heroOwner(hero) === "enemy" ? "enemy" : "player",
46765
- health: 100,
46766
- maxHealth: 100,
46767
- sprite: hero.sprite == null ? void 0 : str(hero.sprite)
46776
+ () => propUnits ?? entityUnits.map((u) => ({
46777
+ id: str(u.id),
46778
+ position: heroPosition(u),
46779
+ name: str(u.name),
46780
+ // lolo uses `team` field (not `owner`)
46781
+ team: str(u.team) === "enemy" ? "enemy" : "player",
46782
+ health: num(u.health) || 100,
46783
+ maxHealth: num(u.maxHealth) || 100,
46784
+ sprite: u.sprite == null ? void 0 : str(u.sprite)
46768
46785
  })),
46769
- [heroes, propUnits]
46786
+ [entityUnits, propUnits]
46787
+ );
46788
+ const gameUnits = baseUnits;
46789
+ const selectedHero = useMemo(
46790
+ () => gameUnits.find((u) => u.id === selectedHeroId) ?? null,
46791
+ [gameUnits, selectedHeroId]
46770
46792
  );
46771
46793
  const MOVE_SPEED_MS_PER_TILE = 300;
46772
46794
  const movementAnimRef = useRef(null);
@@ -46812,51 +46834,50 @@ function WorldMapBoard({
46812
46834
  const validMoves = useMemo(() => {
46813
46835
  if (!selectedHero || heroMovement(selectedHero) <= 0) return [];
46814
46836
  const sp = heroPosition(selectedHero);
46815
- const sOwner = heroOwner(selectedHero);
46837
+ const sTeam = str(selectedHero.team);
46816
46838
  const range = heroMovement(selectedHero);
46817
46839
  const moves = [];
46818
- hexes.forEach((hex) => {
46819
- const hx = num(hex.x);
46820
- const hy = num(hex.y);
46821
- if (!hexPassable(hex)) return;
46822
- if (hx === sp.x && hy === sp.y) return;
46823
- if (!isInRange(sp, { x: hx, y: hy }, range)) return;
46824
- if (heroes.some((h) => {
46825
- const hp = heroPosition(h);
46826
- return hp.x === hx && hp.y === hy && heroOwner(h) === sOwner;
46840
+ tiles.forEach((t) => {
46841
+ const tx = t.x;
46842
+ const ty = t.y;
46843
+ if (t.passable === false) return;
46844
+ if (tx === sp.x && ty === sp.y) return;
46845
+ if (!isInRange(sp, { x: tx, y: ty }, range)) return;
46846
+ if (gameUnits.some((u) => {
46847
+ const up = u.position ?? { x: u.x ?? -1, y: u.y ?? -1 };
46848
+ return up.x === tx && up.y === ty && str(u.team) === sTeam;
46827
46849
  })) return;
46828
- moves.push({ x: hx, y: hy });
46850
+ moves.push({ x: tx, y: ty });
46829
46851
  });
46830
46852
  return moves;
46831
- }, [selectedHero, hexes, heroes, isInRange]);
46853
+ }, [selectedHero, tiles, gameUnits, isInRange]);
46832
46854
  const attackTargets = useMemo(() => {
46833
46855
  if (!selectedHero || heroMovement(selectedHero) <= 0) return [];
46834
46856
  const sp = heroPosition(selectedHero);
46835
- const sOwner = heroOwner(selectedHero);
46857
+ const sTeam = str(selectedHero.team);
46836
46858
  const range = heroMovement(selectedHero);
46837
- return heroes.filter((h) => heroOwner(h) !== sOwner).filter((h) => isInRange(sp, heroPosition(h), range)).map((h) => heroPosition(h));
46838
- }, [selectedHero, heroes, isInRange]);
46839
- const maxY = Math.max(...hexes.map((h) => num(h.y)), 0);
46859
+ 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 });
46860
+ }, [selectedHero, gameUnits, isInRange]);
46861
+ const maxY = Math.max(...tiles.map((t) => t.y), 0);
46840
46862
  const baseOffsetX = (maxY + 1) * (TILE_WIDTH * scale / 2);
46841
46863
  const tileToScreen = useCallback(
46842
46864
  (tx, ty) => isoToScreen(tx, ty, scale, baseOffsetX),
46843
46865
  [scale, baseOffsetX]
46844
46866
  );
46845
46867
  const hoveredHex = useMemo(
46846
- () => hoveredTile ? hexes.find((h) => num(h.x) === hoveredTile.x && num(h.y) === hoveredTile.y) ?? null : null,
46847
- [hoveredTile, hexes]
46868
+ () => hoveredTile ? tiles.find((t) => t.x === hoveredTile.x && t.y === hoveredTile.y) ?? null : null,
46869
+ [hoveredTile, tiles]
46848
46870
  );
46849
46871
  const hoveredHero = useMemo(
46850
- () => hoveredTile ? heroes.find((h) => {
46851
- const hp = heroPosition(h);
46852
- return hp.x === hoveredTile.x && hp.y === hoveredTile.y;
46872
+ () => hoveredTile ? gameUnits.find((u) => {
46873
+ const up = u.position ?? { x: u.x ?? -1, y: u.y ?? -1 };
46874
+ return up.x === hoveredTile.x && up.y === hoveredTile.y;
46853
46875
  }) ?? null : null,
46854
- [hoveredTile, heroes]
46876
+ [hoveredTile, gameUnits]
46855
46877
  );
46856
46878
  const handleTileClick = useCallback((x, y) => {
46857
46879
  if (movementAnimRef.current) return;
46858
- const hex = hexes.find((h) => num(h.x) === x && num(h.y) === y);
46859
- if (!hex) return;
46880
+ const tile = tiles.find((t) => t.x === x && t.y === y);
46860
46881
  if (tileClickEvent) {
46861
46882
  eventBus.emit(`UI:${tileClickEvent}`, { x, y });
46862
46883
  }
@@ -46867,38 +46888,39 @@ function WorldMapBoard({
46867
46888
  if (heroMoveEvent) {
46868
46889
  eventBus.emit(`UI:${heroMoveEvent}`, { heroId, toX: x, toY: y });
46869
46890
  }
46870
- const feature = str(hex.feature);
46891
+ const feature = tile ? str(tile.feature) : "";
46871
46892
  if (feature && feature !== "none") {
46872
- onFeatureEnter?.(heroId, hex);
46893
+ const tileRow = tile;
46894
+ onFeatureEnter?.(heroId, tileRow);
46873
46895
  if (featureEnterEvent) {
46874
- eventBus.emit(`UI:${featureEnterEvent}`, { heroId, feature, hex });
46896
+ eventBus.emit(`UI:${featureEnterEvent}`, { heroId, feature, hex: tileRow });
46875
46897
  }
46876
46898
  }
46877
46899
  });
46878
46900
  return;
46879
46901
  }
46880
- const enemy = heroes.find((h) => {
46881
- const hp = heroPosition(h);
46882
- return hp.x === x && hp.y === y && heroOwner(h) === "enemy";
46902
+ const enemy = gameUnits.find((u) => {
46903
+ const up = u.position ?? { x: u.x ?? -1, y: u.y ?? -1 };
46904
+ return up.x === x && up.y === y && str(u.team) === "enemy";
46883
46905
  });
46884
46906
  if (selectedHero && enemy && attackTargets.some((t) => t.x === x && t.y === y)) {
46885
46907
  const attackerId = str(selectedHero.id);
46886
- const defenderId = str(enemy.id);
46908
+ const defenderId = enemy.id;
46887
46909
  onBattleEncounter?.(attackerId, defenderId);
46888
46910
  if (battleEncounterEvent) {
46889
46911
  eventBus.emit(`UI:${battleEncounterEvent}`, { attackerId, defenderId });
46890
46912
  }
46891
46913
  }
46892
- }, [hexes, heroes, selectedHero, validMoves, attackTargets, startMoveAnimation, onHeroMove, onFeatureEnter, onBattleEncounter, eventBus, tileClickEvent, heroMoveEvent, featureEnterEvent, battleEncounterEvent]);
46914
+ }, [tiles, gameUnits, selectedHero, validMoves, attackTargets, startMoveAnimation, onHeroMove, onFeatureEnter, onBattleEncounter, eventBus, tileClickEvent, heroMoveEvent, featureEnterEvent, battleEncounterEvent]);
46893
46915
  const handleUnitClick = useCallback((unitId) => {
46894
- const hero = heroes.find((h) => str(h.id) === unitId);
46895
- if (hero && (heroOwner(hero) === "player" || allowMoveAllHeroes)) {
46916
+ const unit = gameUnits.find((u) => u.id === unitId);
46917
+ if (unit && (str(unit.team) === "player" || allowMoveAllHeroes)) {
46896
46918
  onHeroSelect?.(unitId);
46897
46919
  if (heroSelectEvent) {
46898
46920
  eventBus.emit(`UI:${heroSelectEvent}`, { heroId: unitId });
46899
46921
  }
46900
46922
  }
46901
- }, [heroes, onHeroSelect, allowMoveAllHeroes, eventBus, heroSelectEvent]);
46923
+ }, [gameUnits, onHeroSelect, allowMoveAllHeroes, eventBus, heroSelectEvent]);
46902
46924
  const selectHero = useCallback((id) => {
46903
46925
  onHeroSelect?.(id);
46904
46926
  if (heroSelectEvent) {