@almadar/ui 5.143.0 → 5.144.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.
@@ -4950,6 +4950,10 @@ var init_Icon = __esm({
4950
4950
  function isTilesheet(a) {
4951
4951
  return typeof a.tileWidth === "number";
4952
4952
  }
4953
+ function isSpriteSheetAtlas(a) {
4954
+ const s = a;
4955
+ return typeof s.frameWidth === "number" && typeof s.frameHeight === "number" && typeof s.animations === "object";
4956
+ }
4953
4957
  function getAtlas(url, onReady) {
4954
4958
  if (atlasCache.has(url)) return atlasCache.get(url) ?? void 0;
4955
4959
  atlasCache.set(url, void 0);
@@ -4989,6 +4993,7 @@ function subRectFor(atlas, sprite) {
4989
4993
  sh: atlas.tileHeight
4990
4994
  };
4991
4995
  }
4996
+ if (isSpriteSheetAtlas(atlas)) return null;
4992
4997
  const st = atlas.subTextures[sprite];
4993
4998
  if (!st) return null;
4994
4999
  return { sx: st.x, sy: st.y, sw: st.width, sh: st.height };
@@ -5298,7 +5303,7 @@ var init_Button = __esm({
5298
5303
  "data-testid": dataTestId ?? (action ? `action-${action}` : void 0),
5299
5304
  children: [
5300
5305
  isLoading ? /* @__PURE__ */ jsxRuntime.jsx(LucideIcons2.Loader2, { className: "h-icon-default w-icon-default animate-spin" }) : resolvedLeftIcon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-shrink-0", children: resolvedLeftIcon }),
5301
- children || label,
5306
+ (Array.isArray(children) ? children.length > 0 : Boolean(children)) ? children : label,
5302
5307
  resolvedRightIcon && !isLoading && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-shrink-0", children: resolvedRightIcon })
5303
5308
  ]
5304
5309
  }
@@ -11042,6 +11047,34 @@ var init_isometric = __esm({
11042
11047
  };
11043
11048
  }
11044
11049
  });
11050
+ function isAnimationName(name) {
11051
+ return core.ANIMATION_NAMES.includes(name);
11052
+ }
11053
+ function frameRect(frame, row, columns, frameWidth, frameHeight) {
11054
+ return {
11055
+ sx: frame % columns * frameWidth,
11056
+ sy: row * frameHeight,
11057
+ sw: frameWidth,
11058
+ sh: frameHeight
11059
+ };
11060
+ }
11061
+ function getCurrentFrameFromDef(def, elapsed) {
11062
+ const frameDuration = 1e3 / def.frameRate;
11063
+ const totalDuration = def.frames * frameDuration;
11064
+ if (def.loop) {
11065
+ const frame2 = Math.floor(elapsed % totalDuration / frameDuration);
11066
+ return { frame: frame2, finished: false };
11067
+ }
11068
+ if (elapsed >= totalDuration) {
11069
+ return { frame: def.frames - 1, finished: true };
11070
+ }
11071
+ const frame = Math.floor(elapsed / frameDuration);
11072
+ return { frame, finished: false };
11073
+ }
11074
+ var init_spriteAnimation = __esm({
11075
+ "lib/spriteAnimation.ts"() {
11076
+ }
11077
+ });
11045
11078
 
11046
11079
  // lib/gameShared.ts
11047
11080
  var init_gameShared = __esm({
@@ -12902,6 +12935,9 @@ function paintFallbackSquare(painter, node, dctx, reason) {
12902
12935
  painter.strokeRect(rect.x, rect.y, rect.w, rect.h, "#5e564b", Math.max(1, tw / 32));
12903
12936
  painter.restore();
12904
12937
  }
12938
+ function isAnimatedSprite(node) {
12939
+ return node.animation !== void 0 && typeof node.asset?.atlas === "string";
12940
+ }
12905
12941
  function DrawSprite(_props) {
12906
12942
  return null;
12907
12943
  }
@@ -12910,6 +12946,7 @@ var init_DrawSprite = __esm({
12910
12946
  "components/game/atoms/DrawSprite.tsx"() {
12911
12947
  "use client";
12912
12948
  init_atlasSlice();
12949
+ init_spriteAnimation();
12913
12950
  init_imageCache();
12914
12951
  init_contract();
12915
12952
  spriteLog = logger.createLogger("almadar:ui:draw-sprite");
@@ -12923,6 +12960,23 @@ var init_DrawSprite = __esm({
12923
12960
  return;
12924
12961
  }
12925
12962
  let src = typeof node.frame === "object" ? node.frame : void 0;
12963
+ if (!src && node.animation !== void 0 && typeof node.asset.atlas === "string") {
12964
+ const atlas = getAtlas(node.asset.atlas, dctx.invalidate);
12965
+ if (!atlas) {
12966
+ if (atlasFailed(node.asset.atlas)) paintFallbackSquare(painter, node, dctx, "atlas-failed");
12967
+ return;
12968
+ }
12969
+ if (isSpriteSheetAtlas(atlas)) {
12970
+ const def = isAnimationName(node.animation) ? atlas.animations[node.animation] : void 0;
12971
+ if (!def) {
12972
+ paintFallbackSquare(painter, node, dctx, "sprite-missing");
12973
+ return;
12974
+ }
12975
+ const { frame } = getCurrentFrameFromDef(node.loop === void 0 ? def : { ...def, loop: node.loop }, dctx.time);
12976
+ const r2 = frameRect(frame, def.row, atlas.columns, atlas.frameWidth, atlas.frameHeight);
12977
+ src = { x: r2.sx, y: r2.sy, w: r2.sw, h: r2.sh };
12978
+ }
12979
+ }
12926
12980
  if (!src && isAtlasAsset(node.asset)) {
12927
12981
  const atlas = getAtlas(node.asset.atlas, dctx.invalidate);
12928
12982
  if (!atlas) {
@@ -13646,9 +13700,11 @@ function Canvas2D({
13646
13700
  const miniMapHeight = gridExtent.height || 10;
13647
13701
  const drawableIsAnimated = (node) => {
13648
13702
  if (node.type === "draw-shape") return isAnimatedShape(node);
13703
+ if (node.type === "draw-sprite") return isAnimatedSprite(node);
13649
13704
  if (node.type === "draw-group")
13650
13705
  return isAnimatedGroup(node) || Array.isArray(node.items) && node.items.some(drawableIsAnimated);
13651
13706
  if (node.type === "draw-shape-layer") return Array.isArray(node.items) && node.items.some(isAnimatedShape);
13707
+ if (node.type === "draw-sprite-layer") return Array.isArray(node.items) && node.items.some(isAnimatedSprite);
13652
13708
  return false;
13653
13709
  };
13654
13710
  const animRafRef = React94.useRef(0);
@@ -13989,6 +14045,7 @@ var init_Canvas2D = __esm({
13989
14045
  init_projector();
13990
14046
  init_paintDispatch();
13991
14047
  init_DrawShape();
14048
+ init_DrawSprite();
13992
14049
  init_DrawGroup();
13993
14050
  init_registry();
13994
14051
  init_hitTest();
@@ -14062,6 +14119,7 @@ function Canvas({
14062
14119
  cameraMode: to3DCameraMode(camera?.mode),
14063
14120
  ...zoom !== void 0 ? { scale: zoom } : {},
14064
14121
  ...camera?.fov !== void 0 ? { fov: camera.fov } : {},
14122
+ ...camera?.azimuth !== void 0 ? { azimuth: camera.azimuth } : {},
14065
14123
  ...camera?.target !== void 0 ? { followTarget: camera.target } : {},
14066
14124
  unitScale,
14067
14125
  backgroundColor,
@@ -25331,6 +25389,14 @@ var init_DashboardLayout = __esm({
25331
25389
  NavLinkBottom.displayName = "NavLinkBottom";
25332
25390
  }
25333
25391
  });
25392
+ function downloadItemUrl(url, label) {
25393
+ const a = document.createElement("a");
25394
+ a.href = url;
25395
+ a.download = url.startsWith("data:") ? label : url.split("/").pop() ?? "download";
25396
+ document.body.appendChild(a);
25397
+ a.click();
25398
+ a.remove();
25399
+ }
25334
25400
  function computeMenuStyle(position, triggerRect) {
25335
25401
  const isTop = position.startsWith("top");
25336
25402
  const isRight = position.endsWith("right") || position.endsWith("end");
@@ -25383,6 +25449,7 @@ function SubMenu({
25383
25449
  onClick: () => {
25384
25450
  if (item.disabled) return;
25385
25451
  if (item.event) eventBus.emit(`UI:${item.event}`, { itemId, label: item.label });
25452
+ if (item.url) downloadItemUrl(item.url, item.label);
25386
25453
  item.onClick?.();
25387
25454
  },
25388
25455
  "aria-disabled": item.disabled || void 0,
@@ -25536,6 +25603,7 @@ var init_Menu = __esm({
25536
25603
  setActiveSubMenu(itemId);
25537
25604
  } else {
25538
25605
  if (item.event) eventBus.emit(`UI:${item.event}`, { itemId, label: item.label });
25606
+ if (item.url) downloadItemUrl(item.url, item.label);
25539
25607
  item.onClick?.();
25540
25608
  setIsOpen(false);
25541
25609
  }
@@ -25571,12 +25639,19 @@ var init_Menu = __esm({
25571
25639
  "bottom-end": "bottom-start"
25572
25640
  };
25573
25641
  const effectivePosition = direction === "rtl" ? rtlMirror[position] ?? position : position;
25574
- const triggerChild = React94__namespace.default.isValidElement(trigger) ? trigger : /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", as: "span", children: trigger });
25575
- const triggerElement = React94__namespace.default.cloneElement(
25576
- triggerChild,
25642
+ const triggerElement = React94__namespace.default.isValidElement(trigger) ? React94__namespace.default.cloneElement(trigger, {
25643
+ ref: triggerRef,
25644
+ onClick: handleToggle
25645
+ }) : /* @__PURE__ */ jsxRuntime.jsx(
25646
+ Box,
25577
25647
  {
25578
- ref: triggerRef,
25579
- onClick: handleToggle
25648
+ as: "span",
25649
+ ref: (el) => {
25650
+ triggerRef.current = el;
25651
+ },
25652
+ onClick: handleToggle,
25653
+ className: "inline-flex",
25654
+ children: typeof trigger === "string" || typeof trigger === "number" ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", as: "span", children: trigger }) : trigger
25580
25655
  }
25581
25656
  );
25582
25657
  const renderMenuItems = (menuItems) => menuItems.map((item, index) => {
package/dist/avl/index.js CHANGED
@@ -7,7 +7,7 @@ import ELK from 'elkjs/lib/elk.bundled.js';
7
7
  import { MarkerType, useReactFlow, Handle, Position, getBezierPath, EdgeLabelRenderer, useNodeId, ReactFlowProvider, BaseEdge, useNodesState, useEdgesState, ReactFlow, Controls, Background, BackgroundVariant } from '@xyflow/react';
8
8
  import { useTranslate } from '@almadar/ui/hooks';
9
9
  import { InMemoryPersistence, StateMachineManager, collectDeclaredConfigDefaults, resolveCallSitePayloadCaptures, createServerEffectHandlers, EffectExecutor, createContextFromBindings, createTickScheduler, isValidCronExpression, parseDurationString, normalizeCallSiteConfigToValues, interpolateValue } from '@almadar/runtime';
10
- import { FieldTypeSchema, isInlineTrait, isPageReference, buildResolvedTraitConfigs, schemaToIR, getPage, mergeEntityFrame, isCircuitEvent, applyListenPayloadMapping, walkSExpr, isRenderBindingMarker, containsEntityBinding, isSExpr, isEventPayloadValue, RENDER_BINDING_MARKER } from '@almadar/core';
10
+ import { FieldTypeSchema, isInlineTrait, isPageReference, buildResolvedTraitConfigs, schemaToIR, getPage, mergeEntityFrame, isCircuitEvent, applyListenPayloadMapping, walkSExpr, isRenderBindingMarker, containsEntityBinding, isSExpr, isEventPayloadValue, RENDER_BINDING_MARKER, ANIMATION_NAMES } from '@almadar/core';
11
11
  import * as LucideIcons2 from 'lucide-react';
12
12
  import { Loader2, X, Code, FileText, WrapText, Check, Copy, Lightbulb, CheckCircle, List, Printer, ChevronRight, ChevronLeft, GitBranch, Pencil, Eye, Plus, ArrowRight, Trash, RotateCcw, Play, Terminal, XCircle, AlertTriangle, Trash2, Link2, ZoomOut, ZoomIn, Download, ChevronDown, Menu as Menu$1, Package, Calendar, MoreHorizontal, Image as Image$1, Upload, ArrowLeft, HelpCircle, PauseCircle, Search, Type, Heading1, Heading2, Heading3, ListOrdered, Quote, Minus, Eraser, TrendingUp, TrendingDown, AlertCircle, Circle, Clock, CheckCircle2, ChevronUp, Tag, User, DollarSign } from 'lucide-react';
13
13
  import { createPortal } from 'react-dom';
@@ -4874,6 +4874,10 @@ var init_Icon = __esm({
4874
4874
  function isTilesheet(a) {
4875
4875
  return typeof a.tileWidth === "number";
4876
4876
  }
4877
+ function isSpriteSheetAtlas(a) {
4878
+ const s = a;
4879
+ return typeof s.frameWidth === "number" && typeof s.frameHeight === "number" && typeof s.animations === "object";
4880
+ }
4877
4881
  function getAtlas(url, onReady) {
4878
4882
  if (atlasCache.has(url)) return atlasCache.get(url) ?? void 0;
4879
4883
  atlasCache.set(url, void 0);
@@ -4913,6 +4917,7 @@ function subRectFor(atlas, sprite) {
4913
4917
  sh: atlas.tileHeight
4914
4918
  };
4915
4919
  }
4920
+ if (isSpriteSheetAtlas(atlas)) return null;
4916
4921
  const st = atlas.subTextures[sprite];
4917
4922
  if (!st) return null;
4918
4923
  return { sx: st.x, sy: st.y, sw: st.width, sh: st.height };
@@ -5222,7 +5227,7 @@ var init_Button = __esm({
5222
5227
  "data-testid": dataTestId ?? (action ? `action-${action}` : void 0),
5223
5228
  children: [
5224
5229
  isLoading ? /* @__PURE__ */ jsx(Loader2, { className: "h-icon-default w-icon-default animate-spin" }) : resolvedLeftIcon && /* @__PURE__ */ jsx("span", { className: "flex-shrink-0", children: resolvedLeftIcon }),
5225
- children || label,
5230
+ (Array.isArray(children) ? children.length > 0 : Boolean(children)) ? children : label,
5226
5231
  resolvedRightIcon && !isLoading && /* @__PURE__ */ jsx("span", { className: "flex-shrink-0", children: resolvedRightIcon })
5227
5232
  ]
5228
5233
  }
@@ -10966,6 +10971,34 @@ var init_isometric = __esm({
10966
10971
  };
10967
10972
  }
10968
10973
  });
10974
+ function isAnimationName(name) {
10975
+ return ANIMATION_NAMES.includes(name);
10976
+ }
10977
+ function frameRect(frame, row, columns, frameWidth, frameHeight) {
10978
+ return {
10979
+ sx: frame % columns * frameWidth,
10980
+ sy: row * frameHeight,
10981
+ sw: frameWidth,
10982
+ sh: frameHeight
10983
+ };
10984
+ }
10985
+ function getCurrentFrameFromDef(def, elapsed) {
10986
+ const frameDuration = 1e3 / def.frameRate;
10987
+ const totalDuration = def.frames * frameDuration;
10988
+ if (def.loop) {
10989
+ const frame2 = Math.floor(elapsed % totalDuration / frameDuration);
10990
+ return { frame: frame2, finished: false };
10991
+ }
10992
+ if (elapsed >= totalDuration) {
10993
+ return { frame: def.frames - 1, finished: true };
10994
+ }
10995
+ const frame = Math.floor(elapsed / frameDuration);
10996
+ return { frame, finished: false };
10997
+ }
10998
+ var init_spriteAnimation = __esm({
10999
+ "lib/spriteAnimation.ts"() {
11000
+ }
11001
+ });
10969
11002
 
10970
11003
  // lib/gameShared.ts
10971
11004
  var init_gameShared = __esm({
@@ -12826,6 +12859,9 @@ function paintFallbackSquare(painter, node, dctx, reason) {
12826
12859
  painter.strokeRect(rect.x, rect.y, rect.w, rect.h, "#5e564b", Math.max(1, tw / 32));
12827
12860
  painter.restore();
12828
12861
  }
12862
+ function isAnimatedSprite(node) {
12863
+ return node.animation !== void 0 && typeof node.asset?.atlas === "string";
12864
+ }
12829
12865
  function DrawSprite(_props) {
12830
12866
  return null;
12831
12867
  }
@@ -12834,6 +12870,7 @@ var init_DrawSprite = __esm({
12834
12870
  "components/game/atoms/DrawSprite.tsx"() {
12835
12871
  "use client";
12836
12872
  init_atlasSlice();
12873
+ init_spriteAnimation();
12837
12874
  init_imageCache();
12838
12875
  init_contract();
12839
12876
  spriteLog = createLogger("almadar:ui:draw-sprite");
@@ -12847,6 +12884,23 @@ var init_DrawSprite = __esm({
12847
12884
  return;
12848
12885
  }
12849
12886
  let src = typeof node.frame === "object" ? node.frame : void 0;
12887
+ if (!src && node.animation !== void 0 && typeof node.asset.atlas === "string") {
12888
+ const atlas = getAtlas(node.asset.atlas, dctx.invalidate);
12889
+ if (!atlas) {
12890
+ if (atlasFailed(node.asset.atlas)) paintFallbackSquare(painter, node, dctx, "atlas-failed");
12891
+ return;
12892
+ }
12893
+ if (isSpriteSheetAtlas(atlas)) {
12894
+ const def = isAnimationName(node.animation) ? atlas.animations[node.animation] : void 0;
12895
+ if (!def) {
12896
+ paintFallbackSquare(painter, node, dctx, "sprite-missing");
12897
+ return;
12898
+ }
12899
+ const { frame } = getCurrentFrameFromDef(node.loop === void 0 ? def : { ...def, loop: node.loop }, dctx.time);
12900
+ const r2 = frameRect(frame, def.row, atlas.columns, atlas.frameWidth, atlas.frameHeight);
12901
+ src = { x: r2.sx, y: r2.sy, w: r2.sw, h: r2.sh };
12902
+ }
12903
+ }
12850
12904
  if (!src && isAtlasAsset(node.asset)) {
12851
12905
  const atlas = getAtlas(node.asset.atlas, dctx.invalidate);
12852
12906
  if (!atlas) {
@@ -13570,9 +13624,11 @@ function Canvas2D({
13570
13624
  const miniMapHeight = gridExtent.height || 10;
13571
13625
  const drawableIsAnimated = (node) => {
13572
13626
  if (node.type === "draw-shape") return isAnimatedShape(node);
13627
+ if (node.type === "draw-sprite") return isAnimatedSprite(node);
13573
13628
  if (node.type === "draw-group")
13574
13629
  return isAnimatedGroup(node) || Array.isArray(node.items) && node.items.some(drawableIsAnimated);
13575
13630
  if (node.type === "draw-shape-layer") return Array.isArray(node.items) && node.items.some(isAnimatedShape);
13631
+ if (node.type === "draw-sprite-layer") return Array.isArray(node.items) && node.items.some(isAnimatedSprite);
13576
13632
  return false;
13577
13633
  };
13578
13634
  const animRafRef = useRef(0);
@@ -13913,6 +13969,7 @@ var init_Canvas2D = __esm({
13913
13969
  init_projector();
13914
13970
  init_paintDispatch();
13915
13971
  init_DrawShape();
13972
+ init_DrawSprite();
13916
13973
  init_DrawGroup();
13917
13974
  init_registry();
13918
13975
  init_hitTest();
@@ -13986,6 +14043,7 @@ function Canvas({
13986
14043
  cameraMode: to3DCameraMode(camera?.mode),
13987
14044
  ...zoom !== void 0 ? { scale: zoom } : {},
13988
14045
  ...camera?.fov !== void 0 ? { fov: camera.fov } : {},
14046
+ ...camera?.azimuth !== void 0 ? { azimuth: camera.azimuth } : {},
13989
14047
  ...camera?.target !== void 0 ? { followTarget: camera.target } : {},
13990
14048
  unitScale,
13991
14049
  backgroundColor,
@@ -25255,6 +25313,14 @@ var init_DashboardLayout = __esm({
25255
25313
  NavLinkBottom.displayName = "NavLinkBottom";
25256
25314
  }
25257
25315
  });
25316
+ function downloadItemUrl(url, label) {
25317
+ const a = document.createElement("a");
25318
+ a.href = url;
25319
+ a.download = url.startsWith("data:") ? label : url.split("/").pop() ?? "download";
25320
+ document.body.appendChild(a);
25321
+ a.click();
25322
+ a.remove();
25323
+ }
25258
25324
  function computeMenuStyle(position, triggerRect) {
25259
25325
  const isTop = position.startsWith("top");
25260
25326
  const isRight = position.endsWith("right") || position.endsWith("end");
@@ -25307,6 +25373,7 @@ function SubMenu({
25307
25373
  onClick: () => {
25308
25374
  if (item.disabled) return;
25309
25375
  if (item.event) eventBus.emit(`UI:${item.event}`, { itemId, label: item.label });
25376
+ if (item.url) downloadItemUrl(item.url, item.label);
25310
25377
  item.onClick?.();
25311
25378
  },
25312
25379
  "aria-disabled": item.disabled || void 0,
@@ -25460,6 +25527,7 @@ var init_Menu = __esm({
25460
25527
  setActiveSubMenu(itemId);
25461
25528
  } else {
25462
25529
  if (item.event) eventBus.emit(`UI:${item.event}`, { itemId, label: item.label });
25530
+ if (item.url) downloadItemUrl(item.url, item.label);
25463
25531
  item.onClick?.();
25464
25532
  setIsOpen(false);
25465
25533
  }
@@ -25495,12 +25563,19 @@ var init_Menu = __esm({
25495
25563
  "bottom-end": "bottom-start"
25496
25564
  };
25497
25565
  const effectivePosition = direction === "rtl" ? rtlMirror[position] ?? position : position;
25498
- const triggerChild = React94__default.isValidElement(trigger) ? trigger : /* @__PURE__ */ jsx(Typography, { variant: "small", as: "span", children: trigger });
25499
- const triggerElement = React94__default.cloneElement(
25500
- triggerChild,
25566
+ const triggerElement = React94__default.isValidElement(trigger) ? React94__default.cloneElement(trigger, {
25567
+ ref: triggerRef,
25568
+ onClick: handleToggle
25569
+ }) : /* @__PURE__ */ jsx(
25570
+ Box,
25501
25571
  {
25502
- ref: triggerRef,
25503
- onClick: handleToggle
25572
+ as: "span",
25573
+ ref: (el) => {
25574
+ triggerRef.current = el;
25575
+ },
25576
+ onClick: handleToggle,
25577
+ className: "inline-flex",
25578
+ children: typeof trigger === "string" || typeof trigger === "number" ? /* @__PURE__ */ jsx(Typography, { variant: "small", as: "span", children: trigger }) : trigger
25504
25579
  }
25505
25580
  );
25506
25581
  const renderMenuItems = (menuItems) => menuItems.map((item, index) => {
@@ -1,7 +1,7 @@
1
1
  import { AnimationName, Asset, ScenePos, EventEmit, JsonObject, JsonValue } from '@almadar/core';
2
2
  import React__default from 'react';
3
3
  import * as THREE from 'three';
4
- import { D as DrawableNode } from './paintDispatch-D3-5_cOb.js';
4
+ import { D as DrawableNode } from './paintDispatch-Cb_hQj4Y.js';
5
5
 
6
6
  /**
7
7
  * Sprite Sheet Animation Types
@@ -60,12 +60,20 @@ interface SpriteFrameDims {
60
60
  /** Height of a single frame in pixels */
61
61
  height: number;
62
62
  }
63
- /** Sheet URLs for both directions */
63
+ /**
64
+ * Sheet URLs per facing. Legacy hand-drawn packs ship only se/sw (ne/nw are
65
+ * mirror-flipped — a cheat that shows the face while walking away); 3D-baked
66
+ * sheets also ship real ne/nw, which `resolveFrame` prefers when present.
67
+ */
64
68
  interface SpriteSheetUrls {
65
69
  /** Southeast-facing sheet URL */
66
70
  se: string;
67
71
  /** Southwest-facing sheet URL */
68
72
  sw: string;
73
+ /** Northeast-facing sheet URL (real back view; omitted → mirror of sw) */
74
+ ne?: string;
75
+ /** Northwest-facing sheet URL (real back view; omitted → mirror of se) */
76
+ nw?: string;
69
77
  }
70
78
 
71
79
  /**
@@ -484,6 +492,8 @@ interface Canvas3DHostProps {
484
492
  pixelsPerUnit?: number;
485
493
  /** Perspective field of view in degrees — the neutral `Camera.fov`. Default 45. */
486
494
  fov?: number;
495
+ /** Orbit the mode's framing position around the vertical axis through the target, in radians — the neutral `Camera.azimuth`. */
496
+ azimuth?: number;
487
497
  /** 3D scene light rig as data — ambient/directional/hemisphere/point lights + optional
488
498
  * 'room' environment. Omitted → the standard fixed rig (this host's current
489
499
  * hardcoded lights, unchanged). */
@@ -1,7 +1,7 @@
1
1
  import { AnimationName, Asset, ScenePos, EventEmit, JsonObject, JsonValue } from '@almadar/core';
2
2
  import React__default from 'react';
3
3
  import * as THREE from 'three';
4
- import { D as DrawableNode } from './paintDispatch-D3-5_cOb.cjs';
4
+ import { D as DrawableNode } from './paintDispatch-Cb_hQj4Y.cjs';
5
5
 
6
6
  /**
7
7
  * Sprite Sheet Animation Types
@@ -60,12 +60,20 @@ interface SpriteFrameDims {
60
60
  /** Height of a single frame in pixels */
61
61
  height: number;
62
62
  }
63
- /** Sheet URLs for both directions */
63
+ /**
64
+ * Sheet URLs per facing. Legacy hand-drawn packs ship only se/sw (ne/nw are
65
+ * mirror-flipped — a cheat that shows the face while walking away); 3D-baked
66
+ * sheets also ship real ne/nw, which `resolveFrame` prefers when present.
67
+ */
64
68
  interface SpriteSheetUrls {
65
69
  /** Southeast-facing sheet URL */
66
70
  se: string;
67
71
  /** Southwest-facing sheet URL */
68
72
  sw: string;
73
+ /** Northeast-facing sheet URL (real back view; omitted → mirror of sw) */
74
+ ne?: string;
75
+ /** Northwest-facing sheet URL (real back view; omitted → mirror of se) */
76
+ nw?: string;
69
77
  }
70
78
 
71
79
  /**
@@ -484,6 +492,8 @@ interface Canvas3DHostProps {
484
492
  pixelsPerUnit?: number;
485
493
  /** Perspective field of view in degrees — the neutral `Camera.fov`. Default 45. */
486
494
  fov?: number;
495
+ /** Orbit the mode's framing position around the vertical axis through the target, in radians — the neutral `Camera.azimuth`. */
496
+ azimuth?: number;
487
497
  /** 3D scene light rig as data — ambient/directional/hemisphere/point lights + optional
488
498
  * 'room' environment. Omitted → the standard fixed rig (this host's current
489
499
  * hardcoded lights, unchanged). */
@@ -1,5 +1,5 @@
1
1
  import { OrbitalSchema, SExpr, Effect, OrbitalVerificationAPI, TraitStateSnapshot, EventPayload, BusEvent, VerificationCheck, BridgeHealth, VerificationSnapshot, VerificationSummary, TransitionTrace, ServerResponseTrace, CheckStatus, AssetLoadStatus } from '@almadar/core';
2
- import { D as DrawableNode } from './paintDispatch-D3-5_cOb.cjs';
2
+ import { D as DrawableNode } from './paintDispatch-Cb_hQj4Y.cjs';
3
3
  import { ClassValue } from 'clsx';
4
4
 
5
5
  /**
@@ -1,5 +1,5 @@
1
1
  import { OrbitalSchema, SExpr, Effect, OrbitalVerificationAPI, TraitStateSnapshot, EventPayload, BusEvent, VerificationCheck, BridgeHealth, VerificationSnapshot, VerificationSummary, TransitionTrace, ServerResponseTrace, CheckStatus, AssetLoadStatus } from '@almadar/core';
2
- import { D as DrawableNode } from './paintDispatch-D3-5_cOb.js';
2
+ import { D as DrawableNode } from './paintDispatch-Cb_hQj4Y.js';
3
3
  import { ClassValue } from 'clsx';
4
4
 
5
5
  /**