@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.
@@ -10384,15 +10384,15 @@ function BattleBoard({
10384
10384
  const board = boardEntity(entity) ?? {};
10385
10385
  const tiles = propTiles ?? (Array.isArray(board.tiles) ? board.tiles : []);
10386
10386
  const features = propFeatures ?? (Array.isArray(board.features) ? board.features : []);
10387
- const boardWidth = num(board.boardWidth, 8);
10388
- const boardHeight = num(board.boardHeight, 6);
10387
+ const boardWidth = num(board.gridWidth ?? board.boardWidth, 8);
10388
+ const boardHeight = num(board.gridHeight ?? board.boardHeight, 6);
10389
10389
  const assetManifest = propAssetManifest ?? board.assetManifest;
10390
10390
  const backgroundImage = board.backgroundImage;
10391
10391
  const units = rows(board.units);
10392
10392
  const selectedUnitId = board.selectedUnitId ?? null;
10393
10393
  const currentPhase = str(board.phase) || "observation";
10394
10394
  const currentTurn = num(board.turn, 1);
10395
- const gameResult = board.gameResult ?? null;
10395
+ const gameResult = board.result ?? null;
10396
10396
  const eventBus = useEventBus();
10397
10397
  const { t } = useTranslate();
10398
10398
  const [hoveredTile, setHoveredTile] = useState(null);
@@ -10413,7 +10413,7 @@ function BattleBoard({
10413
10413
  const validMoves = useMemo(() => {
10414
10414
  if (!selectedUnit || currentPhase !== "movement") return [];
10415
10415
  const moves = [];
10416
- const range = num(selectedUnit.movement);
10416
+ const range = num(board.movementRange, 2);
10417
10417
  const origin = unitPosition(selectedUnit);
10418
10418
  for (let dy = -range; dy <= range; dy++) {
10419
10419
  for (let dx = -range; dx <= range; dx++) {
@@ -18698,15 +18698,24 @@ function CastleBoard({
18698
18698
  featureClickEvent,
18699
18699
  unitClickEvent,
18700
18700
  tileClickEvent,
18701
+ playAgainEvent,
18701
18702
  className
18702
18703
  }) {
18703
18704
  const eventBus = useEventBus();
18705
+ const { t } = useTranslate();
18704
18706
  const resolved = boardEntity(entity);
18705
18707
  const tiles = propTiles ?? (Array.isArray(resolved?.tiles) ? resolved.tiles : []);
18706
18708
  const features = propFeatures ?? (Array.isArray(resolved?.features) ? resolved.features : []);
18707
18709
  const units = propUnits ?? (Array.isArray(resolved?.units) ? resolved.units : []);
18708
18710
  const assetManifest = propAssetManifest ?? resolved?.assetManifest;
18709
18711
  const backgroundImage = resolved?.backgroundImage;
18712
+ const gold = num(resolved?.gold);
18713
+ const health = num(resolved?.health);
18714
+ const maxHealth = num(resolved?.maxHealth);
18715
+ const wave = num(resolved?.wave);
18716
+ const tickCount = num(resolved?.tickCount);
18717
+ const buildings = rows(resolved?.buildings);
18718
+ const result = str(resolved?.result) || "none";
18710
18719
  const [hoveredTile, setHoveredTile] = useState(null);
18711
18720
  const [selectedFeature, setSelectedFeature] = useState(null);
18712
18721
  const hoveredFeature = useMemo(() => {
@@ -18719,7 +18728,7 @@ function CastleBoard({
18719
18728
  (u) => u.position?.x === hoveredTile.x && u.position?.y === hoveredTile.y
18720
18729
  ) ?? null;
18721
18730
  }, [hoveredTile, units]);
18722
- const maxY = Math.max(...tiles.map((t) => t.y), 0);
18731
+ const maxY = Math.max(...tiles.map((t2) => t2.y), 0);
18723
18732
  const baseOffsetX = (maxY + 1) * (TILE_WIDTH * scale / 2);
18724
18733
  const tileToScreen = useCallback(
18725
18734
  (tx, ty) => isoToScreen(tx, ty, scale, baseOffsetX),
@@ -18754,6 +18763,11 @@ function CastleBoard({
18754
18763
  }
18755
18764
  }, [units, onUnitClick, unitClickEvent, eventBus]);
18756
18765
  const clearSelection = useCallback(() => setSelectedFeature(null), []);
18766
+ const handlePlayAgain = useCallback(() => {
18767
+ if (playAgainEvent) {
18768
+ eventBus.emit(`UI:${playAgainEvent}`, {});
18769
+ }
18770
+ }, [playAgainEvent, eventBus]);
18757
18771
  const ctx = useMemo(
18758
18772
  () => ({
18759
18773
  hoveredTile,
@@ -18762,11 +18776,18 @@ function CastleBoard({
18762
18776
  selectedFeature,
18763
18777
  clearSelection,
18764
18778
  tileToScreen,
18765
- scale
18779
+ scale,
18780
+ gold,
18781
+ health,
18782
+ maxHealth,
18783
+ wave,
18784
+ tickCount,
18785
+ buildings,
18786
+ result
18766
18787
  }),
18767
- [hoveredTile, hoveredFeature, hoveredUnit, selectedFeature, clearSelection, tileToScreen, scale]
18788
+ [hoveredTile, hoveredFeature, hoveredUnit, selectedFeature, clearSelection, tileToScreen, scale, gold, health, maxHealth, wave, tickCount, buildings, result]
18768
18789
  );
18769
- return /* @__PURE__ */ jsxs("div", { className: cn("castle-board min-h-screen flex flex-col bg-background", className), children: [
18790
+ return /* @__PURE__ */ jsxs("div", { className: cn("castle-board relative min-h-screen flex flex-col bg-background", className), children: [
18770
18791
  header && header(ctx),
18771
18792
  /* @__PURE__ */ jsxs("div", { className: "flex flex-1 overflow-hidden", children: [
18772
18793
  /* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-auto p-4 relative", children: [
@@ -18791,7 +18812,29 @@ function CastleBoard({
18791
18812
  ] }),
18792
18813
  sidePanel && /* @__PURE__ */ jsx("div", { className: "w-96 shrink-0 border-l border-border bg-surface overflow-y-auto", children: sidePanel(ctx) })
18793
18814
  ] }),
18794
- footer && footer(ctx)
18815
+ footer && footer(ctx),
18816
+ 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: [
18817
+ /* @__PURE__ */ jsx(
18818
+ Typography,
18819
+ {
18820
+ variant: "h2",
18821
+ className: cn(
18822
+ "text-4xl font-black tracking-widest uppercase",
18823
+ result === "victory" ? "text-warning" : "text-error"
18824
+ ),
18825
+ children: result === "victory" ? t("battle.victory") : t("battle.defeat")
18826
+ }
18827
+ ),
18828
+ /* @__PURE__ */ jsx(
18829
+ Button,
18830
+ {
18831
+ variant: "primary",
18832
+ className: "px-8 py-3 font-semibold",
18833
+ onClick: handlePlayAgain,
18834
+ children: t("battle.playAgain")
18835
+ }
18836
+ )
18837
+ ] }) })
18795
18838
  ] });
18796
18839
  }
18797
18840
  var init_CastleBoard = __esm({
@@ -18799,6 +18842,10 @@ var init_CastleBoard = __esm({
18799
18842
  "use client";
18800
18843
  init_cn();
18801
18844
  init_useEventBus();
18845
+ init_Box();
18846
+ init_Button();
18847
+ init_Typography();
18848
+ init_Stack();
18802
18849
  init_IsometricCanvas();
18803
18850
  init_boardEntity();
18804
18851
  init_isometric();
@@ -19721,22 +19768,16 @@ function ClassifierBoard({
19721
19768
  const { emit } = useEventBus();
19722
19769
  const { t } = useTranslate();
19723
19770
  const resolved = boardEntity(entity);
19724
- const [localAssignments, setLocalAssignments] = useState({});
19725
19771
  const [headerError, setHeaderError] = useState(false);
19726
- const [localSubmitted, setLocalSubmitted] = useState(false);
19727
- const [localAttempts, setLocalAttempts] = useState(0);
19728
- const [showHint, setShowHint] = useState(false);
19729
19772
  const items = Array.isArray(resolved?.items) ? resolved.items : [];
19730
19773
  const categories = Array.isArray(resolved?.categories) ? resolved.categories : [];
19731
- const entityResult = str(resolved?.result);
19732
- const entityDrivesResult = entityResult.length > 0;
19733
- const entityHasAssignments = items.some((item) => item.assignedCategory != null);
19734
- const assignments = entityHasAssignments ? items.reduce((acc, item) => {
19735
- if (item.assignedCategory != null) acc[item.id] = item.assignedCategory;
19774
+ const result = str(resolved?.result);
19775
+ const submitted = result === "win";
19776
+ const attempts = num(resolved?.attempts);
19777
+ const assignments = items.reduce((acc, item) => {
19778
+ if (item.assignedCategory != null && item.assignedCategory !== "") acc[item.id] = item.assignedCategory;
19736
19779
  return acc;
19737
- }, {}) : localAssignments;
19738
- const attempts = entityDrivesResult ? num(resolved?.attempts) : localAttempts;
19739
- const submitted = entityDrivesResult || localSubmitted;
19780
+ }, {});
19740
19781
  const unassignedItems = items.filter((item) => !assignments[item.id]);
19741
19782
  const allAssigned = items.length > 0 && Object.keys(assignments).length === items.length;
19742
19783
  const results = submitted ? items.map((item) => ({
@@ -19744,54 +19785,25 @@ function ClassifierBoard({
19744
19785
  assigned: assignments[item.id],
19745
19786
  correct: assignments[item.id] === item.correctCategory
19746
19787
  })) : [];
19747
- const allCorrect = entityDrivesResult ? entityResult === "success" : results.length > 0 && results.every((r) => r.correct);
19788
+ const allCorrect = result === "win";
19748
19789
  const correctCount = results.filter((r) => r.correct).length;
19790
+ const showHint = attempts >= 2 && Boolean(str(resolved?.hint));
19749
19791
  const handleAssign = (itemId, categoryId) => {
19750
19792
  if (submitted) return;
19751
- if (assignEvent) {
19752
- emit(`UI:${assignEvent}`, { itemId, categoryId });
19753
- }
19754
- if (!entityHasAssignments) {
19755
- setLocalAssignments((prev) => ({ ...prev, [itemId]: categoryId }));
19756
- }
19793
+ if (assignEvent) emit(`UI:${assignEvent}`, { itemId, categoryId });
19757
19794
  };
19758
19795
  const handleUnassign = (itemId) => {
19759
19796
  if (submitted) return;
19760
- if (!entityHasAssignments) {
19761
- setLocalAssignments((prev) => {
19762
- const next = { ...prev };
19763
- delete next[itemId];
19764
- return next;
19765
- });
19766
- }
19797
+ if (assignEvent) emit(`UI:${assignEvent}`, { itemId, categoryId: "" });
19767
19798
  };
19768
- const handleSubmit = useCallback(() => {
19769
- if (checkEvent) {
19770
- emit(`UI:${checkEvent}`, {});
19771
- }
19772
- if (!entityDrivesResult) {
19773
- setLocalSubmitted(true);
19774
- setLocalAttempts((a) => a + 1);
19775
- const correct = items.every((item) => assignments[item.id] === item.correctCategory);
19776
- if (correct) {
19777
- emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
19778
- }
19779
- }
19780
- }, [checkEvent, entityDrivesResult, items, assignments, attempts, completeEvent, emit]);
19781
- const handleReset = () => {
19782
- if (!entityDrivesResult) setLocalSubmitted(false);
19783
- if (attempts >= 2 && str(resolved?.hint)) {
19784
- setShowHint(true);
19799
+ const handleSubmit = () => {
19800
+ if (checkEvent) emit(`UI:${checkEvent}`, {});
19801
+ if (allAssigned && items.every((item) => assignments[item.id] === item.correctCategory)) {
19802
+ emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
19785
19803
  }
19786
19804
  };
19787
19805
  const handleFullReset = () => {
19788
- if (playAgainEvent) {
19789
- emit(`UI:${playAgainEvent}`, {});
19790
- }
19791
- setLocalAssignments({});
19792
- setLocalSubmitted(false);
19793
- setLocalAttempts(0);
19794
- setShowHint(false);
19806
+ if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
19795
19807
  };
19796
19808
  if (!resolved) return null;
19797
19809
  const theme = resolved.theme ?? void 0;
@@ -19830,17 +19842,17 @@ function ClassifierBoard({
19830
19842
  /* @__PURE__ */ jsx(Badge, { size: "sm", children: catItems.length })
19831
19843
  ] }),
19832
19844
  /* @__PURE__ */ jsx(HStack, { gap: "xs", className: "flex-wrap min-h-[2rem]", children: catItems.map((item) => {
19833
- const result = results.find((r) => r.item.id === item.id);
19845
+ const result2 = results.find((r) => r.item.id === item.id);
19834
19846
  return /* @__PURE__ */ jsxs(
19835
19847
  Badge,
19836
19848
  {
19837
19849
  size: "sm",
19838
- className: `cursor-pointer ${result ? result.correct ? "border-success bg-success/10" : "border-error bg-error/10" : ""}`,
19850
+ className: `cursor-pointer ${result2 ? result2.correct ? "border-success bg-success/10" : "border-error bg-error/10" : ""}`,
19839
19851
  onClick: () => handleUnassign(item.id),
19840
19852
  children: [
19841
19853
  item.iconUrl && /* @__PURE__ */ jsx("img", { src: item.iconUrl, alt: "", className: "w-3 h-3 object-contain inline-block" }),
19842
19854
  item.label,
19843
- result && /* @__PURE__ */ jsx(Icon, { icon: result.correct ? CheckCircle : XCircle, size: "xs", className: result.correct ? "text-success" : "text-error" })
19855
+ result2 && /* @__PURE__ */ jsx(Icon, { icon: result2.correct ? CheckCircle : XCircle, size: "xs", className: result2.correct ? "text-success" : "text-error" })
19844
19856
  ]
19845
19857
  },
19846
19858
  item.id
@@ -19869,10 +19881,10 @@ function ClassifierBoard({
19869
19881
  ] }) }),
19870
19882
  showHint && hint && /* @__PURE__ */ jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsx(Typography, { variant: "body", children: hint }) }),
19871
19883
  /* @__PURE__ */ jsxs(HStack, { gap: "sm", justify: "center", children: [
19872
- !submitted ? /* @__PURE__ */ jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: !allAssigned, children: [
19884
+ !submitted && /* @__PURE__ */ jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: !allAssigned, children: [
19873
19885
  /* @__PURE__ */ jsx(Icon, { icon: Send, size: "sm" }),
19874
19886
  t("classifier.check")
19875
- ] }) : !allCorrect ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleReset, children: t("classifier.tryAgain") }) : null,
19887
+ ] }),
19876
19888
  /* @__PURE__ */ jsxs(Button, { variant: "secondary", onClick: handleFullReset, children: [
19877
19889
  /* @__PURE__ */ jsx(Icon, { icon: RotateCcw, size: "sm" }),
19878
19890
  t("classifier.reset")
@@ -20159,6 +20171,7 @@ function getComboScale(combo) {
20159
20171
  return "";
20160
20172
  }
20161
20173
  function ComboCounter({
20174
+ assetUrl = DEFAULT_ASSET_URL,
20162
20175
  combo = 5,
20163
20176
  multiplier,
20164
20177
  streak,
@@ -20178,6 +20191,17 @@ function ComboCounter({
20178
20191
  className
20179
20192
  ),
20180
20193
  children: [
20194
+ assetUrl && /* @__PURE__ */ jsx(
20195
+ "img",
20196
+ {
20197
+ src: assetUrl,
20198
+ alt: "combo",
20199
+ width: 24,
20200
+ height: 24,
20201
+ style: { imageRendering: "pixelated", objectFit: "contain" },
20202
+ className: "flex-shrink-0 mb-0.5"
20203
+ }
20204
+ ),
20181
20205
  /* @__PURE__ */ jsx("span", { className: cn("font-black tabular-nums leading-none", sizes.combo, getComboIntensity(combo)), children: combo }),
20182
20206
  /* @__PURE__ */ jsx("span", { className: cn("font-bold uppercase tracking-wider text-muted-foreground", sizes.label), children: "combo" }),
20183
20207
  multiplier != null && multiplier > 1 && /* @__PURE__ */ jsxs("span", { className: cn("font-bold text-warning tabular-nums", sizes.multiplier), children: [
@@ -20192,10 +20216,11 @@ function ComboCounter({
20192
20216
  }
20193
20217
  );
20194
20218
  }
20195
- var sizeMap4;
20219
+ var DEFAULT_ASSET_URL, sizeMap4;
20196
20220
  var init_ComboCounter = __esm({
20197
20221
  "components/game/atoms/ComboCounter.tsx"() {
20198
20222
  init_cn();
20223
+ DEFAULT_ASSET_URL = "https://almadar-kflow-assets.web.app/shared/effects/flash/flash00.png";
20199
20224
  sizeMap4 = {
20200
20225
  sm: { combo: "text-lg", label: "text-xs", multiplier: "text-xs" },
20201
20226
  md: { combo: "text-2xl", label: "text-xs", multiplier: "text-sm" },
@@ -20604,7 +20629,7 @@ var init_CounterTemplate = __esm({
20604
20629
  }
20605
20630
  });
20606
20631
  function ItemSlot({
20607
- assetUrl = DEFAULT_ASSET_URL,
20632
+ assetUrl = DEFAULT_ASSET_URL2,
20608
20633
  icon = "sword",
20609
20634
  label = "Iron Sword",
20610
20635
  quantity,
@@ -20663,7 +20688,7 @@ function ItemSlot({
20663
20688
  }
20664
20689
  );
20665
20690
  }
20666
- var sizeMap5, rarityBorderMap, rarityGlowMap, DEFAULT_ASSET_URL, assetSizeMap;
20691
+ var sizeMap5, rarityBorderMap, rarityGlowMap, DEFAULT_ASSET_URL2, assetSizeMap;
20667
20692
  var init_ItemSlot = __esm({
20668
20693
  "components/game/atoms/ItemSlot.tsx"() {
20669
20694
  "use client";
@@ -20688,7 +20713,7 @@ var init_ItemSlot = __esm({
20688
20713
  epic: "shadow-lg",
20689
20714
  legendary: "shadow-lg"
20690
20715
  };
20691
- DEFAULT_ASSET_URL = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
20716
+ DEFAULT_ASSET_URL2 = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
20692
20717
  assetSizeMap = {
20693
20718
  sm: 28,
20694
20719
  md: 40,
@@ -27244,6 +27269,7 @@ var init_InventoryGrid = __esm({
27244
27269
  }
27245
27270
  });
27246
27271
  function WaypointMarker({
27272
+ assetUrl = DEFAULT_ASSET_URL3,
27247
27273
  label,
27248
27274
  icon,
27249
27275
  active = true,
@@ -27282,7 +27308,17 @@ function WaypointMarker({
27282
27308
  active && !completed && "bg-info text-foreground",
27283
27309
  !active && !completed && "bg-muted"
27284
27310
  ),
27285
- children: completed ? checkIcon : icon && (typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon }) : /* @__PURE__ */ jsx(Icon, { icon }))
27311
+ children: completed ? checkIcon : assetUrl ? /* @__PURE__ */ jsx(
27312
+ "img",
27313
+ {
27314
+ src: assetUrl,
27315
+ alt: label,
27316
+ width: sizes.img,
27317
+ height: sizes.img,
27318
+ style: { imageRendering: "pixelated", objectFit: "contain" },
27319
+ className: "flex-shrink-0"
27320
+ }
27321
+ ) : icon ? typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon }) : /* @__PURE__ */ jsx(Icon, { icon }) : null
27286
27322
  }
27287
27323
  )
27288
27324
  ] }),
@@ -27299,15 +27335,16 @@ function WaypointMarker({
27299
27335
  )
27300
27336
  ] });
27301
27337
  }
27302
- var sizeMap12, checkIcon;
27338
+ var DEFAULT_ASSET_URL3, sizeMap12, checkIcon;
27303
27339
  var init_WaypointMarker = __esm({
27304
27340
  "components/game/atoms/WaypointMarker.tsx"() {
27305
27341
  init_cn();
27306
27342
  init_Icon();
27343
+ DEFAULT_ASSET_URL3 = "https://almadar-kflow-assets.web.app/shared/world-map/battle_marker.png";
27307
27344
  sizeMap12 = {
27308
- sm: { dot: "w-4 h-4", ring: "w-6 h-6", label: "text-xs mt-1" },
27309
- md: { dot: "w-6 h-6", ring: "w-8 h-8", label: "text-sm mt-1.5" },
27310
- lg: { dot: "w-8 h-8", ring: "w-10 h-10", label: "text-base mt-2" }
27345
+ sm: { dot: "w-4 h-4", ring: "w-6 h-6", label: "text-xs mt-1", img: 12 },
27346
+ md: { dot: "w-6 h-6", ring: "w-8 h-8", label: "text-sm mt-1.5", img: 18 },
27347
+ lg: { dot: "w-8 h-8", ring: "w-10 h-10", label: "text-base mt-2", img: 24 }
27311
27348
  };
27312
27349
  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" }) });
27313
27350
  WaypointMarker.displayName = "WaypointMarker";
@@ -29579,7 +29616,7 @@ var init_MapView = __esm({
29579
29616
  shadowSize: [41, 41]
29580
29617
  });
29581
29618
  L.Marker.prototype.options.icon = defaultIcon;
29582
- const { useEffect: useEffect74, useRef: useRef71, useCallback: useCallback114, useState: useState105 } = React77__default;
29619
+ const { useEffect: useEffect74, useRef: useRef71, useCallback: useCallback113, useState: useState105 } = React77__default;
29583
29620
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
29584
29621
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
29585
29622
  function MapUpdater({ centerLat, centerLng, zoom }) {
@@ -29625,7 +29662,7 @@ var init_MapView = __esm({
29625
29662
  }) {
29626
29663
  const eventBus = useEventBus2();
29627
29664
  const [clickedPosition, setClickedPosition] = useState105(null);
29628
- const handleMapClick = useCallback114((lat, lng) => {
29665
+ const handleMapClick = useCallback113((lat, lng) => {
29629
29666
  if (showClickedPin) {
29630
29667
  setClickedPosition({ lat, lng });
29631
29668
  }
@@ -29634,7 +29671,7 @@ var init_MapView = __esm({
29634
29671
  eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
29635
29672
  }
29636
29673
  }, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
29637
- const handleMarkerClick = useCallback114((marker) => {
29674
+ const handleMarkerClick = useCallback113((marker) => {
29638
29675
  onMarkerClick?.(marker);
29639
29676
  if (markerClickEvent) {
29640
29677
  eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
@@ -39077,44 +39114,27 @@ function DebuggerBoard({
39077
39114
  const { t } = useTranslate();
39078
39115
  const resolved = boardEntity(entity);
39079
39116
  const [headerError, setHeaderError] = useState(false);
39080
- const [showHint, setShowHint] = useState(false);
39081
39117
  const lines = Array.isArray(resolved?.lines) ? resolved.lines : [];
39082
- const result = resolved?.result ?? null;
39118
+ const result = str(resolved?.result) || "none";
39083
39119
  const attempts = num(resolved?.attempts);
39084
- const submitted = result != null;
39120
+ const submitted = result === "win";
39121
+ const showHint = !submitted && attempts >= 2 && Boolean(str(resolved?.hint));
39085
39122
  const bugLines = lines.filter((l) => l.isBug);
39086
39123
  const flaggedLines = lines.filter((l) => l.isFlagged);
39087
39124
  const correctFlags = lines.filter((l) => l.isBug && l.isFlagged);
39088
39125
  const falseFlags = lines.filter((l) => !l.isBug && l.isFlagged);
39089
- const allCorrect = result === "win";
39126
+ const allCorrect = submitted;
39090
39127
  const toggleLine = (lineId) => {
39091
39128
  if (submitted) return;
39092
- if (toggleFlagEvent) {
39093
- emit(`UI:${toggleFlagEvent}`, { lineId });
39094
- }
39129
+ if (toggleFlagEvent) emit(`UI:${toggleFlagEvent}`, { lineId });
39095
39130
  };
39096
39131
  const handleSubmit = useCallback(() => {
39097
- if (checkEvent) {
39098
- emit(`UI:${checkEvent}`, {});
39099
- }
39132
+ if (checkEvent) emit(`UI:${checkEvent}`, {});
39100
39133
  const correct = correctFlags.length === bugLines.length && falseFlags.length === 0;
39101
- if (correct) {
39102
- emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
39103
- }
39134
+ if (correct) emit(`UI:${completeEvent}`, { success: true, attempts: attempts + 1 });
39104
39135
  }, [checkEvent, correctFlags.length, bugLines.length, falseFlags.length, attempts, completeEvent, emit]);
39105
39136
  const handleReset = () => {
39106
- if (playAgainEvent) {
39107
- emit(`UI:${playAgainEvent}`, {});
39108
- }
39109
- if (attempts >= 2 && str(resolved?.hint)) {
39110
- setShowHint(true);
39111
- }
39112
- };
39113
- const handleFullReset = () => {
39114
- if (playAgainEvent) {
39115
- emit(`UI:${playAgainEvent}`, {});
39116
- }
39117
- setShowHint(false);
39137
+ if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
39118
39138
  };
39119
39139
  if (!resolved) return null;
39120
39140
  const theme = resolved.theme ?? void 0;
@@ -39138,7 +39158,7 @@ function DebuggerBoard({
39138
39158
  /* @__PURE__ */ jsx(Typography, { variant: "h4", weight: "bold", children: str(resolved.title) })
39139
39159
  ] }),
39140
39160
  /* @__PURE__ */ jsx(Typography, { variant: "body", children: str(resolved.description) }),
39141
- /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-muted-foreground", children: t("debugger.findBugs", { count: String(num(resolved.bugCount)) }) })
39161
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-muted-foreground", children: t("debugger.findBugs", { count: String(lines.filter((l) => l.isBug).length) }) })
39142
39162
  ] }) }),
39143
39163
  /* @__PURE__ */ jsx(Card, { className: "p-0 overflow-hidden", children: /* @__PURE__ */ jsx(VStack, { gap: "none", children: lines.map((line, i) => {
39144
39164
  const isFlagged = !!line.isFlagged;
@@ -39188,11 +39208,11 @@ function DebuggerBoard({
39188
39208
  ] }) }),
39189
39209
  showHint && hint && /* @__PURE__ */ jsx(Card, { className: "p-4 border-l-4 border-l-warning", children: /* @__PURE__ */ jsx(Typography, { variant: "body", children: hint }) }),
39190
39210
  /* @__PURE__ */ jsxs(HStack, { gap: "sm", justify: "center", children: [
39191
- !submitted ? /* @__PURE__ */ jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: flaggedLines.length === 0, children: [
39211
+ !submitted && /* @__PURE__ */ jsxs(Button, { variant: "primary", onClick: handleSubmit, disabled: flaggedLines.length === 0, children: [
39192
39212
  /* @__PURE__ */ jsx(Icon, { icon: Send, size: "sm" }),
39193
39213
  t("debugger.submit")
39194
- ] }) : !allCorrect ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleReset, children: t("debugger.tryAgain") }) : null,
39195
- /* @__PURE__ */ jsxs(Button, { variant: "secondary", onClick: handleFullReset, children: [
39214
+ ] }),
39215
+ /* @__PURE__ */ jsxs(Button, { variant: "secondary", onClick: handleReset, children: [
39196
39216
  /* @__PURE__ */ jsx(Icon, { icon: RotateCcw, size: "sm" }),
39197
39217
  t("debugger.reset")
39198
39218
  ] })
@@ -39782,6 +39802,7 @@ var init_DrawerSlot = __esm({
39782
39802
  }
39783
39803
  });
39784
39804
  function StateIndicator({
39805
+ assetUrl = DEFAULT_ASSET_URL4,
39785
39806
  state = "idle",
39786
39807
  label,
39787
39808
  size = "md",
@@ -39804,18 +39825,29 @@ function StateIndicator({
39804
39825
  className
39805
39826
  ),
39806
39827
  children: [
39807
- /* @__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 }) }),
39828
+ /* @__PURE__ */ jsx(Box, { as: "span", children: assetUrl ? /* @__PURE__ */ jsx(
39829
+ "img",
39830
+ {
39831
+ src: assetUrl,
39832
+ alt: displayLabel,
39833
+ width: 16,
39834
+ height: 16,
39835
+ style: { imageRendering: "pixelated", objectFit: "contain" },
39836
+ className: "flex-shrink-0"
39837
+ }
39838
+ ) : 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 }) }),
39808
39839
  /* @__PURE__ */ jsx(Box, { as: "span", children: displayLabel })
39809
39840
  ]
39810
39841
  }
39811
39842
  );
39812
39843
  }
39813
- var DEFAULT_STATE_STYLES, DEFAULT_STYLE, SIZE_CLASSES;
39844
+ var DEFAULT_ASSET_URL4, DEFAULT_STATE_STYLES, DEFAULT_STYLE, SIZE_CLASSES;
39814
39845
  var init_StateIndicator = __esm({
39815
39846
  "components/game/atoms/StateIndicator.tsx"() {
39816
39847
  init_Box();
39817
39848
  init_Icon();
39818
39849
  init_cn();
39850
+ DEFAULT_ASSET_URL4 = "https://almadar-kflow-assets.web.app/shared/isometric-dungeon/Isometric/chestClosed_E.png";
39819
39851
  DEFAULT_STATE_STYLES = {
39820
39852
  idle: { icon: "\u23F8", bgClass: "bg-muted" },
39821
39853
  active: { icon: "\u25B6", bgClass: "bg-success" },
@@ -40289,39 +40321,40 @@ function EventHandlerBoard({
40289
40321
  stepDurationMs = 800,
40290
40322
  playEvent,
40291
40323
  completeEvent,
40324
+ editRuleEvent,
40325
+ playAgainEvent,
40292
40326
  className
40293
40327
  }) {
40294
40328
  const { emit } = useEventBus();
40295
40329
  const { t } = useTranslate();
40296
40330
  const resolved = boardEntity(entity);
40297
- const entityObjects = rows(resolved?.objects);
40298
- const [objects, setObjects] = useState(() => [...entityObjects]);
40331
+ const objects = rows(resolved?.objects);
40332
+ const entityResult = str(resolved?.result) || "none";
40333
+ const isSuccess = entityResult === "win";
40334
+ const attempts = typeof resolved?.attempts === "number" ? resolved.attempts : 0;
40299
40335
  const [selectedObjectId, setSelectedObjectId] = useState(
40300
- entityObjects[0] ? objId(entityObjects[0]) : null
40336
+ objects[0] ? objId(objects[0]) : null
40301
40337
  );
40302
40338
  const [headerError, setHeaderError] = useState(false);
40303
- const [playState, setPlayState] = useState("editing");
40339
+ const [isPlaying, setIsPlaying] = useState(false);
40304
40340
  const [eventLog, setEventLog] = useState([]);
40305
- const [attempts, setAttempts] = useState(0);
40306
40341
  const timerRef = useRef(null);
40307
40342
  const logIdCounter = useRef(0);
40308
40343
  useEffect(() => () => {
40309
40344
  if (timerRef.current) clearTimeout(timerRef.current);
40310
40345
  }, []);
40311
- const selectedObject = objects.find((o) => objId(o) === selectedObjectId) || null;
40346
+ const selectedObject = objects.find((o) => objId(o) === selectedObjectId) ?? null;
40312
40347
  const handleRulesChange = useCallback((objectId, rules) => {
40313
- setObjects((prev) => prev.map(
40314
- (o) => objId(o) === objectId ? { ...o, rules } : o
40315
- ));
40316
- }, []);
40348
+ if (editRuleEvent) emit(`UI:${editRuleEvent}`, { objectId, rules });
40349
+ }, [editRuleEvent, emit]);
40317
40350
  const addLogEntry = useCallback((icon, message, status = "done") => {
40318
40351
  const id = `log-${logIdCounter.current++}`;
40319
40352
  setEventLog((prev) => [...prev, { id, timestamp: Date.now(), icon, message, status }]);
40320
40353
  }, []);
40321
40354
  const handlePlay = useCallback(() => {
40322
- if (playState !== "editing") return;
40355
+ if (isPlaying || isSuccess) return;
40323
40356
  if (playEvent) emit(`UI:${playEvent}`, {});
40324
- setPlayState("playing");
40357
+ setIsPlaying(true);
40325
40358
  setEventLog([]);
40326
40359
  const allRules = [];
40327
40360
  objects.forEach((obj) => {
@@ -40337,15 +40370,8 @@ function EventHandlerBoard({
40337
40370
  let goalReached = false;
40338
40371
  const processNext = () => {
40339
40372
  if (eventQueue.length === 0 || stepIdx > 20) {
40340
- if (goalReached) {
40341
- setPlayState("success");
40342
- if (completeEvent) {
40343
- emit(`UI:${completeEvent}`, { success: true });
40344
- }
40345
- } else {
40346
- setAttempts((prev) => prev + 1);
40347
- setPlayState("fail");
40348
- }
40373
+ setIsPlaying(false);
40374
+ if (goalReached && completeEvent) emit(`UI:${completeEvent}`, { success: true });
40349
40375
  return;
40350
40376
  }
40351
40377
  const currentEvent = eventQueue.shift();
@@ -40376,21 +40402,19 @@ function EventHandlerBoard({
40376
40402
  addLogEntry("\u{1F3AC}", t("eventHandler.simulationStarted", { events: triggers.join(", ") }), "active");
40377
40403
  }
40378
40404
  timerRef.current = setTimeout(processNext, stepDurationMs);
40379
- }, [playState, objects, resolved, stepDurationMs, playEvent, completeEvent, emit, addLogEntry, t]);
40405
+ }, [isPlaying, isSuccess, objects, resolved, stepDurationMs, playEvent, completeEvent, emit, addLogEntry, t]);
40380
40406
  const handleTryAgain = useCallback(() => {
40381
40407
  if (timerRef.current) clearTimeout(timerRef.current);
40382
- setPlayState("editing");
40408
+ setIsPlaying(false);
40383
40409
  setEventLog([]);
40384
40410
  }, []);
40385
40411
  const handleReset = useCallback(() => {
40386
40412
  if (timerRef.current) clearTimeout(timerRef.current);
40387
- const resetObjects = rows(resolved?.objects);
40388
- setObjects([...resetObjects]);
40389
- setPlayState("editing");
40413
+ setIsPlaying(false);
40390
40414
  setEventLog([]);
40391
- setSelectedObjectId(resetObjects[0] ? objId(resetObjects[0]) : null);
40392
- setAttempts(0);
40393
- }, [resolved?.objects]);
40415
+ setSelectedObjectId(objects[0] ? objId(objects[0]) : null);
40416
+ if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
40417
+ }, [objects, playAgainEvent, emit]);
40394
40418
  if (!resolved) return null;
40395
40419
  const objectViewers = objects.map((obj) => {
40396
40420
  const states = objStates(obj);
@@ -40459,12 +40483,12 @@ function EventHandlerBoard({
40459
40483
  {
40460
40484
  object: selectedObject,
40461
40485
  onRulesChange: handleRulesChange,
40462
- disabled: playState !== "editing"
40486
+ disabled: isPlaying
40463
40487
  }
40464
40488
  ),
40465
40489
  eventLog.length > 0 && /* @__PURE__ */ jsx(EventLog, { entries: eventLog }),
40466
- 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") }) }),
40467
- playState === "fail" && /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
40490
+ 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") }) }),
40491
+ !isPlaying && !isSuccess && attempts > 0 && /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
40468
40492
  /* @__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) }) }),
40469
40493
  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: [
40470
40494
  /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-accent font-bold shrink-0", children: "\u{1F4A1} " + t("game.hint") + ":" }),
@@ -40472,12 +40496,12 @@ function EventHandlerBoard({
40472
40496
  ] }) })
40473
40497
  ] }),
40474
40498
  /* @__PURE__ */ jsxs(HStack, { gap: "sm", children: [
40475
- playState === "fail" ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleTryAgain, children: "\u{1F504} " + t("puzzle.tryAgainButton") }) : /* @__PURE__ */ jsx(
40499
+ !isPlaying && !isSuccess && attempts > 0 ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleTryAgain, children: "\u{1F504} " + t("puzzle.tryAgainButton") }) : /* @__PURE__ */ jsx(
40476
40500
  Button,
40477
40501
  {
40478
40502
  variant: "primary",
40479
40503
  onClick: handlePlay,
40480
- disabled: playState !== "editing",
40504
+ disabled: isPlaying || isSuccess,
40481
40505
  children: "\u25B6 " + t("game.play")
40482
40506
  }
40483
40507
  ),
@@ -43145,21 +43169,6 @@ var init_ModalSlot = __esm({
43145
43169
  ModalSlot.displayName = "ModalSlot";
43146
43170
  }
43147
43171
  });
43148
- function getOpponentAction(strategy, actions, history) {
43149
- const actionIds = actions.map((a) => a.id);
43150
- switch (strategy) {
43151
- case "always-cooperate":
43152
- return actionIds[0];
43153
- case "always-defect":
43154
- return actionIds[actionIds.length - 1];
43155
- case "tit-for-tat":
43156
- if (history.length === 0) return actionIds[0];
43157
- return history[history.length - 1].playerAction;
43158
- case "random":
43159
- default:
43160
- return actionIds[Math.floor(Math.random() * actionIds.length)];
43161
- }
43162
- }
43163
43172
  function NegotiatorBoard({
43164
43173
  entity,
43165
43174
  completeEvent = "PUZZLE_COMPLETE",
@@ -43181,28 +43190,35 @@ function NegotiatorBoard({
43181
43190
  const playerTotal = num(resolved?.score);
43182
43191
  const isComplete = result !== "none" || totalRounds > 0 && currentRound >= totalRounds;
43183
43192
  const won = result === "win";
43184
- const opponentTotal = history.reduce((s, r) => s + r.opponentPayoff, 0);
43193
+ const lastPlayerAction = str(resolved?.lastPlayerAction);
43194
+ const lastOpponentAction = str(resolved?.lastOpponentAction);
43195
+ const lastPayoff = num(resolved?.lastPayoff);
43185
43196
  const actions = Array.isArray(resolved?.actions) ? resolved.actions : [];
43186
43197
  const payoffMatrix = Array.isArray(resolved?.payoffMatrix) ? resolved.payoffMatrix : [];
43198
+ const prevRoundRef = useRef(currentRound);
43199
+ useEffect(() => {
43200
+ if (currentRound > prevRoundRef.current && lastPlayerAction) {
43201
+ const opponentPayoffEntry = payoffMatrix.find(
43202
+ (p2) => p2.playerAction === lastPlayerAction && p2.opponentAction === lastOpponentAction
43203
+ );
43204
+ setHistory((prev) => [
43205
+ ...prev,
43206
+ {
43207
+ round: currentRound,
43208
+ playerAction: lastPlayerAction,
43209
+ opponentAction: lastOpponentAction,
43210
+ playerPayoff: lastPayoff,
43211
+ opponentPayoff: opponentPayoffEntry?.opponentPayoff ?? 0
43212
+ }
43213
+ ]);
43214
+ }
43215
+ prevRoundRef.current = currentRound;
43216
+ }, [currentRound, lastPlayerAction, lastOpponentAction, lastPayoff, payoffMatrix]);
43217
+ const opponentTotal = useMemo(() => history.reduce((s, r) => s + r.opponentPayoff, 0), [history]);
43187
43218
  const handleAction = useCallback((actionId) => {
43188
43219
  if (isComplete) return;
43189
- const opponentAction = getOpponentAction(str(resolved?.opponentStrategy) || "random", actions, history);
43190
- const payoff = payoffMatrix.find(
43191
- (p2) => p2.playerAction === actionId && p2.opponentAction === opponentAction
43192
- );
43193
- const playerPayoff = payoff?.playerPayoff ?? 0;
43194
- setHistory((prev) => [
43195
- ...prev,
43196
- {
43197
- round: prev.length + 1,
43198
- playerAction: actionId,
43199
- opponentAction,
43200
- playerPayoff,
43201
- opponentPayoff: payoff?.opponentPayoff ?? 0
43202
- }
43203
- ]);
43204
43220
  if (playRoundEvent) {
43205
- emit(`UI:${playRoundEvent}`, { playerAction: actionId, payoff: playerPayoff });
43221
+ emit(`UI:${playRoundEvent}`, { playerAction: actionId, payoff: 0 });
43206
43222
  }
43207
43223
  if (totalRounds > 0 && currentRound + 1 >= totalRounds) {
43208
43224
  if (finishEvent) {
@@ -43212,10 +43228,11 @@ function NegotiatorBoard({
43212
43228
  setShowHint(true);
43213
43229
  }
43214
43230
  }
43215
- }, [isComplete, resolved, totalRounds, currentRound, actions, payoffMatrix, history, playRoundEvent, finishEvent, emit]);
43231
+ }, [isComplete, resolved, totalRounds, currentRound, playRoundEvent, finishEvent, emit]);
43216
43232
  const handleReset = useCallback(() => {
43217
43233
  setHistory([]);
43218
43234
  setShowHint(false);
43235
+ prevRoundRef.current = 0;
43219
43236
  if (playAgainEvent) {
43220
43237
  emit(`UI:${playAgainEvent}`, {});
43221
43238
  }
@@ -43449,6 +43466,7 @@ var init_PricingPageTemplate = __esm({
43449
43466
  }
43450
43467
  });
43451
43468
  function ResourceCounter({
43469
+ assetUrl = DEFAULT_ASSET_URL5,
43452
43470
  icon,
43453
43471
  label = "Gold",
43454
43472
  value = 250,
@@ -43468,7 +43486,17 @@ function ResourceCounter({
43468
43486
  className
43469
43487
  ),
43470
43488
  children: [
43471
- icon && /* @__PURE__ */ jsx("span", { className: cn("flex-shrink-0", sizes.icon), children: typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon }) : /* @__PURE__ */ jsx(Icon, { icon }) }),
43489
+ assetUrl ? /* @__PURE__ */ jsx(
43490
+ "img",
43491
+ {
43492
+ src: assetUrl,
43493
+ alt: label,
43494
+ width: sizes.img,
43495
+ height: sizes.img,
43496
+ style: { imageRendering: "pixelated", objectFit: "contain" },
43497
+ className: "flex-shrink-0"
43498
+ }
43499
+ ) : 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,
43472
43500
  /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: label }),
43473
43501
  /* @__PURE__ */ jsxs("span", { className: cn("font-bold tabular-nums", color && (color in colorTokenClasses2 ? colorTokenClasses2[color] : color)), children: [
43474
43502
  value,
@@ -43481,7 +43509,7 @@ function ResourceCounter({
43481
43509
  }
43482
43510
  );
43483
43511
  }
43484
- var colorTokenClasses2, sizeMap15;
43512
+ var colorTokenClasses2, DEFAULT_ASSET_URL5, sizeMap15;
43485
43513
  var init_ResourceCounter = __esm({
43486
43514
  "components/game/atoms/ResourceCounter.tsx"() {
43487
43515
  init_cn();
@@ -43494,10 +43522,11 @@ var init_ResourceCounter = __esm({
43494
43522
  error: "text-error",
43495
43523
  muted: "text-muted-foreground"
43496
43524
  };
43525
+ DEFAULT_ASSET_URL5 = "https://almadar-kflow-assets.web.app/shared/world-map/gold_mine.png";
43497
43526
  sizeMap15 = {
43498
- sm: { wrapper: "text-xs gap-1 px-1.5 py-0.5", icon: "text-sm" },
43499
- md: { wrapper: "text-sm gap-1.5 px-2 py-1", icon: "text-base" },
43500
- lg: { wrapper: "text-base gap-2 px-3 py-1.5", icon: "text-lg" }
43527
+ sm: { wrapper: "text-xs gap-1 px-1.5 py-0.5", icon: "text-sm", img: 16 },
43528
+ md: { wrapper: "text-sm gap-1.5 px-2 py-1", icon: "text-base", img: 20 },
43529
+ lg: { wrapper: "text-base gap-2 px-3 py-1.5", icon: "text-lg", img: 28 }
43501
43530
  };
43502
43531
  ResourceCounter.displayName = "ResourceCounter";
43503
43532
  }
@@ -45605,17 +45634,24 @@ function SequencerBoard({
45605
45634
  const { emit } = useEventBus();
45606
45635
  const { t } = useTranslate();
45607
45636
  const resolved = boardEntity(entity);
45608
- const maxSlots = num(resolved?.maxSlots);
45637
+ const maxSlots = num(resolved?.maxSlots) || 3;
45609
45638
  const solutions = Array.isArray(resolved?.solutions) ? resolved.solutions : [];
45610
45639
  const availableActions = Array.isArray(resolved?.availableActions) ? resolved.availableActions : [];
45611
45640
  const allowDuplicates = resolved?.allowDuplicates !== false;
45641
+ const entitySlots = Array.isArray(resolved?.slots) ? resolved.slots : [];
45642
+ const entityResult = str(resolved?.result);
45643
+ const entityAttempts = num(resolved?.attempts);
45644
+ const entityCurrentStep = typeof resolved?.currentStep === "number" ? resolved.currentStep : -1;
45645
+ const slots = Array.from({ length: maxSlots }, (_, i) => {
45646
+ const entitySlot = entitySlots.find((s) => s.index === i);
45647
+ if (!entitySlot?.placedActionId) return void 0;
45648
+ return availableActions.find((a) => a.id === entitySlot.placedActionId);
45649
+ });
45650
+ const isSuccess = entityResult === "win";
45651
+ const attempts = entityAttempts;
45652
+ const isPlayingBack = entityCurrentStep >= 0 && !isSuccess;
45653
+ const currentStep = entityCurrentStep;
45612
45654
  const [headerError, setHeaderError] = useState(false);
45613
- const [slots, setSlots] = useState(
45614
- () => Array.from({ length: maxSlots }, () => void 0)
45615
- );
45616
- const [playState, setPlayState] = useState("idle");
45617
- const [currentStep, setCurrentStep] = useState(-1);
45618
- const [attempts, setAttempts] = useState(0);
45619
45655
  const [slotFeedback, setSlotFeedback] = useState(
45620
45656
  () => Array.from({ length: maxSlots }, () => null)
45621
45657
  );
@@ -45624,11 +45660,6 @@ function SequencerBoard({
45624
45660
  if (timerRef.current) clearTimeout(timerRef.current);
45625
45661
  }, []);
45626
45662
  const handleSlotDrop = useCallback((index, item) => {
45627
- setSlots((prev) => {
45628
- const next = [...prev];
45629
- next[index] = item;
45630
- return next;
45631
- });
45632
45663
  setSlotFeedback((prev) => {
45633
45664
  const next = [...prev];
45634
45665
  next[index] = null;
@@ -45638,11 +45669,6 @@ function SequencerBoard({
45638
45669
  if (placeEvent) emit(`UI:${placeEvent}`, { slotIndex: index, actionId: item.id });
45639
45670
  }, [emit, placeEvent]);
45640
45671
  const handleSlotRemove = useCallback((index) => {
45641
- setSlots((prev) => {
45642
- const next = [...prev];
45643
- next[index] = void 0;
45644
- return next;
45645
- });
45646
45672
  setSlotFeedback((prev) => {
45647
45673
  const next = [...prev];
45648
45674
  next[index] = null;
@@ -45653,48 +45679,34 @@ function SequencerBoard({
45653
45679
  }, [emit, removeEvent]);
45654
45680
  const handleReset = useCallback(() => {
45655
45681
  if (timerRef.current) clearTimeout(timerRef.current);
45656
- setSlots(Array.from({ length: maxSlots }, () => void 0));
45657
- setPlayState("idle");
45658
- setCurrentStep(-1);
45659
- setAttempts(0);
45660
45682
  setSlotFeedback(Array.from({ length: maxSlots }, () => null));
45661
45683
  if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
45662
45684
  }, [maxSlots, playAgainEvent, emit]);
45663
45685
  const filledSlots = slots.filter((s) => !!s);
45664
- const canPlay = filledSlots.length > 0 && playState === "idle";
45686
+ const canPlay = filledSlots.length > 0 && !isPlayingBack && !isSuccess;
45665
45687
  const handlePlay = useCallback(() => {
45666
45688
  if (!canPlay) return;
45667
45689
  setSlotFeedback(Array.from({ length: maxSlots }, () => null));
45668
45690
  emit("UI:PLAY_SOUND", { key: "confirm" });
45669
45691
  const sequence = slots.map((s) => s?.id || "");
45670
- if (playEvent) {
45671
- emit(`UI:${playEvent}`, { sequence });
45672
- }
45673
- setPlayState("playing");
45674
- setCurrentStep(0);
45692
+ const playerIds = slots.filter(Boolean).map((s) => s?.id || "");
45693
+ if (playEvent) emit(`UI:${playEvent}`, { sequence });
45675
45694
  let step = 0;
45676
45695
  const advance = () => {
45677
45696
  step++;
45697
+ emit("UI:STEP", { step });
45678
45698
  if (step >= maxSlots) {
45699
+ if (checkEvent) emit(`UI:${checkEvent}`, { sequence: playerIds });
45679
45700
  const playerSeq = slots.map((s) => s?.id);
45680
- const playerIds = slots.filter(Boolean).map((s) => s?.id || "");
45681
45701
  const success = solutions.some(
45682
45702
  (sol) => sol.length === playerIds.length && sol.every((id, i) => id === playerIds[i])
45683
45703
  );
45684
- if (checkEvent) emit(`UI:${checkEvent}`, { sequence: playerIds });
45685
45704
  if (success) {
45686
- setPlayState("success");
45687
- setCurrentStep(-1);
45688
45705
  emit("UI:PLAY_SOUND", { key: "levelComplete" });
45689
- if (completeEvent) {
45690
- emit(`UI:${completeEvent}`, { success: true, sequence: playerIds });
45691
- }
45706
+ if (completeEvent) emit(`UI:${completeEvent}`, { success: true, sequence: playerIds });
45692
45707
  } else {
45693
- setAttempts((prev) => prev + 1);
45694
45708
  const feedback = computeSlotFeedback(playerSeq, solutions);
45695
45709
  setSlotFeedback(feedback);
45696
- setPlayState("idle");
45697
- setCurrentStep(-1);
45698
45710
  emit("UI:PLAY_SOUND", { key: "fail" });
45699
45711
  const correctCount2 = feedback.filter((f3) => f3 === "correct").length;
45700
45712
  for (let ci = 0; ci < correctCount2; ci++) {
@@ -45704,7 +45716,6 @@ function SequencerBoard({
45704
45716
  }
45705
45717
  }
45706
45718
  } else {
45707
- setCurrentStep(step);
45708
45719
  timerRef.current = setTimeout(advance, stepDurationMs);
45709
45720
  }
45710
45721
  };
@@ -45754,7 +45765,7 @@ function SequencerBoard({
45754
45765
  /* @__PURE__ */ jsxs(VStack, { gap: "xs", children: [
45755
45766
  /* @__PURE__ */ jsxs(HStack, { className: "items-center justify-between", children: [
45756
45767
  /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-muted-foreground font-medium", children: t("sequencer.yourSequence") + ":" }),
45757
- hasFeedback && playState === "idle" && /* @__PURE__ */ jsxs(Typography, { variant: "caption", className: "text-muted-foreground", children: [
45768
+ hasFeedback && !isPlayingBack && !isSuccess && /* @__PURE__ */ jsxs(Typography, { variant: "caption", className: "text-muted-foreground", children: [
45758
45769
  `${correctCount}/${maxSlots} `,
45759
45770
  "\u2705"
45760
45771
  ] })
@@ -45766,7 +45777,7 @@ function SequencerBoard({
45766
45777
  maxSlots,
45767
45778
  onSlotDrop: handleSlotDrop,
45768
45779
  onSlotRemove: handleSlotRemove,
45769
- playing: playState === "playing",
45780
+ playing: isPlayingBack,
45770
45781
  currentStep,
45771
45782
  categoryColors,
45772
45783
  slotFeedback,
@@ -45774,7 +45785,7 @@ function SequencerBoard({
45774
45785
  }
45775
45786
  )
45776
45787
  ] }),
45777
- playState !== "playing" && /* @__PURE__ */ jsx(
45788
+ !isPlayingBack && /* @__PURE__ */ jsx(
45778
45789
  ActionPalette,
45779
45790
  {
45780
45791
  actions: availableActions,
@@ -45784,8 +45795,8 @@ function SequencerBoard({
45784
45795
  label: t("sequencer.dragActions")
45785
45796
  }
45786
45797
  ),
45787
- 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) }) }),
45788
- 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") }) }),
45798
+ 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) }) }),
45799
+ 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") }) }),
45789
45800
  /* @__PURE__ */ jsxs(HStack, { gap: "sm", children: [
45790
45801
  /* @__PURE__ */ jsx(
45791
45802
  Button,
@@ -46946,6 +46957,9 @@ function StateArchitectBoard({
46946
46957
  stepDurationMs = 600,
46947
46958
  testEvent,
46948
46959
  completeEvent,
46960
+ addTransitionEvent,
46961
+ removeTransitionEvent,
46962
+ playAgainEvent,
46949
46963
  className
46950
46964
  }) {
46951
46965
  const { emit } = useEventBus();
@@ -46958,14 +46972,16 @@ function StateArchitectBoard({
46958
46972
  const testCases = Array.isArray(resolved?.testCases) ? resolved.testCases : [];
46959
46973
  const entityTransitions = Array.isArray(resolved?.transitions) ? resolved.transitions : [];
46960
46974
  const entityVariables = rows(resolved?.variables);
46961
- const [transitions, setTransitions] = useState(entityTransitions);
46975
+ const transitions = entityTransitions;
46976
+ const attempts = typeof resolved?.attempts === "number" ? resolved.attempts : 0;
46977
+ const entityResult = str(resolved?.result) || "none";
46978
+ const isSuccess = entityResult === "win";
46979
+ const [isTesting, setIsTesting] = useState(false);
46962
46980
  const [headerError, setHeaderError] = useState(false);
46963
- const [playState, setPlayState] = useState("editing");
46964
46981
  const [currentState, setCurrentState] = useState(initialState);
46965
46982
  const [selectedState, setSelectedState] = useState(null);
46966
46983
  const [testResults, setTestResults] = useState([]);
46967
46984
  const [variables, setVariables] = useState(() => [...entityVariables]);
46968
- const [attempts, setAttempts] = useState(0);
46969
46985
  const timerRef = useRef(null);
46970
46986
  const [addingFrom, setAddingFrom] = useState(null);
46971
46987
  useEffect(() => () => {
@@ -46975,7 +46991,7 @@ function StateArchitectBoard({
46975
46991
  const GRAPH_H = 400;
46976
46992
  const positions = useMemo(() => layoutStates(entityStates, GRAPH_W, GRAPH_H), [entityStates]);
46977
46993
  const handleStateClick = useCallback((state) => {
46978
- if (playState !== "editing") return;
46994
+ if (isTesting) return;
46979
46995
  if (addingFrom) {
46980
46996
  if (addingFrom !== state) {
46981
46997
  const event = availableEvents[0] || "EVENT";
@@ -46985,20 +47001,20 @@ function StateArchitectBoard({
46985
47001
  to: state,
46986
47002
  event
46987
47003
  };
46988
- setTransitions((prev) => [...prev, newTrans]);
47004
+ if (addTransitionEvent) emit(`UI:${addTransitionEvent}`, { id: newTrans.id, from: newTrans.from, to: newTrans.to, event: newTrans.event });
46989
47005
  }
46990
47006
  setAddingFrom(null);
46991
47007
  } else {
46992
47008
  setSelectedState(state);
46993
47009
  }
46994
- }, [playState, addingFrom, availableEvents]);
47010
+ }, [isTesting, addingFrom, availableEvents, addTransitionEvent, emit]);
46995
47011
  const handleStartAddTransition = useCallback(() => {
46996
47012
  if (!selectedState) return;
46997
47013
  setAddingFrom(selectedState);
46998
47014
  }, [selectedState]);
46999
47015
  const handleRemoveTransition = useCallback((transId) => {
47000
- setTransitions((prev) => prev.filter((t2) => t2.id !== transId));
47001
- }, []);
47016
+ if (removeTransitionEvent) emit(`UI:${removeTransitionEvent}`, { id: transId });
47017
+ }, [removeTransitionEvent, emit]);
47002
47018
  const machine = useMemo(() => ({
47003
47019
  name: entityName,
47004
47020
  description: str(resolved?.description),
@@ -47012,16 +47028,16 @@ function StateArchitectBoard({
47012
47028
  }))
47013
47029
  }), [entityName, resolved, entityStates, currentState, transitions]);
47014
47030
  const handleTest = useCallback(() => {
47015
- if (playState !== "editing") return;
47031
+ if (isTesting) return;
47016
47032
  if (testEvent) emit(`UI:${testEvent}`, {});
47017
- setPlayState("testing");
47033
+ setIsTesting(true);
47018
47034
  setTestResults([]);
47019
47035
  const results = [];
47020
47036
  let testIdx = 0;
47021
47037
  const runNextTest = () => {
47022
47038
  if (testIdx >= testCases.length) {
47023
47039
  const allPassed = results.every((r) => r.passed);
47024
- setPlayState(allPassed ? "success" : "fail");
47040
+ setIsTesting(false);
47025
47041
  setTestResults(results);
47026
47042
  if (allPassed && completeEvent) {
47027
47043
  emit(`UI:${completeEvent}`, {
@@ -47029,9 +47045,6 @@ function StateArchitectBoard({
47029
47045
  passedTests: results.filter((r) => r.passed).length
47030
47046
  });
47031
47047
  }
47032
- if (!allPassed) {
47033
- setAttempts((prev) => prev + 1);
47034
- }
47035
47048
  return;
47036
47049
  }
47037
47050
  const testCase = testCases[testIdx];
@@ -47054,24 +47067,23 @@ function StateArchitectBoard({
47054
47067
  timerRef.current = setTimeout(runNextTest, stepDurationMs);
47055
47068
  };
47056
47069
  timerRef.current = setTimeout(runNextTest, stepDurationMs);
47057
- }, [playState, transitions, testCases, initialState, stepDurationMs, testEvent, completeEvent, emit]);
47070
+ }, [isTesting, transitions, testCases, initialState, stepDurationMs, testEvent, completeEvent, emit]);
47058
47071
  const handleTryAgain = useCallback(() => {
47059
47072
  if (timerRef.current) clearTimeout(timerRef.current);
47060
- setPlayState("editing");
47073
+ setIsTesting(false);
47061
47074
  setCurrentState(initialState);
47062
47075
  setTestResults([]);
47063
47076
  }, [initialState]);
47064
47077
  const handleReset = useCallback(() => {
47065
47078
  if (timerRef.current) clearTimeout(timerRef.current);
47066
- setTransitions(entityTransitions);
47067
- setPlayState("editing");
47079
+ setIsTesting(false);
47068
47080
  setCurrentState(initialState);
47069
47081
  setTestResults([]);
47070
47082
  setVariables([...entityVariables]);
47071
47083
  setSelectedState(null);
47072
47084
  setAddingFrom(null);
47073
- setAttempts(0);
47074
- }, [entityTransitions, initialState, entityVariables]);
47085
+ if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
47086
+ }, [initialState, entityVariables, playAgainEvent, emit]);
47075
47087
  const codeData = useMemo(() => ({
47076
47088
  name: entityName,
47077
47089
  states: entityStates,
@@ -47167,7 +47179,7 @@ function StateArchitectBoard({
47167
47179
  ]
47168
47180
  }
47169
47181
  ),
47170
- playState === "editing" && /* @__PURE__ */ jsx(HStack, { gap: "sm", children: /* @__PURE__ */ jsx(
47182
+ !isTesting && /* @__PURE__ */ jsx(HStack, { gap: "sm", children: /* @__PURE__ */ jsx(
47171
47183
  Button,
47172
47184
  {
47173
47185
  variant: "ghost",
@@ -47189,7 +47201,7 @@ function StateArchitectBoard({
47189
47201
  t2.guardHint,
47190
47202
  ")"
47191
47203
  ] }),
47192
- playState === "editing" && /* @__PURE__ */ jsx(
47204
+ !isTesting && /* @__PURE__ */ jsx(
47193
47205
  Button,
47194
47206
  {
47195
47207
  variant: "ghost",
@@ -47221,21 +47233,21 @@ function StateArchitectBoard({
47221
47233
  resolved.showCodeView !== false && /* @__PURE__ */ jsx(StateJsonView, { data: codeData, label: "View Code" })
47222
47234
  ] })
47223
47235
  ] }),
47224
- 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") }) }),
47225
- playState === "fail" && /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
47236
+ 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") }) }),
47237
+ !isTesting && !isSuccess && testResults.some((r) => !r.passed) && /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
47226
47238
  /* @__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]) }) }),
47227
- 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: [
47239
+ !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: [
47228
47240
  /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-accent font-bold shrink-0", children: "\u{1F4A1} " + t("game.hint") + ":" }),
47229
47241
  /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "text-foreground", children: hint })
47230
47242
  ] }) })
47231
47243
  ] }),
47232
47244
  /* @__PURE__ */ jsxs(HStack, { gap: "sm", children: [
47233
- playState === "fail" ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleTryAgain, children: "\u{1F504} " + t("puzzle.tryAgainButton") }) : /* @__PURE__ */ jsx(
47245
+ !isTesting && !isSuccess && testResults.some((r) => !r.passed) ? /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleTryAgain, children: "\u{1F504} " + t("puzzle.tryAgainButton") }) : /* @__PURE__ */ jsx(
47234
47246
  Button,
47235
47247
  {
47236
47248
  variant: "primary",
47237
47249
  onClick: handleTest,
47238
- disabled: playState !== "editing",
47250
+ disabled: isTesting,
47239
47251
  children: "\u25B6 " + t("game.runTests")
47240
47252
  }
47241
47253
  ),
@@ -47315,6 +47327,7 @@ function formatDuration(seconds) {
47315
47327
  return `${Math.round(seconds)}s`;
47316
47328
  }
47317
47329
  function StatusEffect({
47330
+ assetUrl = DEFAULT_ASSET_URL6,
47318
47331
  icon = "shield",
47319
47332
  label = "Shield",
47320
47333
  duration = 30,
@@ -47335,7 +47348,17 @@ function StatusEffect({
47335
47348
  ),
47336
47349
  title: label,
47337
47350
  children: [
47338
- /* @__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" }) }),
47351
+ /* @__PURE__ */ jsx("span", { className: cn("flex items-center justify-center", sizes.icon), children: assetUrl ? /* @__PURE__ */ jsx(
47352
+ "img",
47353
+ {
47354
+ src: assetUrl,
47355
+ alt: label,
47356
+ width: sizes.img,
47357
+ height: sizes.img,
47358
+ style: { imageRendering: "pixelated", objectFit: "contain" },
47359
+ className: "flex-shrink-0"
47360
+ }
47361
+ ) : typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon, size: "sm" }) : /* @__PURE__ */ jsx(Icon, { icon, size: "sm" }) }),
47339
47362
  duration !== void 0 && /* @__PURE__ */ jsx(
47340
47363
  "span",
47341
47364
  {
@@ -47362,15 +47385,16 @@ function StatusEffect({
47362
47385
  label && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground mt-0.5 text-center whitespace-nowrap", children: label })
47363
47386
  ] });
47364
47387
  }
47365
- var sizeMap16, variantStyles9;
47388
+ var DEFAULT_ASSET_URL6, sizeMap16, variantStyles9;
47366
47389
  var init_StatusEffect = __esm({
47367
47390
  "components/game/atoms/StatusEffect.tsx"() {
47368
47391
  init_cn();
47369
47392
  init_Icon();
47393
+ DEFAULT_ASSET_URL6 = "https://almadar-kflow-assets.web.app/shared/effects/particles/flame_01.png";
47370
47394
  sizeMap16 = {
47371
- sm: { container: "w-8 h-8", icon: "text-sm", badge: "text-xs -top-1 -right-1 w-4 h-4", timer: "text-[9px]" },
47372
- md: { container: "w-10 h-10", icon: "text-base", badge: "text-xs -top-1 -right-1 w-5 h-5", timer: "text-xs" },
47373
- 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" }
47395
+ 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 },
47396
+ 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 },
47397
+ 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 }
47374
47398
  };
47375
47399
  variantStyles9 = {
47376
47400
  buff: "border-success bg-success/20",
@@ -48061,17 +48085,12 @@ var init_UncontrolledBattleBoard = __esm({
48061
48085
  }
48062
48086
  });
48063
48087
  function heroPosition(h) {
48088
+ if ("position" in h && h.position != null) return h.position;
48064
48089
  return vec2(h.position);
48065
48090
  }
48066
- function heroOwner(h) {
48067
- return str(h.owner);
48068
- }
48069
48091
  function heroMovement(h) {
48070
48092
  return num(h.movement);
48071
48093
  }
48072
- function hexPassable(h) {
48073
- return h.passable !== false;
48074
- }
48075
48094
  function defaultIsInRange(from, to, range) {
48076
48095
  return Math.abs(from.x - to.x) + Math.abs(from.y - to.y) <= range;
48077
48096
  }
@@ -48107,38 +48126,41 @@ function WorldMapBoard({
48107
48126
  }) {
48108
48127
  const eventBus = useEventBus();
48109
48128
  const resolved = boardEntity(entity);
48110
- const hexes = rows(resolved?.hexes);
48111
- const heroes = rows(resolved?.heroes);
48129
+ const entityUnits = rows(resolved?.units);
48130
+ const entityTiles = rows(resolved?.tiles);
48112
48131
  const features = propFeatures ?? (Array.isArray(resolved?.features) ? resolved.features : []);
48113
48132
  const selectedHeroId = resolved?.selectedHeroId ?? null;
48114
48133
  const assetManifest = propAssetManifest ?? resolved?.assetManifest;
48115
48134
  const backgroundImage = resolved?.backgroundImage;
48116
48135
  const [hoveredTile, setHoveredTile] = useState(null);
48117
- const selectedHero = useMemo(
48118
- () => heroes.find((h) => str(h.id) === selectedHeroId) ?? null,
48119
- [heroes, selectedHeroId]
48120
- );
48121
48136
  const derivedTiles = useMemo(
48122
- () => hexes.map((hex) => ({
48123
- x: num(hex.x),
48124
- y: num(hex.y),
48125
- terrain: str(hex.terrain),
48126
- terrainSprite: hex.terrainSprite == null ? void 0 : str(hex.terrainSprite)
48137
+ () => entityTiles.map((t) => ({
48138
+ x: num(t.x),
48139
+ y: num(t.y),
48140
+ terrain: str(t.terrain),
48141
+ terrainSprite: t.terrainSprite == null ? void 0 : str(t.terrainSprite),
48142
+ passable: t.passable !== false
48127
48143
  })),
48128
- [hexes]
48144
+ [entityTiles]
48129
48145
  );
48130
48146
  const tiles = propTiles ?? derivedTiles;
48131
48147
  const baseUnits = useMemo(
48132
- () => propUnits ?? heroes.map((hero) => ({
48133
- id: str(hero.id),
48134
- position: heroPosition(hero),
48135
- name: str(hero.name),
48136
- team: heroOwner(hero) === "enemy" ? "enemy" : "player",
48137
- health: 100,
48138
- maxHealth: 100,
48139
- sprite: hero.sprite == null ? void 0 : str(hero.sprite)
48148
+ () => propUnits ?? entityUnits.map((u) => ({
48149
+ id: str(u.id),
48150
+ position: heroPosition(u),
48151
+ name: str(u.name),
48152
+ // lolo uses `team` field (not `owner`)
48153
+ team: str(u.team) === "enemy" ? "enemy" : "player",
48154
+ health: num(u.health) || 100,
48155
+ maxHealth: num(u.maxHealth) || 100,
48156
+ sprite: u.sprite == null ? void 0 : str(u.sprite)
48140
48157
  })),
48141
- [heroes, propUnits]
48158
+ [entityUnits, propUnits]
48159
+ );
48160
+ const gameUnits = baseUnits;
48161
+ const selectedHero = useMemo(
48162
+ () => gameUnits.find((u) => u.id === selectedHeroId) ?? null,
48163
+ [gameUnits, selectedHeroId]
48142
48164
  );
48143
48165
  const MOVE_SPEED_MS_PER_TILE = 300;
48144
48166
  const movementAnimRef = useRef(null);
@@ -48184,51 +48206,50 @@ function WorldMapBoard({
48184
48206
  const validMoves = useMemo(() => {
48185
48207
  if (!selectedHero || heroMovement(selectedHero) <= 0) return [];
48186
48208
  const sp = heroPosition(selectedHero);
48187
- const sOwner = heroOwner(selectedHero);
48209
+ const sTeam = str(selectedHero.team);
48188
48210
  const range = heroMovement(selectedHero);
48189
48211
  const moves = [];
48190
- hexes.forEach((hex) => {
48191
- const hx = num(hex.x);
48192
- const hy = num(hex.y);
48193
- if (!hexPassable(hex)) return;
48194
- if (hx === sp.x && hy === sp.y) return;
48195
- if (!isInRange(sp, { x: hx, y: hy }, range)) return;
48196
- if (heroes.some((h) => {
48197
- const hp = heroPosition(h);
48198
- return hp.x === hx && hp.y === hy && heroOwner(h) === sOwner;
48212
+ tiles.forEach((t) => {
48213
+ const tx = t.x;
48214
+ const ty = t.y;
48215
+ if (t.passable === false) return;
48216
+ if (tx === sp.x && ty === sp.y) return;
48217
+ if (!isInRange(sp, { x: tx, y: ty }, range)) return;
48218
+ if (gameUnits.some((u) => {
48219
+ const up = u.position ?? { x: u.x ?? -1, y: u.y ?? -1 };
48220
+ return up.x === tx && up.y === ty && str(u.team) === sTeam;
48199
48221
  })) return;
48200
- moves.push({ x: hx, y: hy });
48222
+ moves.push({ x: tx, y: ty });
48201
48223
  });
48202
48224
  return moves;
48203
- }, [selectedHero, hexes, heroes, isInRange]);
48225
+ }, [selectedHero, tiles, gameUnits, isInRange]);
48204
48226
  const attackTargets = useMemo(() => {
48205
48227
  if (!selectedHero || heroMovement(selectedHero) <= 0) return [];
48206
48228
  const sp = heroPosition(selectedHero);
48207
- const sOwner = heroOwner(selectedHero);
48229
+ const sTeam = str(selectedHero.team);
48208
48230
  const range = heroMovement(selectedHero);
48209
- return heroes.filter((h) => heroOwner(h) !== sOwner).filter((h) => isInRange(sp, heroPosition(h), range)).map((h) => heroPosition(h));
48210
- }, [selectedHero, heroes, isInRange]);
48211
- const maxY = Math.max(...hexes.map((h) => num(h.y)), 0);
48231
+ 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 });
48232
+ }, [selectedHero, gameUnits, isInRange]);
48233
+ const maxY = Math.max(...tiles.map((t) => t.y), 0);
48212
48234
  const baseOffsetX = (maxY + 1) * (TILE_WIDTH * scale / 2);
48213
48235
  const tileToScreen = useCallback(
48214
48236
  (tx, ty) => isoToScreen(tx, ty, scale, baseOffsetX),
48215
48237
  [scale, baseOffsetX]
48216
48238
  );
48217
48239
  const hoveredHex = useMemo(
48218
- () => hoveredTile ? hexes.find((h) => num(h.x) === hoveredTile.x && num(h.y) === hoveredTile.y) ?? null : null,
48219
- [hoveredTile, hexes]
48240
+ () => hoveredTile ? tiles.find((t) => t.x === hoveredTile.x && t.y === hoveredTile.y) ?? null : null,
48241
+ [hoveredTile, tiles]
48220
48242
  );
48221
48243
  const hoveredHero = useMemo(
48222
- () => hoveredTile ? heroes.find((h) => {
48223
- const hp = heroPosition(h);
48224
- return hp.x === hoveredTile.x && hp.y === hoveredTile.y;
48244
+ () => hoveredTile ? gameUnits.find((u) => {
48245
+ const up = u.position ?? { x: u.x ?? -1, y: u.y ?? -1 };
48246
+ return up.x === hoveredTile.x && up.y === hoveredTile.y;
48225
48247
  }) ?? null : null,
48226
- [hoveredTile, heroes]
48248
+ [hoveredTile, gameUnits]
48227
48249
  );
48228
48250
  const handleTileClick = useCallback((x, y) => {
48229
48251
  if (movementAnimRef.current) return;
48230
- const hex = hexes.find((h) => num(h.x) === x && num(h.y) === y);
48231
- if (!hex) return;
48252
+ const tile = tiles.find((t) => t.x === x && t.y === y);
48232
48253
  if (tileClickEvent) {
48233
48254
  eventBus.emit(`UI:${tileClickEvent}`, { x, y });
48234
48255
  }
@@ -48239,38 +48260,39 @@ function WorldMapBoard({
48239
48260
  if (heroMoveEvent) {
48240
48261
  eventBus.emit(`UI:${heroMoveEvent}`, { heroId, toX: x, toY: y });
48241
48262
  }
48242
- const feature = str(hex.feature);
48263
+ const feature = tile ? str(tile.feature) : "";
48243
48264
  if (feature && feature !== "none") {
48244
- onFeatureEnter?.(heroId, hex);
48265
+ const tileRow = tile;
48266
+ onFeatureEnter?.(heroId, tileRow);
48245
48267
  if (featureEnterEvent) {
48246
- eventBus.emit(`UI:${featureEnterEvent}`, { heroId, feature, hex });
48268
+ eventBus.emit(`UI:${featureEnterEvent}`, { heroId, feature, hex: tileRow });
48247
48269
  }
48248
48270
  }
48249
48271
  });
48250
48272
  return;
48251
48273
  }
48252
- const enemy = heroes.find((h) => {
48253
- const hp = heroPosition(h);
48254
- return hp.x === x && hp.y === y && heroOwner(h) === "enemy";
48274
+ const enemy = gameUnits.find((u) => {
48275
+ const up = u.position ?? { x: u.x ?? -1, y: u.y ?? -1 };
48276
+ return up.x === x && up.y === y && str(u.team) === "enemy";
48255
48277
  });
48256
48278
  if (selectedHero && enemy && attackTargets.some((t) => t.x === x && t.y === y)) {
48257
48279
  const attackerId = str(selectedHero.id);
48258
- const defenderId = str(enemy.id);
48280
+ const defenderId = enemy.id;
48259
48281
  onBattleEncounter?.(attackerId, defenderId);
48260
48282
  if (battleEncounterEvent) {
48261
48283
  eventBus.emit(`UI:${battleEncounterEvent}`, { attackerId, defenderId });
48262
48284
  }
48263
48285
  }
48264
- }, [hexes, heroes, selectedHero, validMoves, attackTargets, startMoveAnimation, onHeroMove, onFeatureEnter, onBattleEncounter, eventBus, tileClickEvent, heroMoveEvent, featureEnterEvent, battleEncounterEvent]);
48286
+ }, [tiles, gameUnits, selectedHero, validMoves, attackTargets, startMoveAnimation, onHeroMove, onFeatureEnter, onBattleEncounter, eventBus, tileClickEvent, heroMoveEvent, featureEnterEvent, battleEncounterEvent]);
48265
48287
  const handleUnitClick = useCallback((unitId) => {
48266
- const hero = heroes.find((h) => str(h.id) === unitId);
48267
- if (hero && (heroOwner(hero) === "player" || allowMoveAllHeroes)) {
48288
+ const unit = gameUnits.find((u) => u.id === unitId);
48289
+ if (unit && (str(unit.team) === "player" || allowMoveAllHeroes)) {
48268
48290
  onHeroSelect?.(unitId);
48269
48291
  if (heroSelectEvent) {
48270
48292
  eventBus.emit(`UI:${heroSelectEvent}`, { heroId: unitId });
48271
48293
  }
48272
48294
  }
48273
- }, [heroes, onHeroSelect, allowMoveAllHeroes, eventBus, heroSelectEvent]);
48295
+ }, [gameUnits, onHeroSelect, allowMoveAllHeroes, eventBus, heroSelectEvent]);
48274
48296
  const selectHero = useCallback((id) => {
48275
48297
  onHeroSelect?.(id);
48276
48298
  if (heroSelectEvent) {