@almadar/ui 5.51.0 → 5.53.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.
@@ -31382,6 +31382,7 @@ function PlatformerCanvas({
31382
31382
  const canvasRef = React90.useRef(null);
31383
31383
  const eventBus = useEventBus();
31384
31384
  const keysRef = React90.useRef(/* @__PURE__ */ new Set());
31385
+ const lastPlatCountRef = React90.useRef(-1);
31385
31386
  const imageCache = React90.useRef(/* @__PURE__ */ new Map());
31386
31387
  const [loadedImages, setLoadedImages] = React90.useState(/* @__PURE__ */ new Set());
31387
31388
  const loadImage = React90.useCallback((url) => {
@@ -31537,6 +31538,18 @@ function PlatformerCanvas({
31537
31538
  camX = Math.max(0, Math.min(px - cw / 2, ww - cw));
31538
31539
  camY = Math.max(0, Math.min(py - ch / 2 - 50, wh - ch));
31539
31540
  }
31541
+ if (plats.length !== lastPlatCountRef.current) {
31542
+ lastPlatCountRef.current = plats.length;
31543
+ canvasLog.debug("draw:platforms", {
31544
+ platformCount: plats.length,
31545
+ camX,
31546
+ camY,
31547
+ player: { x: px, y: py },
31548
+ worldWidth: ww,
31549
+ canvasWidth: cw,
31550
+ followCamera: fc
31551
+ });
31552
+ }
31540
31553
  const bgImage = bgImg ? loadImage(bgImg) : null;
31541
31554
  if (bgImage) {
31542
31555
  ctx.drawImage(bgImage, 0, 0, cw, ch);
@@ -31662,13 +31675,14 @@ function PlatformerCanvas({
31662
31675
  }
31663
31676
  );
31664
31677
  }
31665
- var PLATFORM_COLORS, PLAYER_COLOR, PLAYER_EYE_COLOR, SKY_GRADIENT_TOP, SKY_GRADIENT_BOTTOM, GRID_COLOR;
31678
+ var canvasLog, PLATFORM_COLORS, PLAYER_COLOR, PLAYER_EYE_COLOR, SKY_GRADIENT_TOP, SKY_GRADIENT_BOTTOM, GRID_COLOR;
31666
31679
  var init_PlatformerCanvas = __esm({
31667
31680
  "components/game/molecules/PlatformerCanvas.tsx"() {
31668
31681
  init_cn();
31669
31682
  init_useEventBus();
31670
31683
  init_verificationRegistry();
31671
31684
  init_useRenderInterpolation();
31685
+ canvasLog = logger.createLogger("almadar:ui:game:platformer-canvas");
31672
31686
  PLATFORM_COLORS = {
31673
31687
  ground: "#4a7c59",
31674
31688
  platform: "#7c6b4a",
@@ -46605,14 +46619,20 @@ function PlatformerBoard({
46605
46619
  grounded: false,
46606
46620
  facingRight: true
46607
46621
  };
46608
- const entityPlatforms = Array.isArray(row["platforms"]) ? row["platforms"] : void 0;
46609
- const platforms = propPlatforms ?? entityPlatforms ?? [
46622
+ const platforms = propPlatforms ?? [
46610
46623
  { x: 0, y: 368, width: 800, height: 32, type: "ground" },
46611
46624
  { x: 150, y: 280, width: 160, height: 16, type: "platform" },
46612
46625
  { x: 420, y: 220, width: 160, height: 16, type: "platform" },
46613
46626
  { x: 580, y: 300, width: 80, height: 16, type: "hazard" },
46614
46627
  { x: 700, y: 340, width: 64, height: 28, type: "goal" }
46615
46628
  ];
46629
+ boardLog.debug("platforms-resolve", {
46630
+ propPlatformsCount: Array.isArray(propPlatforms) ? propPlatforms.length : null,
46631
+ usedFallback: propPlatforms === void 0,
46632
+ finalCount: platforms.length,
46633
+ player: { x: player.x, y: player.y, vx: player.vx, vy: player.vy, grounded: player.grounded },
46634
+ result
46635
+ });
46616
46636
  const handleRestart = React90.useCallback(() => {
46617
46637
  if (playAgainEvent) {
46618
46638
  eventBus.emit(`UI:${playAgainEvent}`, {});
@@ -46691,6 +46711,7 @@ function PlatformerBoard({
46691
46711
  }
46692
46712
  );
46693
46713
  }
46714
+ var boardLog;
46694
46715
  var init_PlatformerBoard = __esm({
46695
46716
  "components/game/organisms/PlatformerBoard.tsx"() {
46696
46717
  "use client";
@@ -46701,6 +46722,7 @@ var init_PlatformerBoard = __esm({
46701
46722
  init_Typography();
46702
46723
  init_Stack();
46703
46724
  init_PlatformerCanvas();
46725
+ boardLog = logger.createLogger("almadar:ui:game:platformer-board");
46704
46726
  PlatformerBoard.displayName = "PlatformerBoard";
46705
46727
  }
46706
46728
  });
@@ -57351,7 +57373,7 @@ function convertFnFormLambdasInProps(props) {
57351
57373
  }
57352
57374
  var log10 = logger.createLogger("almadar:ui:effects:client-handlers");
57353
57375
  function createClientEffectHandlers(options) {
57354
- const { eventBus, slotSetter, navigate, notify, callService } = options;
57376
+ const { eventBus, slotSetter, navigate, notify, callService, liveEntity } = options;
57355
57377
  return {
57356
57378
  emit: (event, payload) => {
57357
57379
  const prefixedEvent = event.startsWith("UI:") ? event : `UI:${event}`;
@@ -57360,8 +57382,12 @@ function createClientEffectHandlers(options) {
57360
57382
  persist: async () => {
57361
57383
  log10.warn("persist is server-side only, ignored on client");
57362
57384
  },
57363
- set: () => {
57364
- log10.warn("set is server-side only, ignored on client");
57385
+ set: (_entityId, field, value) => {
57386
+ if (!liveEntity) {
57387
+ log10.warn("set is server-side only, ignored on client (no live entity)");
57388
+ return;
57389
+ }
57390
+ liveEntity[field] = value;
57365
57391
  },
57366
57392
  callService: async (service, action, params) => {
57367
57393
  if (callService) return callService(service, action, params);
@@ -57629,6 +57655,11 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
57629
57655
  const traitName = binding.trait.name;
57630
57656
  const linkedEntity = binding.linkedEntity || "";
57631
57657
  const entityId = payload?.entityId;
57658
+ let liveEntity = traitFieldStatesRef.current.get(traitName);
57659
+ if (!liveEntity) {
57660
+ liveEntity = {};
57661
+ traitFieldStatesRef.current.set(traitName, liveEntity);
57662
+ }
57632
57663
  const effects = syncOnly ? params.effects.filter(
57633
57664
  (e) => Array.isArray(e) && SYNC_TICK_OPERATORS.has(String(e[0]))
57634
57665
  ) : params.effects;
@@ -57651,7 +57682,12 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
57651
57682
  },
57652
57683
  navigate: optionsRef.current?.navigate,
57653
57684
  notify: optionsRef.current?.notify,
57654
- callService: optionsRef.current?.callService
57685
+ callService: optionsRef.current?.callService,
57686
+ // The canonical client `set` writes `(set @entity.X)` straight into
57687
+ // the trait's one live store — the same object bound below. This is
57688
+ // what makes a [runtime] entity's set (from ticks AND events) reach
57689
+ // the render-ui + next tick + guards.
57690
+ liveEntity
57655
57691
  });
57656
57692
  const persistence = syncOnly ? void 0 : optionsRef.current?.persistence;
57657
57693
  let handlers = clientHandlers;
@@ -57662,7 +57698,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
57662
57698
  // uses below. `@payload.*` resolves from `payload` separately,
57663
57699
  // so dropping the prior `payload as EntityRow` cast loses
57664
57700
  // nothing — it just stops mislabelling the payload as an entity.
57665
- entity: traitFieldStatesRef.current.get(traitName) ?? {},
57701
+ entity: liveEntity,
57666
57702
  payload: payload || {},
57667
57703
  state: previousState
57668
57704
  };
@@ -57702,24 +57738,17 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
57702
57738
  handlers = {
57703
57739
  ...handlers,
57704
57740
  set: async (targetId, field, value) => {
57705
- let fieldState = traitFieldStatesRef.current.get(traitName);
57706
- if (!fieldState) {
57707
- fieldState = {};
57708
- traitFieldStatesRef.current.set(traitName, fieldState);
57709
- }
57710
- fieldState[field] = value;
57741
+ if (baseSet) await baseSet(targetId, field, value);
57711
57742
  log15.debug("set:write", {
57712
57743
  traitName,
57713
57744
  field,
57714
57745
  value: JSON.stringify(value),
57715
57746
  transition: `${previousState}->${newState}`
57716
57747
  });
57717
- if (baseSet) await baseSet(targetId, field, value);
57718
57748
  }
57719
57749
  };
57720
- const entityForBinding = traitFieldStatesRef.current.get(traitName) ?? {};
57721
57750
  const bindingCtx = {
57722
- entity: entityForBinding,
57751
+ entity: liveEntity,
57723
57752
  payload: payload || {},
57724
57753
  state: previousState
57725
57754
  };
package/dist/avl/index.js CHANGED
@@ -31335,6 +31335,7 @@ function PlatformerCanvas({
31335
31335
  const canvasRef = useRef(null);
31336
31336
  const eventBus = useEventBus();
31337
31337
  const keysRef = useRef(/* @__PURE__ */ new Set());
31338
+ const lastPlatCountRef = useRef(-1);
31338
31339
  const imageCache = useRef(/* @__PURE__ */ new Map());
31339
31340
  const [loadedImages, setLoadedImages] = useState(/* @__PURE__ */ new Set());
31340
31341
  const loadImage = useCallback((url) => {
@@ -31490,6 +31491,18 @@ function PlatformerCanvas({
31490
31491
  camX = Math.max(0, Math.min(px - cw / 2, ww - cw));
31491
31492
  camY = Math.max(0, Math.min(py - ch / 2 - 50, wh - ch));
31492
31493
  }
31494
+ if (plats.length !== lastPlatCountRef.current) {
31495
+ lastPlatCountRef.current = plats.length;
31496
+ canvasLog.debug("draw:platforms", {
31497
+ platformCount: plats.length,
31498
+ camX,
31499
+ camY,
31500
+ player: { x: px, y: py },
31501
+ worldWidth: ww,
31502
+ canvasWidth: cw,
31503
+ followCamera: fc
31504
+ });
31505
+ }
31493
31506
  const bgImage = bgImg ? loadImage(bgImg) : null;
31494
31507
  if (bgImage) {
31495
31508
  ctx.drawImage(bgImage, 0, 0, cw, ch);
@@ -31615,13 +31628,14 @@ function PlatformerCanvas({
31615
31628
  }
31616
31629
  );
31617
31630
  }
31618
- var PLATFORM_COLORS, PLAYER_COLOR, PLAYER_EYE_COLOR, SKY_GRADIENT_TOP, SKY_GRADIENT_BOTTOM, GRID_COLOR;
31631
+ var canvasLog, PLATFORM_COLORS, PLAYER_COLOR, PLAYER_EYE_COLOR, SKY_GRADIENT_TOP, SKY_GRADIENT_BOTTOM, GRID_COLOR;
31619
31632
  var init_PlatformerCanvas = __esm({
31620
31633
  "components/game/molecules/PlatformerCanvas.tsx"() {
31621
31634
  init_cn();
31622
31635
  init_useEventBus();
31623
31636
  init_verificationRegistry();
31624
31637
  init_useRenderInterpolation();
31638
+ canvasLog = createLogger("almadar:ui:game:platformer-canvas");
31625
31639
  PLATFORM_COLORS = {
31626
31640
  ground: "#4a7c59",
31627
31641
  platform: "#7c6b4a",
@@ -46558,14 +46572,20 @@ function PlatformerBoard({
46558
46572
  grounded: false,
46559
46573
  facingRight: true
46560
46574
  };
46561
- const entityPlatforms = Array.isArray(row["platforms"]) ? row["platforms"] : void 0;
46562
- const platforms = propPlatforms ?? entityPlatforms ?? [
46575
+ const platforms = propPlatforms ?? [
46563
46576
  { x: 0, y: 368, width: 800, height: 32, type: "ground" },
46564
46577
  { x: 150, y: 280, width: 160, height: 16, type: "platform" },
46565
46578
  { x: 420, y: 220, width: 160, height: 16, type: "platform" },
46566
46579
  { x: 580, y: 300, width: 80, height: 16, type: "hazard" },
46567
46580
  { x: 700, y: 340, width: 64, height: 28, type: "goal" }
46568
46581
  ];
46582
+ boardLog.debug("platforms-resolve", {
46583
+ propPlatformsCount: Array.isArray(propPlatforms) ? propPlatforms.length : null,
46584
+ usedFallback: propPlatforms === void 0,
46585
+ finalCount: platforms.length,
46586
+ player: { x: player.x, y: player.y, vx: player.vx, vy: player.vy, grounded: player.grounded },
46587
+ result
46588
+ });
46569
46589
  const handleRestart = useCallback(() => {
46570
46590
  if (playAgainEvent) {
46571
46591
  eventBus.emit(`UI:${playAgainEvent}`, {});
@@ -46644,6 +46664,7 @@ function PlatformerBoard({
46644
46664
  }
46645
46665
  );
46646
46666
  }
46667
+ var boardLog;
46647
46668
  var init_PlatformerBoard = __esm({
46648
46669
  "components/game/organisms/PlatformerBoard.tsx"() {
46649
46670
  "use client";
@@ -46654,6 +46675,7 @@ var init_PlatformerBoard = __esm({
46654
46675
  init_Typography();
46655
46676
  init_Stack();
46656
46677
  init_PlatformerCanvas();
46678
+ boardLog = createLogger("almadar:ui:game:platformer-board");
46657
46679
  PlatformerBoard.displayName = "PlatformerBoard";
46658
46680
  }
46659
46681
  });
@@ -57304,7 +57326,7 @@ function convertFnFormLambdasInProps(props) {
57304
57326
  }
57305
57327
  var log10 = createLogger("almadar:ui:effects:client-handlers");
57306
57328
  function createClientEffectHandlers(options) {
57307
- const { eventBus, slotSetter, navigate, notify, callService } = options;
57329
+ const { eventBus, slotSetter, navigate, notify, callService, liveEntity } = options;
57308
57330
  return {
57309
57331
  emit: (event, payload) => {
57310
57332
  const prefixedEvent = event.startsWith("UI:") ? event : `UI:${event}`;
@@ -57313,8 +57335,12 @@ function createClientEffectHandlers(options) {
57313
57335
  persist: async () => {
57314
57336
  log10.warn("persist is server-side only, ignored on client");
57315
57337
  },
57316
- set: () => {
57317
- log10.warn("set is server-side only, ignored on client");
57338
+ set: (_entityId, field, value) => {
57339
+ if (!liveEntity) {
57340
+ log10.warn("set is server-side only, ignored on client (no live entity)");
57341
+ return;
57342
+ }
57343
+ liveEntity[field] = value;
57318
57344
  },
57319
57345
  callService: async (service, action, params) => {
57320
57346
  if (callService) return callService(service, action, params);
@@ -57582,6 +57608,11 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
57582
57608
  const traitName = binding.trait.name;
57583
57609
  const linkedEntity = binding.linkedEntity || "";
57584
57610
  const entityId = payload?.entityId;
57611
+ let liveEntity = traitFieldStatesRef.current.get(traitName);
57612
+ if (!liveEntity) {
57613
+ liveEntity = {};
57614
+ traitFieldStatesRef.current.set(traitName, liveEntity);
57615
+ }
57585
57616
  const effects = syncOnly ? params.effects.filter(
57586
57617
  (e) => Array.isArray(e) && SYNC_TICK_OPERATORS.has(String(e[0]))
57587
57618
  ) : params.effects;
@@ -57604,7 +57635,12 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
57604
57635
  },
57605
57636
  navigate: optionsRef.current?.navigate,
57606
57637
  notify: optionsRef.current?.notify,
57607
- callService: optionsRef.current?.callService
57638
+ callService: optionsRef.current?.callService,
57639
+ // The canonical client `set` writes `(set @entity.X)` straight into
57640
+ // the trait's one live store — the same object bound below. This is
57641
+ // what makes a [runtime] entity's set (from ticks AND events) reach
57642
+ // the render-ui + next tick + guards.
57643
+ liveEntity
57608
57644
  });
57609
57645
  const persistence = syncOnly ? void 0 : optionsRef.current?.persistence;
57610
57646
  let handlers = clientHandlers;
@@ -57615,7 +57651,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
57615
57651
  // uses below. `@payload.*` resolves from `payload` separately,
57616
57652
  // so dropping the prior `payload as EntityRow` cast loses
57617
57653
  // nothing — it just stops mislabelling the payload as an entity.
57618
- entity: traitFieldStatesRef.current.get(traitName) ?? {},
57654
+ entity: liveEntity,
57619
57655
  payload: payload || {},
57620
57656
  state: previousState
57621
57657
  };
@@ -57655,24 +57691,17 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
57655
57691
  handlers = {
57656
57692
  ...handlers,
57657
57693
  set: async (targetId, field, value) => {
57658
- let fieldState = traitFieldStatesRef.current.get(traitName);
57659
- if (!fieldState) {
57660
- fieldState = {};
57661
- traitFieldStatesRef.current.set(traitName, fieldState);
57662
- }
57663
- fieldState[field] = value;
57694
+ if (baseSet) await baseSet(targetId, field, value);
57664
57695
  log15.debug("set:write", {
57665
57696
  traitName,
57666
57697
  field,
57667
57698
  value: JSON.stringify(value),
57668
57699
  transition: `${previousState}->${newState}`
57669
57700
  });
57670
- if (baseSet) await baseSet(targetId, field, value);
57671
57701
  }
57672
57702
  };
57673
- const entityForBinding = traitFieldStatesRef.current.get(traitName) ?? {};
57674
57703
  const bindingCtx = {
57675
- entity: entityForBinding,
57704
+ entity: liveEntity,
57676
57705
  payload: payload || {},
57677
57706
  state: previousState
57678
57707
  };
@@ -29178,6 +29178,7 @@ function PlatformerCanvas({
29178
29178
  const canvasRef = React76.useRef(null);
29179
29179
  const eventBus = useEventBus();
29180
29180
  const keysRef = React76.useRef(/* @__PURE__ */ new Set());
29181
+ const lastPlatCountRef = React76.useRef(-1);
29181
29182
  const imageCache = React76.useRef(/* @__PURE__ */ new Map());
29182
29183
  const [loadedImages, setLoadedImages] = React76.useState(/* @__PURE__ */ new Set());
29183
29184
  const loadImage = React76.useCallback((url) => {
@@ -29333,6 +29334,18 @@ function PlatformerCanvas({
29333
29334
  camX = Math.max(0, Math.min(px - cw / 2, ww - cw));
29334
29335
  camY = Math.max(0, Math.min(py - ch / 2 - 50, wh - ch));
29335
29336
  }
29337
+ if (plats.length !== lastPlatCountRef.current) {
29338
+ lastPlatCountRef.current = plats.length;
29339
+ canvasLog.debug("draw:platforms", {
29340
+ platformCount: plats.length,
29341
+ camX,
29342
+ camY,
29343
+ player: { x: px, y: py },
29344
+ worldWidth: ww,
29345
+ canvasWidth: cw,
29346
+ followCamera: fc
29347
+ });
29348
+ }
29336
29349
  const bgImage = bgImg ? loadImage(bgImg) : null;
29337
29350
  if (bgImage) {
29338
29351
  ctx.drawImage(bgImage, 0, 0, cw, ch);
@@ -29458,13 +29471,14 @@ function PlatformerCanvas({
29458
29471
  }
29459
29472
  );
29460
29473
  }
29461
- var PLATFORM_COLORS, PLAYER_COLOR, PLAYER_EYE_COLOR, SKY_GRADIENT_TOP, SKY_GRADIENT_BOTTOM, GRID_COLOR;
29474
+ var canvasLog, PLATFORM_COLORS, PLAYER_COLOR, PLAYER_EYE_COLOR, SKY_GRADIENT_TOP, SKY_GRADIENT_BOTTOM, GRID_COLOR;
29462
29475
  var init_PlatformerCanvas = __esm({
29463
29476
  "components/game/molecules/PlatformerCanvas.tsx"() {
29464
29477
  init_cn();
29465
29478
  init_useEventBus();
29466
29479
  init_verificationRegistry();
29467
29480
  init_useRenderInterpolation();
29481
+ canvasLog = logger.createLogger("almadar:ui:game:platformer-canvas");
29468
29482
  PLATFORM_COLORS = {
29469
29483
  ground: "#4a7c59",
29470
29484
  platform: "#7c6b4a",
@@ -45267,14 +45281,20 @@ function PlatformerBoard({
45267
45281
  grounded: false,
45268
45282
  facingRight: true
45269
45283
  };
45270
- const entityPlatforms = Array.isArray(row["platforms"]) ? row["platforms"] : void 0;
45271
- const platforms = propPlatforms ?? entityPlatforms ?? [
45284
+ const platforms = propPlatforms ?? [
45272
45285
  { x: 0, y: 368, width: 800, height: 32, type: "ground" },
45273
45286
  { x: 150, y: 280, width: 160, height: 16, type: "platform" },
45274
45287
  { x: 420, y: 220, width: 160, height: 16, type: "platform" },
45275
45288
  { x: 580, y: 300, width: 80, height: 16, type: "hazard" },
45276
45289
  { x: 700, y: 340, width: 64, height: 28, type: "goal" }
45277
45290
  ];
45291
+ boardLog.debug("platforms-resolve", {
45292
+ propPlatformsCount: Array.isArray(propPlatforms) ? propPlatforms.length : null,
45293
+ usedFallback: propPlatforms === void 0,
45294
+ finalCount: platforms.length,
45295
+ player: { x: player.x, y: player.y, vx: player.vx, vy: player.vy, grounded: player.grounded },
45296
+ result
45297
+ });
45278
45298
  const handleRestart = React76.useCallback(() => {
45279
45299
  if (playAgainEvent) {
45280
45300
  eventBus.emit(`UI:${playAgainEvent}`, {});
@@ -45353,6 +45373,7 @@ function PlatformerBoard({
45353
45373
  }
45354
45374
  );
45355
45375
  }
45376
+ var boardLog;
45356
45377
  var init_PlatformerBoard = __esm({
45357
45378
  "components/game/organisms/PlatformerBoard.tsx"() {
45358
45379
  "use client";
@@ -45363,6 +45384,7 @@ var init_PlatformerBoard = __esm({
45363
45384
  init_Typography();
45364
45385
  init_Stack();
45365
45386
  init_PlatformerCanvas();
45387
+ boardLog = logger.createLogger("almadar:ui:game:platformer-board");
45366
45388
  PlatformerBoard.displayName = "PlatformerBoard";
45367
45389
  }
45368
45390
  });
@@ -29132,6 +29132,7 @@ function PlatformerCanvas({
29132
29132
  const canvasRef = useRef(null);
29133
29133
  const eventBus = useEventBus();
29134
29134
  const keysRef = useRef(/* @__PURE__ */ new Set());
29135
+ const lastPlatCountRef = useRef(-1);
29135
29136
  const imageCache = useRef(/* @__PURE__ */ new Map());
29136
29137
  const [loadedImages, setLoadedImages] = useState(/* @__PURE__ */ new Set());
29137
29138
  const loadImage = useCallback((url) => {
@@ -29287,6 +29288,18 @@ function PlatformerCanvas({
29287
29288
  camX = Math.max(0, Math.min(px - cw / 2, ww - cw));
29288
29289
  camY = Math.max(0, Math.min(py - ch / 2 - 50, wh - ch));
29289
29290
  }
29291
+ if (plats.length !== lastPlatCountRef.current) {
29292
+ lastPlatCountRef.current = plats.length;
29293
+ canvasLog.debug("draw:platforms", {
29294
+ platformCount: plats.length,
29295
+ camX,
29296
+ camY,
29297
+ player: { x: px, y: py },
29298
+ worldWidth: ww,
29299
+ canvasWidth: cw,
29300
+ followCamera: fc
29301
+ });
29302
+ }
29290
29303
  const bgImage = bgImg ? loadImage(bgImg) : null;
29291
29304
  if (bgImage) {
29292
29305
  ctx.drawImage(bgImage, 0, 0, cw, ch);
@@ -29412,13 +29425,14 @@ function PlatformerCanvas({
29412
29425
  }
29413
29426
  );
29414
29427
  }
29415
- var PLATFORM_COLORS, PLAYER_COLOR, PLAYER_EYE_COLOR, SKY_GRADIENT_TOP, SKY_GRADIENT_BOTTOM, GRID_COLOR;
29428
+ var canvasLog, PLATFORM_COLORS, PLAYER_COLOR, PLAYER_EYE_COLOR, SKY_GRADIENT_TOP, SKY_GRADIENT_BOTTOM, GRID_COLOR;
29416
29429
  var init_PlatformerCanvas = __esm({
29417
29430
  "components/game/molecules/PlatformerCanvas.tsx"() {
29418
29431
  init_cn();
29419
29432
  init_useEventBus();
29420
29433
  init_verificationRegistry();
29421
29434
  init_useRenderInterpolation();
29435
+ canvasLog = createLogger("almadar:ui:game:platformer-canvas");
29422
29436
  PLATFORM_COLORS = {
29423
29437
  ground: "#4a7c59",
29424
29438
  platform: "#7c6b4a",
@@ -45221,14 +45235,20 @@ function PlatformerBoard({
45221
45235
  grounded: false,
45222
45236
  facingRight: true
45223
45237
  };
45224
- const entityPlatforms = Array.isArray(row["platforms"]) ? row["platforms"] : void 0;
45225
- const platforms = propPlatforms ?? entityPlatforms ?? [
45238
+ const platforms = propPlatforms ?? [
45226
45239
  { x: 0, y: 368, width: 800, height: 32, type: "ground" },
45227
45240
  { x: 150, y: 280, width: 160, height: 16, type: "platform" },
45228
45241
  { x: 420, y: 220, width: 160, height: 16, type: "platform" },
45229
45242
  { x: 580, y: 300, width: 80, height: 16, type: "hazard" },
45230
45243
  { x: 700, y: 340, width: 64, height: 28, type: "goal" }
45231
45244
  ];
45245
+ boardLog.debug("platforms-resolve", {
45246
+ propPlatformsCount: Array.isArray(propPlatforms) ? propPlatforms.length : null,
45247
+ usedFallback: propPlatforms === void 0,
45248
+ finalCount: platforms.length,
45249
+ player: { x: player.x, y: player.y, vx: player.vx, vy: player.vy, grounded: player.grounded },
45250
+ result
45251
+ });
45232
45252
  const handleRestart = useCallback(() => {
45233
45253
  if (playAgainEvent) {
45234
45254
  eventBus.emit(`UI:${playAgainEvent}`, {});
@@ -45307,6 +45327,7 @@ function PlatformerBoard({
45307
45327
  }
45308
45328
  );
45309
45329
  }
45330
+ var boardLog;
45310
45331
  var init_PlatformerBoard = __esm({
45311
45332
  "components/game/organisms/PlatformerBoard.tsx"() {
45312
45333
  "use client";
@@ -45317,6 +45338,7 @@ var init_PlatformerBoard = __esm({
45317
45338
  init_Typography();
45318
45339
  init_Stack();
45319
45340
  init_PlatformerCanvas();
45341
+ boardLog = createLogger("almadar:ui:game:platformer-board");
45320
45342
  PlatformerBoard.displayName = "PlatformerBoard";
45321
45343
  }
45322
45344
  });
@@ -28774,6 +28774,7 @@ function PlatformerCanvas({
28774
28774
  const canvasRef = React82.useRef(null);
28775
28775
  const eventBus = useEventBus();
28776
28776
  const keysRef = React82.useRef(/* @__PURE__ */ new Set());
28777
+ const lastPlatCountRef = React82.useRef(-1);
28777
28778
  const imageCache = React82.useRef(/* @__PURE__ */ new Map());
28778
28779
  const [loadedImages, setLoadedImages] = React82.useState(/* @__PURE__ */ new Set());
28779
28780
  const loadImage = React82.useCallback((url) => {
@@ -28929,6 +28930,18 @@ function PlatformerCanvas({
28929
28930
  camX = Math.max(0, Math.min(px - cw / 2, ww - cw));
28930
28931
  camY = Math.max(0, Math.min(py - ch / 2 - 50, wh - ch));
28931
28932
  }
28933
+ if (plats.length !== lastPlatCountRef.current) {
28934
+ lastPlatCountRef.current = plats.length;
28935
+ canvasLog.debug("draw:platforms", {
28936
+ platformCount: plats.length,
28937
+ camX,
28938
+ camY,
28939
+ player: { x: px, y: py },
28940
+ worldWidth: ww,
28941
+ canvasWidth: cw,
28942
+ followCamera: fc
28943
+ });
28944
+ }
28932
28945
  const bgImage = bgImg ? loadImage(bgImg) : null;
28933
28946
  if (bgImage) {
28934
28947
  ctx.drawImage(bgImage, 0, 0, cw, ch);
@@ -29054,13 +29067,14 @@ function PlatformerCanvas({
29054
29067
  }
29055
29068
  );
29056
29069
  }
29057
- var PLATFORM_COLORS, PLAYER_COLOR, PLAYER_EYE_COLOR, SKY_GRADIENT_TOP, SKY_GRADIENT_BOTTOM, GRID_COLOR;
29070
+ var canvasLog, PLATFORM_COLORS, PLAYER_COLOR, PLAYER_EYE_COLOR, SKY_GRADIENT_TOP, SKY_GRADIENT_BOTTOM, GRID_COLOR;
29058
29071
  var init_PlatformerCanvas = __esm({
29059
29072
  "components/game/molecules/PlatformerCanvas.tsx"() {
29060
29073
  init_cn();
29061
29074
  init_useEventBus();
29062
29075
  init_verificationRegistry();
29063
29076
  init_useRenderInterpolation();
29077
+ canvasLog = logger.createLogger("almadar:ui:game:platformer-canvas");
29064
29078
  PLATFORM_COLORS = {
29065
29079
  ground: "#4a7c59",
29066
29080
  platform: "#7c6b4a",
@@ -44406,14 +44420,20 @@ function PlatformerBoard({
44406
44420
  grounded: false,
44407
44421
  facingRight: true
44408
44422
  };
44409
- const entityPlatforms = Array.isArray(row["platforms"]) ? row["platforms"] : void 0;
44410
- const platforms = propPlatforms ?? entityPlatforms ?? [
44423
+ const platforms = propPlatforms ?? [
44411
44424
  { x: 0, y: 368, width: 800, height: 32, type: "ground" },
44412
44425
  { x: 150, y: 280, width: 160, height: 16, type: "platform" },
44413
44426
  { x: 420, y: 220, width: 160, height: 16, type: "platform" },
44414
44427
  { x: 580, y: 300, width: 80, height: 16, type: "hazard" },
44415
44428
  { x: 700, y: 340, width: 64, height: 28, type: "goal" }
44416
44429
  ];
44430
+ boardLog.debug("platforms-resolve", {
44431
+ propPlatformsCount: Array.isArray(propPlatforms) ? propPlatforms.length : null,
44432
+ usedFallback: propPlatforms === void 0,
44433
+ finalCount: platforms.length,
44434
+ player: { x: player.x, y: player.y, vx: player.vx, vy: player.vy, grounded: player.grounded },
44435
+ result
44436
+ });
44417
44437
  const handleRestart = React82.useCallback(() => {
44418
44438
  if (playAgainEvent) {
44419
44439
  eventBus.emit(`UI:${playAgainEvent}`, {});
@@ -44492,6 +44512,7 @@ function PlatformerBoard({
44492
44512
  }
44493
44513
  );
44494
44514
  }
44515
+ var boardLog;
44495
44516
  var init_PlatformerBoard = __esm({
44496
44517
  "components/game/organisms/PlatformerBoard.tsx"() {
44497
44518
  "use client";
@@ -44502,6 +44523,7 @@ var init_PlatformerBoard = __esm({
44502
44523
  init_Typography();
44503
44524
  init_Stack();
44504
44525
  init_PlatformerCanvas();
44526
+ boardLog = logger.createLogger("almadar:ui:game:platformer-board");
44505
44527
  PlatformerBoard.displayName = "PlatformerBoard";
44506
44528
  }
44507
44529
  });
@@ -28727,6 +28727,7 @@ function PlatformerCanvas({
28727
28727
  const canvasRef = useRef(null);
28728
28728
  const eventBus = useEventBus();
28729
28729
  const keysRef = useRef(/* @__PURE__ */ new Set());
28730
+ const lastPlatCountRef = useRef(-1);
28730
28731
  const imageCache = useRef(/* @__PURE__ */ new Map());
28731
28732
  const [loadedImages, setLoadedImages] = useState(/* @__PURE__ */ new Set());
28732
28733
  const loadImage = useCallback((url) => {
@@ -28882,6 +28883,18 @@ function PlatformerCanvas({
28882
28883
  camX = Math.max(0, Math.min(px - cw / 2, ww - cw));
28883
28884
  camY = Math.max(0, Math.min(py - ch / 2 - 50, wh - ch));
28884
28885
  }
28886
+ if (plats.length !== lastPlatCountRef.current) {
28887
+ lastPlatCountRef.current = plats.length;
28888
+ canvasLog.debug("draw:platforms", {
28889
+ platformCount: plats.length,
28890
+ camX,
28891
+ camY,
28892
+ player: { x: px, y: py },
28893
+ worldWidth: ww,
28894
+ canvasWidth: cw,
28895
+ followCamera: fc
28896
+ });
28897
+ }
28885
28898
  const bgImage = bgImg ? loadImage(bgImg) : null;
28886
28899
  if (bgImage) {
28887
28900
  ctx.drawImage(bgImage, 0, 0, cw, ch);
@@ -29007,13 +29020,14 @@ function PlatformerCanvas({
29007
29020
  }
29008
29021
  );
29009
29022
  }
29010
- var PLATFORM_COLORS, PLAYER_COLOR, PLAYER_EYE_COLOR, SKY_GRADIENT_TOP, SKY_GRADIENT_BOTTOM, GRID_COLOR;
29023
+ var canvasLog, PLATFORM_COLORS, PLAYER_COLOR, PLAYER_EYE_COLOR, SKY_GRADIENT_TOP, SKY_GRADIENT_BOTTOM, GRID_COLOR;
29011
29024
  var init_PlatformerCanvas = __esm({
29012
29025
  "components/game/molecules/PlatformerCanvas.tsx"() {
29013
29026
  init_cn();
29014
29027
  init_useEventBus();
29015
29028
  init_verificationRegistry();
29016
29029
  init_useRenderInterpolation();
29030
+ canvasLog = createLogger("almadar:ui:game:platformer-canvas");
29017
29031
  PLATFORM_COLORS = {
29018
29032
  ground: "#4a7c59",
29019
29033
  platform: "#7c6b4a",
@@ -44359,14 +44373,20 @@ function PlatformerBoard({
44359
44373
  grounded: false,
44360
44374
  facingRight: true
44361
44375
  };
44362
- const entityPlatforms = Array.isArray(row["platforms"]) ? row["platforms"] : void 0;
44363
- const platforms = propPlatforms ?? entityPlatforms ?? [
44376
+ const platforms = propPlatforms ?? [
44364
44377
  { x: 0, y: 368, width: 800, height: 32, type: "ground" },
44365
44378
  { x: 150, y: 280, width: 160, height: 16, type: "platform" },
44366
44379
  { x: 420, y: 220, width: 160, height: 16, type: "platform" },
44367
44380
  { x: 580, y: 300, width: 80, height: 16, type: "hazard" },
44368
44381
  { x: 700, y: 340, width: 64, height: 28, type: "goal" }
44369
44382
  ];
44383
+ boardLog.debug("platforms-resolve", {
44384
+ propPlatformsCount: Array.isArray(propPlatforms) ? propPlatforms.length : null,
44385
+ usedFallback: propPlatforms === void 0,
44386
+ finalCount: platforms.length,
44387
+ player: { x: player.x, y: player.y, vx: player.vx, vy: player.vy, grounded: player.grounded },
44388
+ result
44389
+ });
44370
44390
  const handleRestart = useCallback(() => {
44371
44391
  if (playAgainEvent) {
44372
44392
  eventBus.emit(`UI:${playAgainEvent}`, {});
@@ -44445,6 +44465,7 @@ function PlatformerBoard({
44445
44465
  }
44446
44466
  );
44447
44467
  }
44468
+ var boardLog;
44448
44469
  var init_PlatformerBoard = __esm({
44449
44470
  "components/game/organisms/PlatformerBoard.tsx"() {
44450
44471
  "use client";
@@ -44455,6 +44476,7 @@ var init_PlatformerBoard = __esm({
44455
44476
  init_Typography();
44456
44477
  init_Stack();
44457
44478
  init_PlatformerCanvas();
44479
+ boardLog = createLogger("almadar:ui:game:platformer-board");
44458
44480
  PlatformerBoard.displayName = "PlatformerBoard";
44459
44481
  }
44460
44482
  });
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * @packageDocumentation
7
7
  */
8
- import type { EventPayload, ServiceParams } from '@almadar/core';
8
+ import type { EventPayload, EntityRow, ServiceParams } from '@almadar/core';
9
9
  import type { EffectHandlers } from '@almadar/runtime';
10
10
  export interface ClientEventBus {
11
11
  emit: (type: string, payload?: EventPayload) => void;
@@ -19,6 +19,17 @@ export interface CreateClientEffectHandlersOptions {
19
19
  slotSetter: SlotSetter;
20
20
  navigate?: (path: string, params?: Record<string, unknown>) => void;
21
21
  notify?: (message: string, type: 'success' | 'error' | 'warning' | 'info') => void;
22
+ /**
23
+ * Live client-entity write target for `[runtime]` entities. `(set
24
+ * @entity.<field> value)` runs entirely in the browser for in-memory
25
+ * entities (game boards, wizards): there is no server row to persist to,
26
+ * so the canonical client `set` must mutate THIS object — the same object
27
+ * `EffectExecutor` reads `@entity.*` from for the current `executeAll` and
28
+ * the next tick seeds from. When omitted, `set` is a no-op (bridge mode:
29
+ * the server owns persistence). One store, read live by render-ui, guards,
30
+ * and ticks — no guard-vs-render split.
31
+ */
32
+ liveEntity?: EntityRow;
22
33
  /**
23
34
  * Optional consumer-supplied call-service handler. When set, it runs
24
35
  * instead of the default mock fallback — use to wire the playground
@@ -28472,6 +28472,7 @@ function PlatformerCanvas({
28472
28472
  const canvasRef = React81.useRef(null);
28473
28473
  const eventBus = useEventBus();
28474
28474
  const keysRef = React81.useRef(/* @__PURE__ */ new Set());
28475
+ const lastPlatCountRef = React81.useRef(-1);
28475
28476
  const imageCache = React81.useRef(/* @__PURE__ */ new Map());
28476
28477
  const [loadedImages, setLoadedImages] = React81.useState(/* @__PURE__ */ new Set());
28477
28478
  const loadImage = React81.useCallback((url) => {
@@ -28627,6 +28628,18 @@ function PlatformerCanvas({
28627
28628
  camX = Math.max(0, Math.min(px - cw / 2, ww - cw));
28628
28629
  camY = Math.max(0, Math.min(py - ch / 2 - 50, wh - ch));
28629
28630
  }
28631
+ if (plats.length !== lastPlatCountRef.current) {
28632
+ lastPlatCountRef.current = plats.length;
28633
+ canvasLog.debug("draw:platforms", {
28634
+ platformCount: plats.length,
28635
+ camX,
28636
+ camY,
28637
+ player: { x: px, y: py },
28638
+ worldWidth: ww,
28639
+ canvasWidth: cw,
28640
+ followCamera: fc
28641
+ });
28642
+ }
28630
28643
  const bgImage = bgImg ? loadImage(bgImg) : null;
28631
28644
  if (bgImage) {
28632
28645
  ctx.drawImage(bgImage, 0, 0, cw, ch);
@@ -28752,13 +28765,14 @@ function PlatformerCanvas({
28752
28765
  }
28753
28766
  );
28754
28767
  }
28755
- var PLATFORM_COLORS, PLAYER_COLOR, PLAYER_EYE_COLOR, SKY_GRADIENT_TOP, SKY_GRADIENT_BOTTOM, GRID_COLOR;
28768
+ var canvasLog, PLATFORM_COLORS, PLAYER_COLOR, PLAYER_EYE_COLOR, SKY_GRADIENT_TOP, SKY_GRADIENT_BOTTOM, GRID_COLOR;
28756
28769
  var init_PlatformerCanvas = __esm({
28757
28770
  "components/game/molecules/PlatformerCanvas.tsx"() {
28758
28771
  init_cn();
28759
28772
  init_useEventBus();
28760
28773
  init_verificationRegistry();
28761
28774
  init_useRenderInterpolation();
28775
+ canvasLog = logger.createLogger("almadar:ui:game:platformer-canvas");
28762
28776
  PLATFORM_COLORS = {
28763
28777
  ground: "#4a7c59",
28764
28778
  platform: "#7c6b4a",
@@ -44104,14 +44118,20 @@ function PlatformerBoard({
44104
44118
  grounded: false,
44105
44119
  facingRight: true
44106
44120
  };
44107
- const entityPlatforms = Array.isArray(row["platforms"]) ? row["platforms"] : void 0;
44108
- const platforms = propPlatforms ?? entityPlatforms ?? [
44121
+ const platforms = propPlatforms ?? [
44109
44122
  { x: 0, y: 368, width: 800, height: 32, type: "ground" },
44110
44123
  { x: 150, y: 280, width: 160, height: 16, type: "platform" },
44111
44124
  { x: 420, y: 220, width: 160, height: 16, type: "platform" },
44112
44125
  { x: 580, y: 300, width: 80, height: 16, type: "hazard" },
44113
44126
  { x: 700, y: 340, width: 64, height: 28, type: "goal" }
44114
44127
  ];
44128
+ boardLog.debug("platforms-resolve", {
44129
+ propPlatformsCount: Array.isArray(propPlatforms) ? propPlatforms.length : null,
44130
+ usedFallback: propPlatforms === void 0,
44131
+ finalCount: platforms.length,
44132
+ player: { x: player.x, y: player.y, vx: player.vx, vy: player.vy, grounded: player.grounded },
44133
+ result
44134
+ });
44115
44135
  const handleRestart = React81.useCallback(() => {
44116
44136
  if (playAgainEvent) {
44117
44137
  eventBus.emit(`UI:${playAgainEvent}`, {});
@@ -44190,6 +44210,7 @@ function PlatformerBoard({
44190
44210
  }
44191
44211
  );
44192
44212
  }
44213
+ var boardLog;
44193
44214
  var init_PlatformerBoard = __esm({
44194
44215
  "components/game/organisms/PlatformerBoard.tsx"() {
44195
44216
  "use client";
@@ -44200,6 +44221,7 @@ var init_PlatformerBoard = __esm({
44200
44221
  init_Typography();
44201
44222
  init_Stack();
44202
44223
  init_PlatformerCanvas();
44224
+ boardLog = logger.createLogger("almadar:ui:game:platformer-board");
44203
44225
  PlatformerBoard.displayName = "PlatformerBoard";
44204
44226
  }
44205
44227
  });
@@ -51279,7 +51301,7 @@ var init_UISlotRenderer = __esm({
51279
51301
  });
51280
51302
  var log = logger.createLogger("almadar:ui:effects:client-handlers");
51281
51303
  function createClientEffectHandlers(options) {
51282
- const { eventBus, slotSetter, navigate, notify, callService } = options;
51304
+ const { eventBus, slotSetter, navigate, notify, callService, liveEntity } = options;
51283
51305
  return {
51284
51306
  emit: (event, payload) => {
51285
51307
  const prefixedEvent = event.startsWith("UI:") ? event : `UI:${event}`;
@@ -51288,8 +51310,12 @@ function createClientEffectHandlers(options) {
51288
51310
  persist: async () => {
51289
51311
  log.warn("persist is server-side only, ignored on client");
51290
51312
  },
51291
- set: () => {
51292
- log.warn("set is server-side only, ignored on client");
51313
+ set: (_entityId, field, value) => {
51314
+ if (!liveEntity) {
51315
+ log.warn("set is server-side only, ignored on client (no live entity)");
51316
+ return;
51317
+ }
51318
+ liveEntity[field] = value;
51293
51319
  },
51294
51320
  callService: async (service, action, params) => {
51295
51321
  if (callService) return callService(service, action, params);
@@ -51683,6 +51709,11 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
51683
51709
  const traitName = binding.trait.name;
51684
51710
  const linkedEntity = binding.linkedEntity || "";
51685
51711
  const entityId = payload?.entityId;
51712
+ let liveEntity = traitFieldStatesRef.current.get(traitName);
51713
+ if (!liveEntity) {
51714
+ liveEntity = {};
51715
+ traitFieldStatesRef.current.set(traitName, liveEntity);
51716
+ }
51686
51717
  const effects = syncOnly ? params.effects.filter(
51687
51718
  (e) => Array.isArray(e) && SYNC_TICK_OPERATORS.has(String(e[0]))
51688
51719
  ) : params.effects;
@@ -51705,7 +51736,12 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
51705
51736
  },
51706
51737
  navigate: optionsRef.current?.navigate,
51707
51738
  notify: optionsRef.current?.notify,
51708
- callService: optionsRef.current?.callService
51739
+ callService: optionsRef.current?.callService,
51740
+ // The canonical client `set` writes `(set @entity.X)` straight into
51741
+ // the trait's one live store — the same object bound below. This is
51742
+ // what makes a [runtime] entity's set (from ticks AND events) reach
51743
+ // the render-ui + next tick + guards.
51744
+ liveEntity
51709
51745
  });
51710
51746
  const persistence = syncOnly ? void 0 : optionsRef.current?.persistence;
51711
51747
  let handlers = clientHandlers;
@@ -51716,7 +51752,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
51716
51752
  // uses below. `@payload.*` resolves from `payload` separately,
51717
51753
  // so dropping the prior `payload as EntityRow` cast loses
51718
51754
  // nothing — it just stops mislabelling the payload as an entity.
51719
- entity: traitFieldStatesRef.current.get(traitName) ?? {},
51755
+ entity: liveEntity,
51720
51756
  payload: payload || {},
51721
51757
  state: previousState
51722
51758
  };
@@ -51756,24 +51792,17 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
51756
51792
  handlers = {
51757
51793
  ...handlers,
51758
51794
  set: async (targetId, field, value) => {
51759
- let fieldState = traitFieldStatesRef.current.get(traitName);
51760
- if (!fieldState) {
51761
- fieldState = {};
51762
- traitFieldStatesRef.current.set(traitName, fieldState);
51763
- }
51764
- fieldState[field] = value;
51795
+ if (baseSet) await baseSet(targetId, field, value);
51765
51796
  log14.debug("set:write", {
51766
51797
  traitName,
51767
51798
  field,
51768
51799
  value: JSON.stringify(value),
51769
51800
  transition: `${previousState}->${newState}`
51770
51801
  });
51771
- if (baseSet) await baseSet(targetId, field, value);
51772
51802
  }
51773
51803
  };
51774
- const entityForBinding = traitFieldStatesRef.current.get(traitName) ?? {};
51775
51804
  const bindingCtx = {
51776
- entity: entityForBinding,
51805
+ entity: liveEntity,
51777
51806
  payload: payload || {},
51778
51807
  state: previousState
51779
51808
  };
@@ -28425,6 +28425,7 @@ function PlatformerCanvas({
28425
28425
  const canvasRef = useRef(null);
28426
28426
  const eventBus = useEventBus();
28427
28427
  const keysRef = useRef(/* @__PURE__ */ new Set());
28428
+ const lastPlatCountRef = useRef(-1);
28428
28429
  const imageCache = useRef(/* @__PURE__ */ new Map());
28429
28430
  const [loadedImages, setLoadedImages] = useState(/* @__PURE__ */ new Set());
28430
28431
  const loadImage = useCallback((url) => {
@@ -28580,6 +28581,18 @@ function PlatformerCanvas({
28580
28581
  camX = Math.max(0, Math.min(px - cw / 2, ww - cw));
28581
28582
  camY = Math.max(0, Math.min(py - ch / 2 - 50, wh - ch));
28582
28583
  }
28584
+ if (plats.length !== lastPlatCountRef.current) {
28585
+ lastPlatCountRef.current = plats.length;
28586
+ canvasLog.debug("draw:platforms", {
28587
+ platformCount: plats.length,
28588
+ camX,
28589
+ camY,
28590
+ player: { x: px, y: py },
28591
+ worldWidth: ww,
28592
+ canvasWidth: cw,
28593
+ followCamera: fc
28594
+ });
28595
+ }
28583
28596
  const bgImage = bgImg ? loadImage(bgImg) : null;
28584
28597
  if (bgImage) {
28585
28598
  ctx.drawImage(bgImage, 0, 0, cw, ch);
@@ -28705,13 +28718,14 @@ function PlatformerCanvas({
28705
28718
  }
28706
28719
  );
28707
28720
  }
28708
- var PLATFORM_COLORS, PLAYER_COLOR, PLAYER_EYE_COLOR, SKY_GRADIENT_TOP, SKY_GRADIENT_BOTTOM, GRID_COLOR;
28721
+ var canvasLog, PLATFORM_COLORS, PLAYER_COLOR, PLAYER_EYE_COLOR, SKY_GRADIENT_TOP, SKY_GRADIENT_BOTTOM, GRID_COLOR;
28709
28722
  var init_PlatformerCanvas = __esm({
28710
28723
  "components/game/molecules/PlatformerCanvas.tsx"() {
28711
28724
  init_cn();
28712
28725
  init_useEventBus();
28713
28726
  init_verificationRegistry();
28714
28727
  init_useRenderInterpolation();
28728
+ canvasLog = createLogger("almadar:ui:game:platformer-canvas");
28715
28729
  PLATFORM_COLORS = {
28716
28730
  ground: "#4a7c59",
28717
28731
  platform: "#7c6b4a",
@@ -44057,14 +44071,20 @@ function PlatformerBoard({
44057
44071
  grounded: false,
44058
44072
  facingRight: true
44059
44073
  };
44060
- const entityPlatforms = Array.isArray(row["platforms"]) ? row["platforms"] : void 0;
44061
- const platforms = propPlatforms ?? entityPlatforms ?? [
44074
+ const platforms = propPlatforms ?? [
44062
44075
  { x: 0, y: 368, width: 800, height: 32, type: "ground" },
44063
44076
  { x: 150, y: 280, width: 160, height: 16, type: "platform" },
44064
44077
  { x: 420, y: 220, width: 160, height: 16, type: "platform" },
44065
44078
  { x: 580, y: 300, width: 80, height: 16, type: "hazard" },
44066
44079
  { x: 700, y: 340, width: 64, height: 28, type: "goal" }
44067
44080
  ];
44081
+ boardLog.debug("platforms-resolve", {
44082
+ propPlatformsCount: Array.isArray(propPlatforms) ? propPlatforms.length : null,
44083
+ usedFallback: propPlatforms === void 0,
44084
+ finalCount: platforms.length,
44085
+ player: { x: player.x, y: player.y, vx: player.vx, vy: player.vy, grounded: player.grounded },
44086
+ result
44087
+ });
44068
44088
  const handleRestart = useCallback(() => {
44069
44089
  if (playAgainEvent) {
44070
44090
  eventBus.emit(`UI:${playAgainEvent}`, {});
@@ -44143,6 +44163,7 @@ function PlatformerBoard({
44143
44163
  }
44144
44164
  );
44145
44165
  }
44166
+ var boardLog;
44146
44167
  var init_PlatformerBoard = __esm({
44147
44168
  "components/game/organisms/PlatformerBoard.tsx"() {
44148
44169
  "use client";
@@ -44153,6 +44174,7 @@ var init_PlatformerBoard = __esm({
44153
44174
  init_Typography();
44154
44175
  init_Stack();
44155
44176
  init_PlatformerCanvas();
44177
+ boardLog = createLogger("almadar:ui:game:platformer-board");
44156
44178
  PlatformerBoard.displayName = "PlatformerBoard";
44157
44179
  }
44158
44180
  });
@@ -51232,7 +51254,7 @@ var init_UISlotRenderer = __esm({
51232
51254
  });
51233
51255
  var log = createLogger("almadar:ui:effects:client-handlers");
51234
51256
  function createClientEffectHandlers(options) {
51235
- const { eventBus, slotSetter, navigate, notify, callService } = options;
51257
+ const { eventBus, slotSetter, navigate, notify, callService, liveEntity } = options;
51236
51258
  return {
51237
51259
  emit: (event, payload) => {
51238
51260
  const prefixedEvent = event.startsWith("UI:") ? event : `UI:${event}`;
@@ -51241,8 +51263,12 @@ function createClientEffectHandlers(options) {
51241
51263
  persist: async () => {
51242
51264
  log.warn("persist is server-side only, ignored on client");
51243
51265
  },
51244
- set: () => {
51245
- log.warn("set is server-side only, ignored on client");
51266
+ set: (_entityId, field, value) => {
51267
+ if (!liveEntity) {
51268
+ log.warn("set is server-side only, ignored on client (no live entity)");
51269
+ return;
51270
+ }
51271
+ liveEntity[field] = value;
51246
51272
  },
51247
51273
  callService: async (service, action, params) => {
51248
51274
  if (callService) return callService(service, action, params);
@@ -51636,6 +51662,11 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
51636
51662
  const traitName = binding.trait.name;
51637
51663
  const linkedEntity = binding.linkedEntity || "";
51638
51664
  const entityId = payload?.entityId;
51665
+ let liveEntity = traitFieldStatesRef.current.get(traitName);
51666
+ if (!liveEntity) {
51667
+ liveEntity = {};
51668
+ traitFieldStatesRef.current.set(traitName, liveEntity);
51669
+ }
51639
51670
  const effects = syncOnly ? params.effects.filter(
51640
51671
  (e) => Array.isArray(e) && SYNC_TICK_OPERATORS.has(String(e[0]))
51641
51672
  ) : params.effects;
@@ -51658,7 +51689,12 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
51658
51689
  },
51659
51690
  navigate: optionsRef.current?.navigate,
51660
51691
  notify: optionsRef.current?.notify,
51661
- callService: optionsRef.current?.callService
51692
+ callService: optionsRef.current?.callService,
51693
+ // The canonical client `set` writes `(set @entity.X)` straight into
51694
+ // the trait's one live store — the same object bound below. This is
51695
+ // what makes a [runtime] entity's set (from ticks AND events) reach
51696
+ // the render-ui + next tick + guards.
51697
+ liveEntity
51662
51698
  });
51663
51699
  const persistence = syncOnly ? void 0 : optionsRef.current?.persistence;
51664
51700
  let handlers = clientHandlers;
@@ -51669,7 +51705,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
51669
51705
  // uses below. `@payload.*` resolves from `payload` separately,
51670
51706
  // so dropping the prior `payload as EntityRow` cast loses
51671
51707
  // nothing — it just stops mislabelling the payload as an entity.
51672
- entity: traitFieldStatesRef.current.get(traitName) ?? {},
51708
+ entity: liveEntity,
51673
51709
  payload: payload || {},
51674
51710
  state: previousState
51675
51711
  };
@@ -51709,24 +51745,17 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
51709
51745
  handlers = {
51710
51746
  ...handlers,
51711
51747
  set: async (targetId, field, value) => {
51712
- let fieldState = traitFieldStatesRef.current.get(traitName);
51713
- if (!fieldState) {
51714
- fieldState = {};
51715
- traitFieldStatesRef.current.set(traitName, fieldState);
51716
- }
51717
- fieldState[field] = value;
51748
+ if (baseSet) await baseSet(targetId, field, value);
51718
51749
  log14.debug("set:write", {
51719
51750
  traitName,
51720
51751
  field,
51721
51752
  value: JSON.stringify(value),
51722
51753
  transition: `${previousState}->${newState}`
51723
51754
  });
51724
- if (baseSet) await baseSet(targetId, field, value);
51725
51755
  }
51726
51756
  };
51727
- const entityForBinding = traitFieldStatesRef.current.get(traitName) ?? {};
51728
51757
  const bindingCtx = {
51729
- entity: entityForBinding,
51758
+ entity: liveEntity,
51730
51759
  payload: payload || {},
51731
51760
  state: previousState
51732
51761
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@almadar/ui",
3
- "version": "5.51.0",
3
+ "version": "5.53.0",
4
4
  "description": "React UI components, hooks, and providers for Almadar",
5
5
  "type": "module",
6
6
  "sideEffects": [