@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.
package/dist/avl/index.js CHANGED
@@ -11017,6 +11017,7 @@ var init_Sprite = __esm({
11017
11017
  }
11018
11018
  });
11019
11019
  function StateIndicator({
11020
+ assetUrl = DEFAULT_ASSET_URL,
11020
11021
  state = "idle",
11021
11022
  label,
11022
11023
  size = "md",
@@ -11039,18 +11040,29 @@ function StateIndicator({
11039
11040
  className
11040
11041
  ),
11041
11042
  children: [
11042
- /* @__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 }) }),
11043
+ /* @__PURE__ */ jsx(Box, { as: "span", children: assetUrl ? /* @__PURE__ */ jsx(
11044
+ "img",
11045
+ {
11046
+ src: assetUrl,
11047
+ alt: displayLabel,
11048
+ width: 16,
11049
+ height: 16,
11050
+ style: { imageRendering: "pixelated", objectFit: "contain" },
11051
+ className: "flex-shrink-0"
11052
+ }
11053
+ ) : 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 }) }),
11043
11054
  /* @__PURE__ */ jsx(Box, { as: "span", children: displayLabel })
11044
11055
  ]
11045
11056
  }
11046
11057
  );
11047
11058
  }
11048
- var DEFAULT_STATE_STYLES, DEFAULT_STYLE, SIZE_CLASSES;
11059
+ var DEFAULT_ASSET_URL, DEFAULT_STATE_STYLES, DEFAULT_STYLE, SIZE_CLASSES;
11049
11060
  var init_StateIndicator = __esm({
11050
11061
  "components/game/atoms/StateIndicator.tsx"() {
11051
11062
  init_Box();
11052
11063
  init_Icon();
11053
11064
  init_cn();
11065
+ DEFAULT_ASSET_URL = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
11054
11066
  DEFAULT_STATE_STYLES = {
11055
11067
  idle: { icon: "\u23F8", bgClass: "bg-muted" },
11056
11068
  active: { icon: "\u25B6", bgClass: "bg-success" },
@@ -11125,6 +11137,7 @@ var init_TimerDisplay = __esm({
11125
11137
  }
11126
11138
  });
11127
11139
  function ResourceCounter({
11140
+ assetUrl = DEFAULT_ASSET_URL2,
11128
11141
  icon,
11129
11142
  label = "Gold",
11130
11143
  value = 250,
@@ -11144,7 +11157,17 @@ function ResourceCounter({
11144
11157
  className
11145
11158
  ),
11146
11159
  children: [
11147
- icon && /* @__PURE__ */ jsx("span", { className: cn("flex-shrink-0", sizes.icon), children: typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon }) : /* @__PURE__ */ jsx(Icon, { icon }) }),
11160
+ assetUrl ? /* @__PURE__ */ jsx(
11161
+ "img",
11162
+ {
11163
+ src: assetUrl,
11164
+ alt: label,
11165
+ width: sizes.img,
11166
+ height: sizes.img,
11167
+ style: { imageRendering: "pixelated", objectFit: "contain" },
11168
+ className: "flex-shrink-0"
11169
+ }
11170
+ ) : 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,
11148
11171
  /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: label }),
11149
11172
  /* @__PURE__ */ jsxs("span", { className: cn("font-bold tabular-nums", color && (color in colorTokenClasses2 ? colorTokenClasses2[color] : color)), children: [
11150
11173
  value,
@@ -11157,7 +11180,7 @@ function ResourceCounter({
11157
11180
  }
11158
11181
  );
11159
11182
  }
11160
- var colorTokenClasses2, sizeMap5;
11183
+ var colorTokenClasses2, DEFAULT_ASSET_URL2, sizeMap5;
11161
11184
  var init_ResourceCounter = __esm({
11162
11185
  "components/game/atoms/ResourceCounter.tsx"() {
11163
11186
  init_cn();
@@ -11170,16 +11193,17 @@ var init_ResourceCounter = __esm({
11170
11193
  error: "text-error",
11171
11194
  muted: "text-muted-foreground"
11172
11195
  };
11196
+ DEFAULT_ASSET_URL2 = "https://almadar-kflow-assets.web.app/shared/world-map/gold_mine.png";
11173
11197
  sizeMap5 = {
11174
- sm: { wrapper: "text-xs gap-1 px-1.5 py-0.5", icon: "text-sm" },
11175
- md: { wrapper: "text-sm gap-1.5 px-2 py-1", icon: "text-base" },
11176
- lg: { wrapper: "text-base gap-2 px-3 py-1.5", icon: "text-lg" }
11198
+ sm: { wrapper: "text-xs gap-1 px-1.5 py-0.5", icon: "text-sm", img: 16 },
11199
+ md: { wrapper: "text-sm gap-1.5 px-2 py-1", icon: "text-base", img: 20 },
11200
+ lg: { wrapper: "text-base gap-2 px-3 py-1.5", icon: "text-lg", img: 28 }
11177
11201
  };
11178
11202
  ResourceCounter.displayName = "ResourceCounter";
11179
11203
  }
11180
11204
  });
11181
11205
  function ItemSlot({
11182
- assetUrl = DEFAULT_ASSET_URL,
11206
+ assetUrl = DEFAULT_ASSET_URL3,
11183
11207
  icon = "sword",
11184
11208
  label = "Iron Sword",
11185
11209
  quantity,
@@ -11238,7 +11262,7 @@ function ItemSlot({
11238
11262
  }
11239
11263
  );
11240
11264
  }
11241
- var sizeMap6, rarityBorderMap, rarityGlowMap, DEFAULT_ASSET_URL, assetSizeMap;
11265
+ var sizeMap6, rarityBorderMap, rarityGlowMap, DEFAULT_ASSET_URL3, assetSizeMap;
11242
11266
  var init_ItemSlot = __esm({
11243
11267
  "components/game/atoms/ItemSlot.tsx"() {
11244
11268
  "use client";
@@ -11263,7 +11287,7 @@ var init_ItemSlot = __esm({
11263
11287
  epic: "shadow-lg",
11264
11288
  legendary: "shadow-lg"
11265
11289
  };
11266
- DEFAULT_ASSET_URL = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
11290
+ DEFAULT_ASSET_URL3 = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
11267
11291
  assetSizeMap = {
11268
11292
  sm: 28,
11269
11293
  md: 40,
@@ -11336,6 +11360,7 @@ function getComboScale(combo) {
11336
11360
  return "";
11337
11361
  }
11338
11362
  function ComboCounter({
11363
+ assetUrl = DEFAULT_ASSET_URL4,
11339
11364
  combo = 5,
11340
11365
  multiplier,
11341
11366
  streak,
@@ -11355,6 +11380,17 @@ function ComboCounter({
11355
11380
  className
11356
11381
  ),
11357
11382
  children: [
11383
+ assetUrl && /* @__PURE__ */ jsx(
11384
+ "img",
11385
+ {
11386
+ src: assetUrl,
11387
+ alt: "combo",
11388
+ width: 24,
11389
+ height: 24,
11390
+ style: { imageRendering: "pixelated", objectFit: "contain" },
11391
+ className: "flex-shrink-0 mb-0.5"
11392
+ }
11393
+ ),
11358
11394
  /* @__PURE__ */ jsx("span", { className: cn("font-black tabular-nums leading-none", sizes.combo, getComboIntensity(combo)), children: combo }),
11359
11395
  /* @__PURE__ */ jsx("span", { className: cn("font-bold uppercase tracking-wider text-muted-foreground", sizes.label), children: "combo" }),
11360
11396
  multiplier != null && multiplier > 1 && /* @__PURE__ */ jsxs("span", { className: cn("font-bold text-warning tabular-nums", sizes.multiplier), children: [
@@ -11369,10 +11405,11 @@ function ComboCounter({
11369
11405
  }
11370
11406
  );
11371
11407
  }
11372
- var sizeMap8;
11408
+ var DEFAULT_ASSET_URL4, sizeMap8;
11373
11409
  var init_ComboCounter = __esm({
11374
11410
  "components/game/atoms/ComboCounter.tsx"() {
11375
11411
  init_cn();
11412
+ DEFAULT_ASSET_URL4 = "https://almadar-kflow-assets.web.app/shared/effects/flash/flash00.png";
11376
11413
  sizeMap8 = {
11377
11414
  sm: { combo: "text-lg", label: "text-xs", multiplier: "text-xs" },
11378
11415
  md: { combo: "text-2xl", label: "text-xs", multiplier: "text-sm" },
@@ -11466,6 +11503,7 @@ var init_XPBar = __esm({
11466
11503
  }
11467
11504
  });
11468
11505
  function WaypointMarker({
11506
+ assetUrl = DEFAULT_ASSET_URL5,
11469
11507
  label,
11470
11508
  icon,
11471
11509
  active = true,
@@ -11504,7 +11542,17 @@ function WaypointMarker({
11504
11542
  active && !completed && "bg-info text-foreground",
11505
11543
  !active && !completed && "bg-muted"
11506
11544
  ),
11507
- children: completed ? checkIcon : icon && (typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon }) : /* @__PURE__ */ jsx(Icon, { icon }))
11545
+ children: completed ? checkIcon : assetUrl ? /* @__PURE__ */ jsx(
11546
+ "img",
11547
+ {
11548
+ src: assetUrl,
11549
+ alt: label,
11550
+ width: sizes.img,
11551
+ height: sizes.img,
11552
+ style: { imageRendering: "pixelated", objectFit: "contain" },
11553
+ className: "flex-shrink-0"
11554
+ }
11555
+ ) : icon ? typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon }) : /* @__PURE__ */ jsx(Icon, { icon }) : null
11508
11556
  }
11509
11557
  )
11510
11558
  ] }),
@@ -11521,15 +11569,16 @@ function WaypointMarker({
11521
11569
  )
11522
11570
  ] });
11523
11571
  }
11524
- var sizeMap10, checkIcon;
11572
+ var DEFAULT_ASSET_URL5, sizeMap10, checkIcon;
11525
11573
  var init_WaypointMarker = __esm({
11526
11574
  "components/game/atoms/WaypointMarker.tsx"() {
11527
11575
  init_cn();
11528
11576
  init_Icon();
11577
+ DEFAULT_ASSET_URL5 = "https://almadar-kflow-assets.web.app/shared/world-map/battle_marker.png";
11529
11578
  sizeMap10 = {
11530
- sm: { dot: "w-4 h-4", ring: "w-6 h-6", label: "text-xs mt-1" },
11531
- md: { dot: "w-6 h-6", ring: "w-8 h-8", label: "text-sm mt-1.5" },
11532
- lg: { dot: "w-8 h-8", ring: "w-10 h-10", label: "text-base mt-2" }
11579
+ sm: { dot: "w-4 h-4", ring: "w-6 h-6", label: "text-xs mt-1", img: 12 },
11580
+ md: { dot: "w-6 h-6", ring: "w-8 h-8", label: "text-sm mt-1.5", img: 18 },
11581
+ lg: { dot: "w-8 h-8", ring: "w-10 h-10", label: "text-base mt-2", img: 24 }
11533
11582
  };
11534
11583
  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" }) });
11535
11584
  WaypointMarker.displayName = "WaypointMarker";
@@ -11542,6 +11591,7 @@ function formatDuration(seconds) {
11542
11591
  return `${Math.round(seconds)}s`;
11543
11592
  }
11544
11593
  function StatusEffect({
11594
+ assetUrl = DEFAULT_ASSET_URL6,
11545
11595
  icon = "shield",
11546
11596
  label = "Shield",
11547
11597
  duration = 30,
@@ -11562,7 +11612,17 @@ function StatusEffect({
11562
11612
  ),
11563
11613
  title: label,
11564
11614
  children: [
11565
- /* @__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" }) }),
11615
+ /* @__PURE__ */ jsx("span", { className: cn("flex items-center justify-center", sizes.icon), children: assetUrl ? /* @__PURE__ */ jsx(
11616
+ "img",
11617
+ {
11618
+ src: assetUrl,
11619
+ alt: label,
11620
+ width: sizes.img,
11621
+ height: sizes.img,
11622
+ style: { imageRendering: "pixelated", objectFit: "contain" },
11623
+ className: "flex-shrink-0"
11624
+ }
11625
+ ) : typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon, size: "sm" }) : /* @__PURE__ */ jsx(Icon, { icon, size: "sm" }) }),
11566
11626
  duration !== void 0 && /* @__PURE__ */ jsx(
11567
11627
  "span",
11568
11628
  {
@@ -11589,15 +11649,16 @@ function StatusEffect({
11589
11649
  label && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground mt-0.5 text-center whitespace-nowrap", children: label })
11590
11650
  ] });
11591
11651
  }
11592
- var sizeMap11, variantStyles7;
11652
+ var DEFAULT_ASSET_URL6, sizeMap11, variantStyles7;
11593
11653
  var init_StatusEffect = __esm({
11594
11654
  "components/game/atoms/StatusEffect.tsx"() {
11595
11655
  init_cn();
11596
11656
  init_Icon();
11657
+ DEFAULT_ASSET_URL6 = "https://almadar-kflow-assets.web.app/shared/effects/particles/flame_01.png";
11597
11658
  sizeMap11 = {
11598
- sm: { container: "w-8 h-8", icon: "text-sm", badge: "text-xs -top-1 -right-1 w-4 h-4", timer: "text-[9px]" },
11599
- md: { container: "w-10 h-10", icon: "text-base", badge: "text-xs -top-1 -right-1 w-5 h-5", timer: "text-xs" },
11600
- 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" }
11659
+ 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 },
11660
+ 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 },
11661
+ 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 }
11601
11662
  };
11602
11663
  variantStyles7 = {
11603
11664
  buff: "border-success bg-success/20",
@@ -15166,15 +15227,15 @@ function BattleBoard({
15166
15227
  const board = boardEntity(entity) ?? {};
15167
15228
  const tiles = propTiles ?? (Array.isArray(board.tiles) ? board.tiles : []);
15168
15229
  const features = propFeatures ?? (Array.isArray(board.features) ? board.features : []);
15169
- const boardWidth = num(board.boardWidth, 8);
15170
- const boardHeight = num(board.boardHeight, 6);
15230
+ const boardWidth = num(board.gridWidth ?? board.boardWidth, 8);
15231
+ const boardHeight = num(board.gridHeight ?? board.boardHeight, 6);
15171
15232
  const assetManifest = propAssetManifest ?? board.assetManifest;
15172
15233
  const backgroundImage = board.backgroundImage;
15173
15234
  const units = rows(board.units);
15174
15235
  const selectedUnitId = board.selectedUnitId ?? null;
15175
15236
  const currentPhase = str(board.phase) || "observation";
15176
15237
  const currentTurn = num(board.turn, 1);
15177
- const gameResult = board.gameResult ?? null;
15238
+ const gameResult = board.result ?? null;
15178
15239
  const eventBus = useEventBus();
15179
15240
  const { t } = useTranslate();
15180
15241
  const [hoveredTile, setHoveredTile] = useState(null);
@@ -15195,7 +15256,7 @@ function BattleBoard({
15195
15256
  const validMoves = useMemo(() => {
15196
15257
  if (!selectedUnit || currentPhase !== "movement") return [];
15197
15258
  const moves = [];
15198
- const range = num(selectedUnit.movement);
15259
+ const range = num(board.movementRange, 2);
15199
15260
  const origin = unitPosition(selectedUnit);
15200
15261
  for (let dy = -range; dy <= range; dy++) {
15201
15262
  for (let dx = -range; dx <= range; dx++) {
@@ -22651,15 +22712,24 @@ function CastleBoard({
22651
22712
  featureClickEvent,
22652
22713
  unitClickEvent,
22653
22714
  tileClickEvent,
22715
+ playAgainEvent,
22654
22716
  className
22655
22717
  }) {
22656
22718
  const eventBus = useEventBus();
22719
+ const { t } = useTranslate();
22657
22720
  const resolved = boardEntity(entity);
22658
22721
  const tiles = propTiles ?? (Array.isArray(resolved?.tiles) ? resolved.tiles : []);
22659
22722
  const features = propFeatures ?? (Array.isArray(resolved?.features) ? resolved.features : []);
22660
22723
  const units = propUnits ?? (Array.isArray(resolved?.units) ? resolved.units : []);
22661
22724
  const assetManifest = propAssetManifest ?? resolved?.assetManifest;
22662
22725
  const backgroundImage = resolved?.backgroundImage;
22726
+ const gold = num(resolved?.gold);
22727
+ const health = num(resolved?.health);
22728
+ const maxHealth = num(resolved?.maxHealth);
22729
+ const wave = num(resolved?.wave);
22730
+ const tickCount = num(resolved?.tickCount);
22731
+ const buildings = rows(resolved?.buildings);
22732
+ const result = str(resolved?.result) || "none";
22663
22733
  const [hoveredTile, setHoveredTile] = useState(null);
22664
22734
  const [selectedFeature, setSelectedFeature] = useState(null);
22665
22735
  const hoveredFeature = useMemo(() => {
@@ -22672,7 +22742,7 @@ function CastleBoard({
22672
22742
  (u) => u.position?.x === hoveredTile.x && u.position?.y === hoveredTile.y
22673
22743
  ) ?? null;
22674
22744
  }, [hoveredTile, units]);
22675
- const maxY = Math.max(...tiles.map((t) => t.y), 0);
22745
+ const maxY = Math.max(...tiles.map((t2) => t2.y), 0);
22676
22746
  const baseOffsetX = (maxY + 1) * (TILE_WIDTH * scale / 2);
22677
22747
  const tileToScreen = useCallback(
22678
22748
  (tx, ty) => isoToScreen(tx, ty, scale, baseOffsetX),
@@ -22707,6 +22777,11 @@ function CastleBoard({
22707
22777
  }
22708
22778
  }, [units, onUnitClick, unitClickEvent, eventBus]);
22709
22779
  const clearSelection = useCallback(() => setSelectedFeature(null), []);
22780
+ const handlePlayAgain = useCallback(() => {
22781
+ if (playAgainEvent) {
22782
+ eventBus.emit(`UI:${playAgainEvent}`, {});
22783
+ }
22784
+ }, [playAgainEvent, eventBus]);
22710
22785
  const ctx = useMemo(
22711
22786
  () => ({
22712
22787
  hoveredTile,
@@ -22715,11 +22790,18 @@ function CastleBoard({
22715
22790
  selectedFeature,
22716
22791
  clearSelection,
22717
22792
  tileToScreen,
22718
- scale
22793
+ scale,
22794
+ gold,
22795
+ health,
22796
+ maxHealth,
22797
+ wave,
22798
+ tickCount,
22799
+ buildings,
22800
+ result
22719
22801
  }),
22720
- [hoveredTile, hoveredFeature, hoveredUnit, selectedFeature, clearSelection, tileToScreen, scale]
22802
+ [hoveredTile, hoveredFeature, hoveredUnit, selectedFeature, clearSelection, tileToScreen, scale, gold, health, maxHealth, wave, tickCount, buildings, result]
22721
22803
  );
22722
- return /* @__PURE__ */ jsxs("div", { className: cn("castle-board min-h-screen flex flex-col bg-background", className), children: [
22804
+ return /* @__PURE__ */ jsxs("div", { className: cn("castle-board relative min-h-screen flex flex-col bg-background", className), children: [
22723
22805
  header && header(ctx),
22724
22806
  /* @__PURE__ */ jsxs("div", { className: "flex flex-1 overflow-hidden", children: [
22725
22807
  /* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-auto p-4 relative", children: [
@@ -22744,7 +22826,29 @@ function CastleBoard({
22744
22826
  ] }),
22745
22827
  sidePanel && /* @__PURE__ */ jsx("div", { className: "w-96 shrink-0 border-l border-border bg-surface overflow-y-auto", children: sidePanel(ctx) })
22746
22828
  ] }),
22747
- footer && footer(ctx)
22829
+ footer && footer(ctx),
22830
+ 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: [
22831
+ /* @__PURE__ */ jsx(
22832
+ Typography,
22833
+ {
22834
+ variant: "h2",
22835
+ className: cn(
22836
+ "text-4xl font-black tracking-widest uppercase",
22837
+ result === "victory" ? "text-warning" : "text-error"
22838
+ ),
22839
+ children: result === "victory" ? t("battle.victory") : t("battle.defeat")
22840
+ }
22841
+ ),
22842
+ /* @__PURE__ */ jsx(
22843
+ Button,
22844
+ {
22845
+ variant: "primary",
22846
+ className: "px-8 py-3 font-semibold",
22847
+ onClick: handlePlayAgain,
22848
+ children: t("battle.playAgain")
22849
+ }
22850
+ )
22851
+ ] }) })
22748
22852
  ] });
22749
22853
  }
22750
22854
  var init_CastleBoard = __esm({
@@ -22752,6 +22856,10 @@ var init_CastleBoard = __esm({
22752
22856
  "use client";
22753
22857
  init_cn();
22754
22858
  init_useEventBus();
22859
+ init_Box();
22860
+ init_Button();
22861
+ init_Typography();
22862
+ init_Stack();
22755
22863
  init_IsometricCanvas();
22756
22864
  init_boardEntity();
22757
22865
  init_isometric();
@@ -23628,22 +23736,16 @@ function ClassifierBoard({
23628
23736
  const { emit } = useEventBus();
23629
23737
  const { t } = useTranslate();
23630
23738
  const resolved = boardEntity(entity);
23631
- const [localAssignments, setLocalAssignments] = useState({});
23632
23739
  const [headerError, setHeaderError] = useState(false);
23633
- const [localSubmitted, setLocalSubmitted] = useState(false);
23634
- const [localAttempts, setLocalAttempts] = useState(0);
23635
- const [showHint, setShowHint] = useState(false);
23636
23740
  const items = Array.isArray(resolved?.items) ? resolved.items : [];
23637
23741
  const categories = Array.isArray(resolved?.categories) ? resolved.categories : [];
23638
- const entityResult = str(resolved?.result);
23639
- const entityDrivesResult = entityResult.length > 0;
23640
- const entityHasAssignments = items.some((item) => item.assignedCategory != null);
23641
- const assignments = entityHasAssignments ? items.reduce((acc, item) => {
23642
- if (item.assignedCategory != null) acc[item.id] = item.assignedCategory;
23742
+ const result = str(resolved?.result);
23743
+ const submitted = result === "win";
23744
+ const attempts = num(resolved?.attempts);
23745
+ const assignments = items.reduce((acc, item) => {
23746
+ if (item.assignedCategory != null && item.assignedCategory !== "") acc[item.id] = item.assignedCategory;
23643
23747
  return acc;
23644
- }, {}) : localAssignments;
23645
- const attempts = entityDrivesResult ? num(resolved?.attempts) : localAttempts;
23646
- const submitted = entityDrivesResult || localSubmitted;
23748
+ }, {});
23647
23749
  const unassignedItems = items.filter((item) => !assignments[item.id]);
23648
23750
  const allAssigned = items.length > 0 && Object.keys(assignments).length === items.length;
23649
23751
  const results = submitted ? items.map((item) => ({
@@ -23651,54 +23753,25 @@ function ClassifierBoard({
23651
23753
  assigned: assignments[item.id],
23652
23754
  correct: assignments[item.id] === item.correctCategory
23653
23755
  })) : [];
23654
- const allCorrect = entityDrivesResult ? entityResult === "success" : results.length > 0 && results.every((r2) => r2.correct);
23756
+ const allCorrect = result === "win";
23655
23757
  const correctCount = results.filter((r2) => r2.correct).length;
23758
+ const showHint = attempts >= 2 && Boolean(str(resolved?.hint));
23656
23759
  const handleAssign = (itemId, categoryId) => {
23657
23760
  if (submitted) return;
23658
- if (assignEvent) {
23659
- emit(`UI:${assignEvent}`, { itemId, categoryId });
23660
- }
23661
- if (!entityHasAssignments) {
23662
- setLocalAssignments((prev) => ({ ...prev, [itemId]: categoryId }));
23663
- }
23761
+ if (assignEvent) emit(`UI:${assignEvent}`, { itemId, categoryId });
23664
23762
  };
23665
23763
  const handleUnassign = (itemId) => {
23666
23764
  if (submitted) return;
23667
- if (!entityHasAssignments) {
23668
- setLocalAssignments((prev) => {
23669
- const next = { ...prev };
23670
- delete next[itemId];
23671
- return next;
23672
- });
23673
- }
23765
+ if (assignEvent) emit(`UI:${assignEvent}`, { itemId, categoryId: "" });
23674
23766
  };
23675
- const handleSubmit = useCallback(() => {
23676
- if (checkEvent) {
23677
- emit(`UI:${checkEvent}`, {});
23678
- }
23679
- if (!entityDrivesResult) {
23680
- setLocalSubmitted(true);
23681
- setLocalAttempts((a) => a + 1);
23682
- const correct = items.every((item) => assignments[item.id] === item.correctCategory);
23683
- if (correct) {
23684
- emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
23685
- }
23686
- }
23687
- }, [checkEvent, entityDrivesResult, items, assignments, attempts, completeEvent, emit]);
23688
- const handleReset = () => {
23689
- if (!entityDrivesResult) setLocalSubmitted(false);
23690
- if (attempts >= 2 && str(resolved?.hint)) {
23691
- setShowHint(true);
23767
+ const handleSubmit = () => {
23768
+ if (checkEvent) emit(`UI:${checkEvent}`, {});
23769
+ if (allAssigned && items.every((item) => assignments[item.id] === item.correctCategory)) {
23770
+ emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
23692
23771
  }
23693
23772
  };
23694
23773
  const handleFullReset = () => {
23695
- if (playAgainEvent) {
23696
- emit(`UI:${playAgainEvent}`, {});
23697
- }
23698
- setLocalAssignments({});
23699
- setLocalSubmitted(false);
23700
- setLocalAttempts(0);
23701
- setShowHint(false);
23774
+ if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
23702
23775
  };
23703
23776
  if (!resolved) return null;
23704
23777
  const theme = resolved.theme ?? void 0;
@@ -23737,17 +23810,17 @@ function ClassifierBoard({
23737
23810
  /* @__PURE__ */ jsx(Badge, { size: "sm", children: catItems.length })
23738
23811
  ] }),
23739
23812
  /* @__PURE__ */ jsx(HStack, { gap: "xs", className: "flex-wrap min-h-[2rem]", children: catItems.map((item) => {
23740
- const result = results.find((r2) => r2.item.id === item.id);
23813
+ const result2 = results.find((r2) => r2.item.id === item.id);
23741
23814
  return /* @__PURE__ */ jsxs(
23742
23815
  Badge,
23743
23816
  {
23744
23817
  size: "sm",
23745
- className: `cursor-pointer ${result ? result.correct ? "border-success bg-success/10" : "border-error bg-error/10" : ""}`,
23818
+ className: `cursor-pointer ${result2 ? result2.correct ? "border-success bg-success/10" : "border-error bg-error/10" : ""}`,
23746
23819
  onClick: () => handleUnassign(item.id),
23747
23820
  children: [
23748
23821
  item.iconUrl && /* @__PURE__ */ jsx("img", { src: item.iconUrl, alt: "", className: "w-3 h-3 object-contain inline-block" }),
23749
23822
  item.label,
23750
- result && /* @__PURE__ */ jsx(Icon, { icon: result.correct ? CheckCircle : XCircle, size: "xs", className: result.correct ? "text-success" : "text-error" })
23823
+ result2 && /* @__PURE__ */ jsx(Icon, { icon: result2.correct ? CheckCircle : XCircle, size: "xs", className: result2.correct ? "text-success" : "text-error" })
23751
23824
  ]
23752
23825
  },
23753
23826
  item.id
@@ -23776,10 +23849,10 @@ function ClassifierBoard({
23776
23849
  ] }) }),
23777
23850
  showHint && hint && /* @__PURE__ */ jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsx(Typography, { variant: "body", children: hint }) }),
23778
23851
  /* @__PURE__ */ jsxs(HStack, { gap: "sm", justify: "center", children: [
23779
- !submitted ? /* @__PURE__ */ jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: !allAssigned, children: [
23852
+ !submitted && /* @__PURE__ */ jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: !allAssigned, children: [
23780
23853
  /* @__PURE__ */ jsx(Icon, { icon: Send, size: "sm" }),
23781
23854
  t("classifier.check")
23782
- ] }) : !allCorrect ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleReset, children: t("classifier.tryAgain") }) : null,
23855
+ ] }),
23783
23856
  /* @__PURE__ */ jsxs(Button, { variant: "secondary", onClick: handleFullReset, children: [
23784
23857
  /* @__PURE__ */ jsx(Icon, { icon: RotateCcw, size: "sm" }),
23785
23858
  t("classifier.reset")
@@ -31808,7 +31881,7 @@ var init_MapView = __esm({
31808
31881
  shadowSize: [41, 41]
31809
31882
  });
31810
31883
  L.Marker.prototype.options.icon = defaultIcon;
31811
- const { useEffect: useEffect78, useRef: useRef70, useCallback: useCallback118, useState: useState115 } = React91__default;
31884
+ const { useEffect: useEffect78, useRef: useRef70, useCallback: useCallback117, useState: useState115 } = React91__default;
31812
31885
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
31813
31886
  const { useEventBus: useEventBus3 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
31814
31887
  function MapUpdater({ centerLat, centerLng, zoom }) {
@@ -31854,7 +31927,7 @@ var init_MapView = __esm({
31854
31927
  }) {
31855
31928
  const eventBus = useEventBus3();
31856
31929
  const [clickedPosition, setClickedPosition] = useState115(null);
31857
- const handleMapClick = useCallback118((lat, lng) => {
31930
+ const handleMapClick = useCallback117((lat, lng) => {
31858
31931
  if (showClickedPin) {
31859
31932
  setClickedPosition({ lat, lng });
31860
31933
  }
@@ -31863,7 +31936,7 @@ var init_MapView = __esm({
31863
31936
  eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
31864
31937
  }
31865
31938
  }, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
31866
- const handleMarkerClick = useCallback118((marker) => {
31939
+ const handleMarkerClick = useCallback117((marker) => {
31867
31940
  onMarkerClick?.(marker);
31868
31941
  if (markerClickEvent) {
31869
31942
  eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
@@ -40685,44 +40758,27 @@ function DebuggerBoard({
40685
40758
  const { t } = useTranslate();
40686
40759
  const resolved = boardEntity(entity);
40687
40760
  const [headerError, setHeaderError] = useState(false);
40688
- const [showHint, setShowHint] = useState(false);
40689
40761
  const lines = Array.isArray(resolved?.lines) ? resolved.lines : [];
40690
- const result = resolved?.result ?? null;
40762
+ const result = str(resolved?.result) || "none";
40691
40763
  const attempts = num(resolved?.attempts);
40692
- const submitted = result != null;
40764
+ const submitted = result === "win";
40765
+ const showHint = !submitted && attempts >= 2 && Boolean(str(resolved?.hint));
40693
40766
  const bugLines = lines.filter((l) => l.isBug);
40694
40767
  const flaggedLines = lines.filter((l) => l.isFlagged);
40695
40768
  const correctFlags = lines.filter((l) => l.isBug && l.isFlagged);
40696
40769
  const falseFlags = lines.filter((l) => !l.isBug && l.isFlagged);
40697
- const allCorrect = result === "win";
40770
+ const allCorrect = submitted;
40698
40771
  const toggleLine = (lineId) => {
40699
40772
  if (submitted) return;
40700
- if (toggleFlagEvent) {
40701
- emit(`UI:${toggleFlagEvent}`, { lineId });
40702
- }
40773
+ if (toggleFlagEvent) emit(`UI:${toggleFlagEvent}`, { lineId });
40703
40774
  };
40704
40775
  const handleSubmit = useCallback(() => {
40705
- if (checkEvent) {
40706
- emit(`UI:${checkEvent}`, {});
40707
- }
40776
+ if (checkEvent) emit(`UI:${checkEvent}`, {});
40708
40777
  const correct = correctFlags.length === bugLines.length && falseFlags.length === 0;
40709
- if (correct) {
40710
- emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
40711
- }
40778
+ if (correct) emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
40712
40779
  }, [checkEvent, correctFlags.length, bugLines.length, falseFlags.length, attempts, completeEvent, emit]);
40713
40780
  const handleReset = () => {
40714
- if (playAgainEvent) {
40715
- emit(`UI:${playAgainEvent}`, {});
40716
- }
40717
- if (attempts >= 2 && str(resolved?.hint)) {
40718
- setShowHint(true);
40719
- }
40720
- };
40721
- const handleFullReset = () => {
40722
- if (playAgainEvent) {
40723
- emit(`UI:${playAgainEvent}`, {});
40724
- }
40725
- setShowHint(false);
40781
+ if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
40726
40782
  };
40727
40783
  if (!resolved) return null;
40728
40784
  const theme = resolved.theme ?? void 0;
@@ -40746,7 +40802,7 @@ function DebuggerBoard({
40746
40802
  /* @__PURE__ */ jsx(Typography, { variant: "h4", weight: "bold", children: str(resolved.title) })
40747
40803
  ] }),
40748
40804
  /* @__PURE__ */ jsx(Typography, { variant: "body", children: str(resolved.description) }),
40749
- /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-muted-foreground", children: t("debugger.findBugs", { count: String(num(resolved.bugCount)) }) })
40805
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-muted-foreground", children: t("debugger.findBugs", { count: String(lines.filter((l) => l.isBug).length) }) })
40750
40806
  ] }) }),
40751
40807
  /* @__PURE__ */ jsx(Card, { className: "p-0 overflow-hidden", children: /* @__PURE__ */ jsx(VStack, { gap: "none", children: lines.map((line, i) => {
40752
40808
  const isFlagged = !!line.isFlagged;
@@ -40796,11 +40852,11 @@ function DebuggerBoard({
40796
40852
  ] }) }),
40797
40853
  showHint && hint && /* @__PURE__ */ jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsx(Typography, { variant: "body", children: hint }) }),
40798
40854
  /* @__PURE__ */ jsxs(HStack, { gap: "sm", justify: "center", children: [
40799
- !submitted ? /* @__PURE__ */ jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: flaggedLines.length === 0, children: [
40855
+ !submitted && /* @__PURE__ */ jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: flaggedLines.length === 0, children: [
40800
40856
  /* @__PURE__ */ jsx(Icon, { icon: Send, size: "sm" }),
40801
40857
  t("debugger.submit")
40802
- ] }) : !allCorrect ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleReset, children: t("debugger.tryAgain") }) : null,
40803
- /* @__PURE__ */ jsxs(Button, { variant: "secondary", onClick: handleFullReset, children: [
40858
+ ] }),
40859
+ /* @__PURE__ */ jsxs(Button, { variant: "secondary", onClick: handleReset, children: [
40804
40860
  /* @__PURE__ */ jsx(Icon, { icon: RotateCcw, size: "sm" }),
40805
40861
  t("debugger.reset")
40806
40862
  ] })
@@ -41801,39 +41857,40 @@ function EventHandlerBoard({
41801
41857
  stepDurationMs = 800,
41802
41858
  playEvent,
41803
41859
  completeEvent,
41860
+ editRuleEvent,
41861
+ playAgainEvent,
41804
41862
  className
41805
41863
  }) {
41806
41864
  const { emit } = useEventBus();
41807
41865
  const { t } = useTranslate();
41808
41866
  const resolved = boardEntity(entity);
41809
- const entityObjects = rows(resolved?.objects);
41810
- const [objects, setObjects] = useState(() => [...entityObjects]);
41867
+ const objects = rows(resolved?.objects);
41868
+ const entityResult = str(resolved?.result) || "none";
41869
+ const isSuccess = entityResult === "win";
41870
+ const attempts = typeof resolved?.attempts === "number" ? resolved.attempts : 0;
41811
41871
  const [selectedObjectId, setSelectedObjectId] = useState(
41812
- entityObjects[0] ? objId(entityObjects[0]) : null
41872
+ objects[0] ? objId(objects[0]) : null
41813
41873
  );
41814
41874
  const [headerError, setHeaderError] = useState(false);
41815
- const [playState, setPlayState] = useState("editing");
41875
+ const [isPlaying, setIsPlaying] = useState(false);
41816
41876
  const [eventLog, setEventLog] = useState([]);
41817
- const [attempts, setAttempts] = useState(0);
41818
41877
  const timerRef = useRef(null);
41819
41878
  const logIdCounter = useRef(0);
41820
41879
  useEffect(() => () => {
41821
41880
  if (timerRef.current) clearTimeout(timerRef.current);
41822
41881
  }, []);
41823
- const selectedObject = objects.find((o) => objId(o) === selectedObjectId) || null;
41882
+ const selectedObject = objects.find((o) => objId(o) === selectedObjectId) ?? null;
41824
41883
  const handleRulesChange = useCallback((objectId, rules) => {
41825
- setObjects((prev) => prev.map(
41826
- (o) => objId(o) === objectId ? { ...o, rules } : o
41827
- ));
41828
- }, []);
41884
+ if (editRuleEvent) emit(`UI:${editRuleEvent}`, { objectId, rules });
41885
+ }, [editRuleEvent, emit]);
41829
41886
  const addLogEntry = useCallback((icon, message, status = "done") => {
41830
41887
  const id = `log-${logIdCounter.current++}`;
41831
41888
  setEventLog((prev) => [...prev, { id, timestamp: Date.now(), icon, message, status }]);
41832
41889
  }, []);
41833
41890
  const handlePlay = useCallback(() => {
41834
- if (playState !== "editing") return;
41891
+ if (isPlaying || isSuccess) return;
41835
41892
  if (playEvent) emit(`UI:${playEvent}`, {});
41836
- setPlayState("playing");
41893
+ setIsPlaying(true);
41837
41894
  setEventLog([]);
41838
41895
  const allRules = [];
41839
41896
  objects.forEach((obj) => {
@@ -41849,15 +41906,8 @@ function EventHandlerBoard({
41849
41906
  let goalReached = false;
41850
41907
  const processNext = () => {
41851
41908
  if (eventQueue.length === 0 || stepIdx > 20) {
41852
- if (goalReached) {
41853
- setPlayState("success");
41854
- if (completeEvent) {
41855
- emit(`UI:${completeEvent}`, { success: true });
41856
- }
41857
- } else {
41858
- setAttempts((prev) => prev + 1);
41859
- setPlayState("fail");
41860
- }
41909
+ setIsPlaying(false);
41910
+ if (goalReached && completeEvent) emit(`UI:${completeEvent}`, { success: true });
41861
41911
  return;
41862
41912
  }
41863
41913
  const currentEvent = eventQueue.shift();
@@ -41888,21 +41938,19 @@ function EventHandlerBoard({
41888
41938
  addLogEntry("\u{1F3AC}", t("eventHandler.simulationStarted", { events: triggers.join(", ") }), "active");
41889
41939
  }
41890
41940
  timerRef.current = setTimeout(processNext, stepDurationMs);
41891
- }, [playState, objects, resolved, stepDurationMs, playEvent, completeEvent, emit, addLogEntry, t]);
41941
+ }, [isPlaying, isSuccess, objects, resolved, stepDurationMs, playEvent, completeEvent, emit, addLogEntry, t]);
41892
41942
  const handleTryAgain = useCallback(() => {
41893
41943
  if (timerRef.current) clearTimeout(timerRef.current);
41894
- setPlayState("editing");
41944
+ setIsPlaying(false);
41895
41945
  setEventLog([]);
41896
41946
  }, []);
41897
41947
  const handleReset = useCallback(() => {
41898
41948
  if (timerRef.current) clearTimeout(timerRef.current);
41899
- const resetObjects = rows(resolved?.objects);
41900
- setObjects([...resetObjects]);
41901
- setPlayState("editing");
41949
+ setIsPlaying(false);
41902
41950
  setEventLog([]);
41903
- setSelectedObjectId(resetObjects[0] ? objId(resetObjects[0]) : null);
41904
- setAttempts(0);
41905
- }, [resolved?.objects]);
41951
+ setSelectedObjectId(objects[0] ? objId(objects[0]) : null);
41952
+ if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
41953
+ }, [objects, playAgainEvent, emit]);
41906
41954
  if (!resolved) return null;
41907
41955
  const objectViewers = objects.map((obj) => {
41908
41956
  const states = objStates(obj);
@@ -41971,12 +42019,12 @@ function EventHandlerBoard({
41971
42019
  {
41972
42020
  object: selectedObject,
41973
42021
  onRulesChange: handleRulesChange,
41974
- disabled: playState !== "editing"
42022
+ disabled: isPlaying
41975
42023
  }
41976
42024
  ),
41977
42025
  eventLog.length > 0 && /* @__PURE__ */ jsx(EventLog, { entries: eventLog }),
41978
- 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") }) }),
41979
- playState === "fail" && /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
42026
+ 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") }) }),
42027
+ !isPlaying && !isSuccess && attempts > 0 && /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
41980
42028
  /* @__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) }) }),
41981
42029
  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: [
41982
42030
  /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-accent font-bold shrink-0", children: "\u{1F4A1} " + t("game.hint") + ":" }),
@@ -41984,12 +42032,12 @@ function EventHandlerBoard({
41984
42032
  ] }) })
41985
42033
  ] }),
41986
42034
  /* @__PURE__ */ jsxs(HStack, { gap: "sm", children: [
41987
- playState === "fail" ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleTryAgain, children: "\u{1F504} " + t("puzzle.tryAgainButton") }) : /* @__PURE__ */ jsx(
42035
+ !isPlaying && !isSuccess && attempts > 0 ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleTryAgain, children: "\u{1F504} " + t("puzzle.tryAgainButton") }) : /* @__PURE__ */ jsx(
41988
42036
  Button,
41989
42037
  {
41990
42038
  variant: "primary",
41991
42039
  onClick: handlePlay,
41992
- disabled: playState !== "editing",
42040
+ disabled: isPlaying || isSuccess,
41993
42041
  children: "\u25B6 " + t("game.play")
41994
42042
  }
41995
42043
  ),
@@ -44542,21 +44590,6 @@ var init_ModalSlot = __esm({
44542
44590
  ModalSlot.displayName = "ModalSlot";
44543
44591
  }
44544
44592
  });
44545
- function getOpponentAction(strategy, actions, history) {
44546
- const actionIds = actions.map((a) => a.id);
44547
- switch (strategy) {
44548
- case "always-cooperate":
44549
- return actionIds[0];
44550
- case "always-defect":
44551
- return actionIds[actionIds.length - 1];
44552
- case "tit-for-tat":
44553
- if (history.length === 0) return actionIds[0];
44554
- return history[history.length - 1].playerAction;
44555
- case "random":
44556
- default:
44557
- return actionIds[Math.floor(Math.random() * actionIds.length)];
44558
- }
44559
- }
44560
44593
  function NegotiatorBoard({
44561
44594
  entity,
44562
44595
  completeEvent = "PUZZLE_COMPLETE",
@@ -44578,28 +44611,35 @@ function NegotiatorBoard({
44578
44611
  const playerTotal = num(resolved?.score);
44579
44612
  const isComplete = result !== "none" || totalRounds > 0 && currentRound >= totalRounds;
44580
44613
  const won = result === "win";
44581
- const opponentTotal = history.reduce((s, r2) => s + r2.opponentPayoff, 0);
44614
+ const lastPlayerAction = str(resolved?.lastPlayerAction);
44615
+ const lastOpponentAction = str(resolved?.lastOpponentAction);
44616
+ const lastPayoff = num(resolved?.lastPayoff);
44582
44617
  const actions = Array.isArray(resolved?.actions) ? resolved.actions : [];
44583
44618
  const payoffMatrix = Array.isArray(resolved?.payoffMatrix) ? resolved.payoffMatrix : [];
44619
+ const prevRoundRef = useRef(currentRound);
44620
+ useEffect(() => {
44621
+ if (currentRound > prevRoundRef.current && lastPlayerAction) {
44622
+ const opponentPayoffEntry = payoffMatrix.find(
44623
+ (p2) => p2.playerAction === lastPlayerAction && p2.opponentAction === lastOpponentAction
44624
+ );
44625
+ setHistory((prev) => [
44626
+ ...prev,
44627
+ {
44628
+ round: currentRound,
44629
+ playerAction: lastPlayerAction,
44630
+ opponentAction: lastOpponentAction,
44631
+ playerPayoff: lastPayoff,
44632
+ opponentPayoff: opponentPayoffEntry?.opponentPayoff ?? 0
44633
+ }
44634
+ ]);
44635
+ }
44636
+ prevRoundRef.current = currentRound;
44637
+ }, [currentRound, lastPlayerAction, lastOpponentAction, lastPayoff, payoffMatrix]);
44638
+ const opponentTotal = useMemo(() => history.reduce((s, r2) => s + r2.opponentPayoff, 0), [history]);
44584
44639
  const handleAction = useCallback((actionId) => {
44585
44640
  if (isComplete) return;
44586
- const opponentAction = getOpponentAction(str(resolved?.opponentStrategy) || "random", actions, history);
44587
- const payoff = payoffMatrix.find(
44588
- (p2) => p2.playerAction === actionId && p2.opponentAction === opponentAction
44589
- );
44590
- const playerPayoff = payoff?.playerPayoff ?? 0;
44591
- setHistory((prev) => [
44592
- ...prev,
44593
- {
44594
- round: prev.length + 1,
44595
- playerAction: actionId,
44596
- opponentAction,
44597
- playerPayoff,
44598
- opponentPayoff: payoff?.opponentPayoff ?? 0
44599
- }
44600
- ]);
44601
44641
  if (playRoundEvent) {
44602
- emit(`UI:${playRoundEvent}`, { playerAction: actionId, payoff: playerPayoff });
44642
+ emit(`UI:${playRoundEvent}`, { playerAction: actionId, payoff: 0 });
44603
44643
  }
44604
44644
  if (totalRounds > 0 && currentRound + 1 >= totalRounds) {
44605
44645
  if (finishEvent) {
@@ -44609,10 +44649,11 @@ function NegotiatorBoard({
44609
44649
  setShowHint(true);
44610
44650
  }
44611
44651
  }
44612
- }, [isComplete, resolved, totalRounds, currentRound, actions, payoffMatrix, history, playRoundEvent, finishEvent, emit]);
44652
+ }, [isComplete, resolved, totalRounds, currentRound, playRoundEvent, finishEvent, emit]);
44613
44653
  const handleReset = useCallback(() => {
44614
44654
  setHistory([]);
44615
44655
  setShowHint(false);
44656
+ prevRoundRef.current = 0;
44616
44657
  if (playAgainEvent) {
44617
44658
  emit(`UI:${playAgainEvent}`, {});
44618
44659
  }
@@ -46967,17 +47008,24 @@ function SequencerBoard({
46967
47008
  const { emit } = useEventBus();
46968
47009
  const { t } = useTranslate();
46969
47010
  const resolved = boardEntity(entity);
46970
- const maxSlots = num(resolved?.maxSlots);
47011
+ const maxSlots = num(resolved?.maxSlots) || 3;
46971
47012
  const solutions = Array.isArray(resolved?.solutions) ? resolved.solutions : [];
46972
47013
  const availableActions = Array.isArray(resolved?.availableActions) ? resolved.availableActions : [];
46973
47014
  const allowDuplicates = resolved?.allowDuplicates !== false;
47015
+ const entitySlots = Array.isArray(resolved?.slots) ? resolved.slots : [];
47016
+ const entityResult = str(resolved?.result);
47017
+ const entityAttempts = num(resolved?.attempts);
47018
+ const entityCurrentStep = typeof resolved?.currentStep === "number" ? resolved.currentStep : -1;
47019
+ const slots = Array.from({ length: maxSlots }, (_, i) => {
47020
+ const entitySlot = entitySlots.find((s) => s.index === i);
47021
+ if (!entitySlot?.placedActionId) return void 0;
47022
+ return availableActions.find((a) => a.id === entitySlot.placedActionId);
47023
+ });
47024
+ const isSuccess = entityResult === "win";
47025
+ const attempts = entityAttempts;
47026
+ const isPlayingBack = entityCurrentStep >= 0 && !isSuccess;
47027
+ const currentStep = entityCurrentStep;
46974
47028
  const [headerError, setHeaderError] = useState(false);
46975
- const [slots, setSlots] = useState(
46976
- () => Array.from({ length: maxSlots }, () => void 0)
46977
- );
46978
- const [playState, setPlayState] = useState("idle");
46979
- const [currentStep, setCurrentStep] = useState(-1);
46980
- const [attempts, setAttempts] = useState(0);
46981
47029
  const [slotFeedback, setSlotFeedback] = useState(
46982
47030
  () => Array.from({ length: maxSlots }, () => null)
46983
47031
  );
@@ -46986,11 +47034,6 @@ function SequencerBoard({
46986
47034
  if (timerRef.current) clearTimeout(timerRef.current);
46987
47035
  }, []);
46988
47036
  const handleSlotDrop = useCallback((index, item) => {
46989
- setSlots((prev) => {
46990
- const next = [...prev];
46991
- next[index] = item;
46992
- return next;
46993
- });
46994
47037
  setSlotFeedback((prev) => {
46995
47038
  const next = [...prev];
46996
47039
  next[index] = null;
@@ -47000,11 +47043,6 @@ function SequencerBoard({
47000
47043
  if (placeEvent) emit(`UI:${placeEvent}`, { slotIndex: index, actionId: item.id });
47001
47044
  }, [emit, placeEvent]);
47002
47045
  const handleSlotRemove = useCallback((index) => {
47003
- setSlots((prev) => {
47004
- const next = [...prev];
47005
- next[index] = void 0;
47006
- return next;
47007
- });
47008
47046
  setSlotFeedback((prev) => {
47009
47047
  const next = [...prev];
47010
47048
  next[index] = null;
@@ -47015,48 +47053,34 @@ function SequencerBoard({
47015
47053
  }, [emit, removeEvent]);
47016
47054
  const handleReset = useCallback(() => {
47017
47055
  if (timerRef.current) clearTimeout(timerRef.current);
47018
- setSlots(Array.from({ length: maxSlots }, () => void 0));
47019
- setPlayState("idle");
47020
- setCurrentStep(-1);
47021
- setAttempts(0);
47022
47056
  setSlotFeedback(Array.from({ length: maxSlots }, () => null));
47023
47057
  if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
47024
47058
  }, [maxSlots, playAgainEvent, emit]);
47025
47059
  const filledSlots = slots.filter((s) => !!s);
47026
- const canPlay = filledSlots.length > 0 && playState === "idle";
47060
+ const canPlay = filledSlots.length > 0 && !isPlayingBack && !isSuccess;
47027
47061
  const handlePlay = useCallback(() => {
47028
47062
  if (!canPlay) return;
47029
47063
  setSlotFeedback(Array.from({ length: maxSlots }, () => null));
47030
47064
  emit("UI:PLAY_SOUND", { key: "confirm" });
47031
47065
  const sequence = slots.map((s) => s?.id || "");
47032
- if (playEvent) {
47033
- emit(`UI:${playEvent}`, { sequence });
47034
- }
47035
- setPlayState("playing");
47036
- setCurrentStep(0);
47066
+ const playerIds = slots.filter(Boolean).map((s) => s?.id || "");
47067
+ if (playEvent) emit(`UI:${playEvent}`, { sequence });
47037
47068
  let step = 0;
47038
47069
  const advance = () => {
47039
47070
  step++;
47071
+ emit("UI:STEP", { step });
47040
47072
  if (step >= maxSlots) {
47073
+ if (checkEvent) emit(`UI:${checkEvent}`, { sequence: playerIds });
47041
47074
  const playerSeq = slots.map((s) => s?.id);
47042
- const playerIds = slots.filter(Boolean).map((s) => s?.id || "");
47043
47075
  const success = solutions.some(
47044
47076
  (sol) => sol.length === playerIds.length && sol.every((id, i) => id === playerIds[i])
47045
47077
  );
47046
- if (checkEvent) emit(`UI:${checkEvent}`, { sequence: playerIds });
47047
47078
  if (success) {
47048
- setPlayState("success");
47049
- setCurrentStep(-1);
47050
47079
  emit("UI:PLAY_SOUND", { key: "levelComplete" });
47051
- if (completeEvent) {
47052
- emit(`UI:${completeEvent}`, { success: true, sequence: playerIds });
47053
- }
47080
+ if (completeEvent) emit(`UI:${completeEvent}`, { success: true, sequence: playerIds });
47054
47081
  } else {
47055
- setAttempts((prev) => prev + 1);
47056
47082
  const feedback = computeSlotFeedback(playerSeq, solutions);
47057
47083
  setSlotFeedback(feedback);
47058
- setPlayState("idle");
47059
- setCurrentStep(-1);
47060
47084
  emit("UI:PLAY_SOUND", { key: "fail" });
47061
47085
  const correctCount2 = feedback.filter((f3) => f3 === "correct").length;
47062
47086
  for (let ci = 0; ci < correctCount2; ci++) {
@@ -47066,7 +47090,6 @@ function SequencerBoard({
47066
47090
  }
47067
47091
  }
47068
47092
  } else {
47069
- setCurrentStep(step);
47070
47093
  timerRef.current = setTimeout(advance, stepDurationMs);
47071
47094
  }
47072
47095
  };
@@ -47116,7 +47139,7 @@ function SequencerBoard({
47116
47139
  /* @__PURE__ */ jsxs(VStack, { gap: "xs", children: [
47117
47140
  /* @__PURE__ */ jsxs(HStack, { className: "items-center justify-between", children: [
47118
47141
  /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("sequencer.yourSequence") + ":" }),
47119
- hasFeedback && playState === "idle" && /* @__PURE__ */ jsxs(Typography, { variant: "caption", className: "text-muted-foreground", children: [
47142
+ hasFeedback && !isPlayingBack && !isSuccess && /* @__PURE__ */ jsxs(Typography, { variant: "caption", className: "text-muted-foreground", children: [
47120
47143
  `${correctCount}/${maxSlots} `,
47121
47144
  "\u2705"
47122
47145
  ] })
@@ -47128,7 +47151,7 @@ function SequencerBoard({
47128
47151
  maxSlots,
47129
47152
  onSlotDrop: handleSlotDrop,
47130
47153
  onSlotRemove: handleSlotRemove,
47131
- playing: playState === "playing",
47154
+ playing: isPlayingBack,
47132
47155
  currentStep,
47133
47156
  categoryColors,
47134
47157
  slotFeedback,
@@ -47136,7 +47159,7 @@ function SequencerBoard({
47136
47159
  }
47137
47160
  )
47138
47161
  ] }),
47139
- playState !== "playing" && /* @__PURE__ */ jsx(
47162
+ !isPlayingBack && /* @__PURE__ */ jsx(
47140
47163
  ActionPalette,
47141
47164
  {
47142
47165
  actions: availableActions,
@@ -47146,8 +47169,8 @@ function SequencerBoard({
47146
47169
  label: t("sequencer.dragActions")
47147
47170
  }
47148
47171
  ),
47149
- 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) }) }),
47150
- 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") }) }),
47172
+ 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) }) }),
47173
+ 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") }) }),
47151
47174
  /* @__PURE__ */ jsxs(HStack, { gap: "sm", children: [
47152
47175
  /* @__PURE__ */ jsx(
47153
47176
  Button,
@@ -48197,6 +48220,9 @@ function StateArchitectBoard({
48197
48220
  stepDurationMs = 600,
48198
48221
  testEvent,
48199
48222
  completeEvent,
48223
+ addTransitionEvent,
48224
+ removeTransitionEvent,
48225
+ playAgainEvent,
48200
48226
  className
48201
48227
  }) {
48202
48228
  const { emit } = useEventBus();
@@ -48209,14 +48235,16 @@ function StateArchitectBoard({
48209
48235
  const testCases = Array.isArray(resolved?.testCases) ? resolved.testCases : [];
48210
48236
  const entityTransitions = Array.isArray(resolved?.transitions) ? resolved.transitions : [];
48211
48237
  const entityVariables = rows(resolved?.variables);
48212
- const [transitions, setTransitions] = useState(entityTransitions);
48238
+ const transitions = entityTransitions;
48239
+ const attempts = typeof resolved?.attempts === "number" ? resolved.attempts : 0;
48240
+ const entityResult = str(resolved?.result) || "none";
48241
+ const isSuccess = entityResult === "win";
48242
+ const [isTesting, setIsTesting] = useState(false);
48213
48243
  const [headerError, setHeaderError] = useState(false);
48214
- const [playState, setPlayState] = useState("editing");
48215
48244
  const [currentState, setCurrentState] = useState(initialState);
48216
48245
  const [selectedState, setSelectedState] = useState(null);
48217
48246
  const [testResults, setTestResults] = useState([]);
48218
48247
  const [variables, setVariables] = useState(() => [...entityVariables]);
48219
- const [attempts, setAttempts] = useState(0);
48220
48248
  const timerRef = useRef(null);
48221
48249
  const [addingFrom, setAddingFrom] = useState(null);
48222
48250
  useEffect(() => () => {
@@ -48226,7 +48254,7 @@ function StateArchitectBoard({
48226
48254
  const GRAPH_H = 400;
48227
48255
  const positions = useMemo(() => layoutStates(entityStates, GRAPH_W, GRAPH_H), [entityStates]);
48228
48256
  const handleStateClick = useCallback((state) => {
48229
- if (playState !== "editing") return;
48257
+ if (isTesting) return;
48230
48258
  if (addingFrom) {
48231
48259
  if (addingFrom !== state) {
48232
48260
  const event = availableEvents[0] || "EVENT";
@@ -48236,20 +48264,20 @@ function StateArchitectBoard({
48236
48264
  to: state,
48237
48265
  event
48238
48266
  };
48239
- setTransitions((prev) => [...prev, newTrans]);
48267
+ if (addTransitionEvent) emit(`UI:${addTransitionEvent}`, { id: newTrans.id, from: newTrans.from, to: newTrans.to, event: newTrans.event });
48240
48268
  }
48241
48269
  setAddingFrom(null);
48242
48270
  } else {
48243
48271
  setSelectedState(state);
48244
48272
  }
48245
- }, [playState, addingFrom, availableEvents]);
48273
+ }, [isTesting, addingFrom, availableEvents, addTransitionEvent, emit]);
48246
48274
  const handleStartAddTransition = useCallback(() => {
48247
48275
  if (!selectedState) return;
48248
48276
  setAddingFrom(selectedState);
48249
48277
  }, [selectedState]);
48250
48278
  const handleRemoveTransition = useCallback((transId) => {
48251
- setTransitions((prev) => prev.filter((t2) => t2.id !== transId));
48252
- }, []);
48279
+ if (removeTransitionEvent) emit(`UI:${removeTransitionEvent}`, { id: transId });
48280
+ }, [removeTransitionEvent, emit]);
48253
48281
  const machine = useMemo(() => ({
48254
48282
  name: entityName,
48255
48283
  description: str(resolved?.description),
@@ -48263,16 +48291,16 @@ function StateArchitectBoard({
48263
48291
  }))
48264
48292
  }), [entityName, resolved, entityStates, currentState, transitions]);
48265
48293
  const handleTest = useCallback(() => {
48266
- if (playState !== "editing") return;
48294
+ if (isTesting) return;
48267
48295
  if (testEvent) emit(`UI:${testEvent}`, {});
48268
- setPlayState("testing");
48296
+ setIsTesting(true);
48269
48297
  setTestResults([]);
48270
48298
  const results = [];
48271
48299
  let testIdx = 0;
48272
48300
  const runNextTest = () => {
48273
48301
  if (testIdx >= testCases.length) {
48274
48302
  const allPassed = results.every((r2) => r2.passed);
48275
- setPlayState(allPassed ? "success" : "fail");
48303
+ setIsTesting(false);
48276
48304
  setTestResults(results);
48277
48305
  if (allPassed && completeEvent) {
48278
48306
  emit(`UI:${completeEvent}`, {
@@ -48280,9 +48308,6 @@ function StateArchitectBoard({
48280
48308
  passedTests: results.filter((r2) => r2.passed).length
48281
48309
  });
48282
48310
  }
48283
- if (!allPassed) {
48284
- setAttempts((prev) => prev + 1);
48285
- }
48286
48311
  return;
48287
48312
  }
48288
48313
  const testCase = testCases[testIdx];
@@ -48305,24 +48330,23 @@ function StateArchitectBoard({
48305
48330
  timerRef.current = setTimeout(runNextTest, stepDurationMs);
48306
48331
  };
48307
48332
  timerRef.current = setTimeout(runNextTest, stepDurationMs);
48308
- }, [playState, transitions, testCases, initialState, stepDurationMs, testEvent, completeEvent, emit]);
48333
+ }, [isTesting, transitions, testCases, initialState, stepDurationMs, testEvent, completeEvent, emit]);
48309
48334
  const handleTryAgain = useCallback(() => {
48310
48335
  if (timerRef.current) clearTimeout(timerRef.current);
48311
- setPlayState("editing");
48336
+ setIsTesting(false);
48312
48337
  setCurrentState(initialState);
48313
48338
  setTestResults([]);
48314
48339
  }, [initialState]);
48315
48340
  const handleReset = useCallback(() => {
48316
48341
  if (timerRef.current) clearTimeout(timerRef.current);
48317
- setTransitions(entityTransitions);
48318
- setPlayState("editing");
48342
+ setIsTesting(false);
48319
48343
  setCurrentState(initialState);
48320
48344
  setTestResults([]);
48321
48345
  setVariables([...entityVariables]);
48322
48346
  setSelectedState(null);
48323
48347
  setAddingFrom(null);
48324
- setAttempts(0);
48325
- }, [entityTransitions, initialState, entityVariables]);
48348
+ if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
48349
+ }, [initialState, entityVariables, playAgainEvent, emit]);
48326
48350
  const codeData = useMemo(() => ({
48327
48351
  name: entityName,
48328
48352
  states: entityStates,
@@ -48418,7 +48442,7 @@ function StateArchitectBoard({
48418
48442
  ]
48419
48443
  }
48420
48444
  ),
48421
- playState === "editing" && /* @__PURE__ */ jsx(HStack, { gap: "sm", children: /* @__PURE__ */ jsx(
48445
+ !isTesting && /* @__PURE__ */ jsx(HStack, { gap: "sm", children: /* @__PURE__ */ jsx(
48422
48446
  Button,
48423
48447
  {
48424
48448
  variant: "ghost",
@@ -48440,7 +48464,7 @@ function StateArchitectBoard({
48440
48464
  t2.guardHint,
48441
48465
  ")"
48442
48466
  ] }),
48443
- playState === "editing" && /* @__PURE__ */ jsx(
48467
+ !isTesting && /* @__PURE__ */ jsx(
48444
48468
  Button,
48445
48469
  {
48446
48470
  variant: "ghost",
@@ -48472,21 +48496,21 @@ function StateArchitectBoard({
48472
48496
  resolved.showCodeView !== false && /* @__PURE__ */ jsx(StateJsonView, { data: codeData, label: "View Code" })
48473
48497
  ] })
48474
48498
  ] }),
48475
- 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") }) }),
48476
- playState === "fail" && /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
48499
+ 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") }) }),
48500
+ !isTesting && !isSuccess && testResults.some((r2) => !r2.passed) && /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
48477
48501
  /* @__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]) }) }),
48478
- 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: [
48502
+ !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: [
48479
48503
  /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-accent font-bold shrink-0", children: "\u{1F4A1} " + t("game.hint") + ":" }),
48480
48504
  /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-foreground", children: hint })
48481
48505
  ] }) })
48482
48506
  ] }),
48483
48507
  /* @__PURE__ */ jsxs(HStack, { gap: "sm", children: [
48484
- playState === "fail" ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleTryAgain, children: "\u{1F504} " + t("puzzle.tryAgainButton") }) : /* @__PURE__ */ jsx(
48508
+ !isTesting && !isSuccess && testResults.some((r2) => !r2.passed) ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleTryAgain, children: "\u{1F504} " + t("puzzle.tryAgainButton") }) : /* @__PURE__ */ jsx(
48485
48509
  Button,
48486
48510
  {
48487
48511
  variant: "primary",
48488
48512
  onClick: handleTest,
48489
- disabled: playState !== "editing",
48513
+ disabled: isTesting,
48490
48514
  children: "\u25B6 " + t("game.runTests")
48491
48515
  }
48492
48516
  ),
@@ -49190,17 +49214,12 @@ var init_UncontrolledBattleBoard = __esm({
49190
49214
  }
49191
49215
  });
49192
49216
  function heroPosition(h) {
49217
+ if ("position" in h && h.position != null) return h.position;
49193
49218
  return vec2(h.position);
49194
49219
  }
49195
- function heroOwner(h) {
49196
- return str(h.owner);
49197
- }
49198
49220
  function heroMovement(h) {
49199
49221
  return num(h.movement);
49200
49222
  }
49201
- function hexPassable(h) {
49202
- return h.passable !== false;
49203
- }
49204
49223
  function defaultIsInRange(from, to, range) {
49205
49224
  return Math.abs(from.x - to.x) + Math.abs(from.y - to.y) <= range;
49206
49225
  }
@@ -49236,38 +49255,41 @@ function WorldMapBoard({
49236
49255
  }) {
49237
49256
  const eventBus = useEventBus();
49238
49257
  const resolved = boardEntity(entity);
49239
- const hexes = rows(resolved?.hexes);
49240
- const heroes = rows(resolved?.heroes);
49258
+ const entityUnits = rows(resolved?.units);
49259
+ const entityTiles = rows(resolved?.tiles);
49241
49260
  const features = propFeatures ?? (Array.isArray(resolved?.features) ? resolved.features : []);
49242
49261
  const selectedHeroId = resolved?.selectedHeroId ?? null;
49243
49262
  const assetManifest = propAssetManifest ?? resolved?.assetManifest;
49244
49263
  const backgroundImage = resolved?.backgroundImage;
49245
49264
  const [hoveredTile, setHoveredTile] = useState(null);
49246
- const selectedHero = useMemo(
49247
- () => heroes.find((h) => str(h.id) === selectedHeroId) ?? null,
49248
- [heroes, selectedHeroId]
49249
- );
49250
49265
  const derivedTiles = useMemo(
49251
- () => hexes.map((hex) => ({
49252
- x: num(hex.x),
49253
- y: num(hex.y),
49254
- terrain: str(hex.terrain),
49255
- terrainSprite: hex.terrainSprite == null ? void 0 : str(hex.terrainSprite)
49266
+ () => entityTiles.map((t) => ({
49267
+ x: num(t.x),
49268
+ y: num(t.y),
49269
+ terrain: str(t.terrain),
49270
+ terrainSprite: t.terrainSprite == null ? void 0 : str(t.terrainSprite),
49271
+ passable: t.passable !== false
49256
49272
  })),
49257
- [hexes]
49273
+ [entityTiles]
49258
49274
  );
49259
49275
  const tiles = propTiles ?? derivedTiles;
49260
49276
  const baseUnits = useMemo(
49261
- () => propUnits ?? heroes.map((hero) => ({
49262
- id: str(hero.id),
49263
- position: heroPosition(hero),
49264
- name: str(hero.name),
49265
- team: heroOwner(hero) === "enemy" ? "enemy" : "player",
49266
- health: 100,
49267
- maxHealth: 100,
49268
- sprite: hero.sprite == null ? void 0 : str(hero.sprite)
49277
+ () => propUnits ?? entityUnits.map((u) => ({
49278
+ id: str(u.id),
49279
+ position: heroPosition(u),
49280
+ name: str(u.name),
49281
+ // lolo uses `team` field (not `owner`)
49282
+ team: str(u.team) === "enemy" ? "enemy" : "player",
49283
+ health: num(u.health) || 100,
49284
+ maxHealth: num(u.maxHealth) || 100,
49285
+ sprite: u.sprite == null ? void 0 : str(u.sprite)
49269
49286
  })),
49270
- [heroes, propUnits]
49287
+ [entityUnits, propUnits]
49288
+ );
49289
+ const gameUnits = baseUnits;
49290
+ const selectedHero = useMemo(
49291
+ () => gameUnits.find((u) => u.id === selectedHeroId) ?? null,
49292
+ [gameUnits, selectedHeroId]
49271
49293
  );
49272
49294
  const MOVE_SPEED_MS_PER_TILE = 300;
49273
49295
  const movementAnimRef = useRef(null);
@@ -49313,51 +49335,50 @@ function WorldMapBoard({
49313
49335
  const validMoves = useMemo(() => {
49314
49336
  if (!selectedHero || heroMovement(selectedHero) <= 0) return [];
49315
49337
  const sp = heroPosition(selectedHero);
49316
- const sOwner = heroOwner(selectedHero);
49338
+ const sTeam = str(selectedHero.team);
49317
49339
  const range = heroMovement(selectedHero);
49318
49340
  const moves = [];
49319
- hexes.forEach((hex) => {
49320
- const hx = num(hex.x);
49321
- const hy = num(hex.y);
49322
- if (!hexPassable(hex)) return;
49323
- if (hx === sp.x && hy === sp.y) return;
49324
- if (!isInRange(sp, { x: hx, y: hy }, range)) return;
49325
- if (heroes.some((h) => {
49326
- const hp = heroPosition(h);
49327
- return hp.x === hx && hp.y === hy && heroOwner(h) === sOwner;
49341
+ tiles.forEach((t) => {
49342
+ const tx = t.x;
49343
+ const ty = t.y;
49344
+ if (t.passable === false) return;
49345
+ if (tx === sp.x && ty === sp.y) return;
49346
+ if (!isInRange(sp, { x: tx, y: ty }, range)) return;
49347
+ if (gameUnits.some((u) => {
49348
+ const up = u.position ?? { x: u.x ?? -1, y: u.y ?? -1 };
49349
+ return up.x === tx && up.y === ty && str(u.team) === sTeam;
49328
49350
  })) return;
49329
- moves.push({ x: hx, y: hy });
49351
+ moves.push({ x: tx, y: ty });
49330
49352
  });
49331
49353
  return moves;
49332
- }, [selectedHero, hexes, heroes, isInRange]);
49354
+ }, [selectedHero, tiles, gameUnits, isInRange]);
49333
49355
  const attackTargets = useMemo(() => {
49334
49356
  if (!selectedHero || heroMovement(selectedHero) <= 0) return [];
49335
49357
  const sp = heroPosition(selectedHero);
49336
- const sOwner = heroOwner(selectedHero);
49358
+ const sTeam = str(selectedHero.team);
49337
49359
  const range = heroMovement(selectedHero);
49338
- return heroes.filter((h) => heroOwner(h) !== sOwner).filter((h) => isInRange(sp, heroPosition(h), range)).map((h) => heroPosition(h));
49339
- }, [selectedHero, heroes, isInRange]);
49340
- const maxY = Math.max(...hexes.map((h) => num(h.y)), 0);
49360
+ 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 });
49361
+ }, [selectedHero, gameUnits, isInRange]);
49362
+ const maxY = Math.max(...tiles.map((t) => t.y), 0);
49341
49363
  const baseOffsetX = (maxY + 1) * (TILE_WIDTH * scale / 2);
49342
49364
  const tileToScreen = useCallback(
49343
49365
  (tx, ty) => isoToScreen(tx, ty, scale, baseOffsetX),
49344
49366
  [scale, baseOffsetX]
49345
49367
  );
49346
49368
  const hoveredHex = useMemo(
49347
- () => hoveredTile ? hexes.find((h) => num(h.x) === hoveredTile.x && num(h.y) === hoveredTile.y) ?? null : null,
49348
- [hoveredTile, hexes]
49369
+ () => hoveredTile ? tiles.find((t) => t.x === hoveredTile.x && t.y === hoveredTile.y) ?? null : null,
49370
+ [hoveredTile, tiles]
49349
49371
  );
49350
49372
  const hoveredHero = useMemo(
49351
- () => hoveredTile ? heroes.find((h) => {
49352
- const hp = heroPosition(h);
49353
- return hp.x === hoveredTile.x && hp.y === hoveredTile.y;
49373
+ () => hoveredTile ? gameUnits.find((u) => {
49374
+ const up = u.position ?? { x: u.x ?? -1, y: u.y ?? -1 };
49375
+ return up.x === hoveredTile.x && up.y === hoveredTile.y;
49354
49376
  }) ?? null : null,
49355
- [hoveredTile, heroes]
49377
+ [hoveredTile, gameUnits]
49356
49378
  );
49357
49379
  const handleTileClick = useCallback((x, y) => {
49358
49380
  if (movementAnimRef.current) return;
49359
- const hex = hexes.find((h) => num(h.x) === x && num(h.y) === y);
49360
- if (!hex) return;
49381
+ const tile = tiles.find((t) => t.x === x && t.y === y);
49361
49382
  if (tileClickEvent) {
49362
49383
  eventBus.emit(`UI:${tileClickEvent}`, { x, y });
49363
49384
  }
@@ -49368,38 +49389,39 @@ function WorldMapBoard({
49368
49389
  if (heroMoveEvent) {
49369
49390
  eventBus.emit(`UI:${heroMoveEvent}`, { heroId, toX: x, toY: y });
49370
49391
  }
49371
- const feature = str(hex.feature);
49392
+ const feature = tile ? str(tile.feature) : "";
49372
49393
  if (feature && feature !== "none") {
49373
- onFeatureEnter?.(heroId, hex);
49394
+ const tileRow = tile;
49395
+ onFeatureEnter?.(heroId, tileRow);
49374
49396
  if (featureEnterEvent) {
49375
- eventBus.emit(`UI:${featureEnterEvent}`, { heroId, feature, hex });
49397
+ eventBus.emit(`UI:${featureEnterEvent}`, { heroId, feature, hex: tileRow });
49376
49398
  }
49377
49399
  }
49378
49400
  });
49379
49401
  return;
49380
49402
  }
49381
- const enemy = heroes.find((h) => {
49382
- const hp = heroPosition(h);
49383
- return hp.x === x && hp.y === y && heroOwner(h) === "enemy";
49403
+ const enemy = gameUnits.find((u) => {
49404
+ const up = u.position ?? { x: u.x ?? -1, y: u.y ?? -1 };
49405
+ return up.x === x && up.y === y && str(u.team) === "enemy";
49384
49406
  });
49385
49407
  if (selectedHero && enemy && attackTargets.some((t) => t.x === x && t.y === y)) {
49386
49408
  const attackerId = str(selectedHero.id);
49387
- const defenderId = str(enemy.id);
49409
+ const defenderId = enemy.id;
49388
49410
  onBattleEncounter?.(attackerId, defenderId);
49389
49411
  if (battleEncounterEvent) {
49390
49412
  eventBus.emit(`UI:${battleEncounterEvent}`, { attackerId, defenderId });
49391
49413
  }
49392
49414
  }
49393
- }, [hexes, heroes, selectedHero, validMoves, attackTargets, startMoveAnimation, onHeroMove, onFeatureEnter, onBattleEncounter, eventBus, tileClickEvent, heroMoveEvent, featureEnterEvent, battleEncounterEvent]);
49415
+ }, [tiles, gameUnits, selectedHero, validMoves, attackTargets, startMoveAnimation, onHeroMove, onFeatureEnter, onBattleEncounter, eventBus, tileClickEvent, heroMoveEvent, featureEnterEvent, battleEncounterEvent]);
49394
49416
  const handleUnitClick = useCallback((unitId) => {
49395
- const hero = heroes.find((h) => str(h.id) === unitId);
49396
- if (hero && (heroOwner(hero) === "player" || allowMoveAllHeroes)) {
49417
+ const unit = gameUnits.find((u) => u.id === unitId);
49418
+ if (unit && (str(unit.team) === "player" || allowMoveAllHeroes)) {
49397
49419
  onHeroSelect?.(unitId);
49398
49420
  if (heroSelectEvent) {
49399
49421
  eventBus.emit(`UI:${heroSelectEvent}`, { heroId: unitId });
49400
49422
  }
49401
49423
  }
49402
- }, [heroes, onHeroSelect, allowMoveAllHeroes, eventBus, heroSelectEvent]);
49424
+ }, [gameUnits, onHeroSelect, allowMoveAllHeroes, eventBus, heroSelectEvent]);
49403
49425
  const selectHero = useCallback((id) => {
49404
49426
  onHeroSelect?.(id);
49405
49427
  if (heroSelectEvent) {