@almadar/ui 5.112.0 → 5.114.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.
@@ -164,6 +164,11 @@ function useEmitEvent() {
164
164
  );
165
165
  }
166
166
 
167
+ // lib/drawable/contract.ts
168
+ function isValidScenePos(pos) {
169
+ return Number.isFinite(pos?.x) && Number.isFinite(pos?.y);
170
+ }
171
+
167
172
  // lib/drawable/hitTest.ts
168
173
  function collectDrawnItems(nodes) {
169
174
  const out = [];
@@ -172,12 +177,14 @@ function collectDrawnItems(nodes) {
172
177
  case "draw-sprite":
173
178
  case "draw-shape":
174
179
  case "draw-text":
175
- out.push({ pos: n.position, id: n.id });
180
+ if (isValidScenePos(n.position)) out.push({ pos: n.position, id: n.id });
176
181
  break;
177
182
  case "draw-sprite-layer":
178
183
  case "draw-shape-layer":
179
184
  case "draw-text-layer":
180
- for (const it of n.items) out.push({ pos: it.position, id: it.id });
185
+ for (const it of n.items) {
186
+ if (isValidScenePos(it.position)) out.push({ pos: it.position, id: it.id });
187
+ }
181
188
  break;
182
189
  }
183
190
  }
@@ -828,7 +835,7 @@ function useAtlasFrame(asset) {
828
835
  return { frame: { x: r.sx, y: r.sy, w: r.sw, h: r.sh }, ready: true };
829
836
  }, [asset, tick]);
830
837
  }
831
- function SpriteBillboard({ node, world }) {
838
+ function SpriteBillboard({ node, world, cellSize = 1 }) {
832
839
  const { texture, error: textureError } = useBillboardTexture(node.asset.url);
833
840
  const { frame: atlasFrame, ready: atlasReady } = useAtlasFrame(node.asset);
834
841
  const frame = node.frame ?? atlasFrame;
@@ -839,14 +846,14 @@ function SpriteBillboard({ node, world }) {
839
846
  const imgH = img?.height || 1;
840
847
  const aspect = frame ? frame.w / frame.h : imgW / imgH;
841
848
  if (anchor === "top-left") {
842
- const width2 = node.width ?? 1;
843
- const height2 = node.height ?? 1;
849
+ const width2 = (node.width ?? 1) * cellSize;
850
+ const height2 = (node.height ?? 1) * cellSize;
844
851
  return { width: width2, height: height2, imgW, imgH };
845
852
  }
846
853
  const height = node.height ?? 1;
847
854
  const width = node.width ?? height * aspect;
848
- return { width, height, imgW, imgH };
849
- }, [texture, frame, node.height, node.width, anchor]);
855
+ return { width: width * cellSize, height: height * cellSize, imgW, imgH };
856
+ }, [texture, frame, node.height, node.width, anchor, cellSize]);
850
857
  const groundGeometry = React3__default.default.useMemo(() => {
851
858
  if (anchor !== "top-left" || !texture || !atlasReady) return null;
852
859
  const g = new THREE9__namespace.PlaneGeometry(size.width, size.height);
@@ -898,31 +905,39 @@ function SpriteBillboard({ node, world }) {
898
905
  ] }) });
899
906
  }
900
907
  function Sprite3D({ node, projector }) {
901
- if (node.asset.dimension === "3d" && node.asset.url) {
908
+ const asset = node.asset;
909
+ if (!asset?.url || !isValidScenePos(node.position)) return null;
910
+ if (asset.dimension === "3d") {
911
+ const scale = node.width === void 0 ? 1 : node.height === void 0 ? node.width * projector.cellSize : [
912
+ node.width * projector.cellSize,
913
+ node.height * projector.cellSize,
914
+ node.width * projector.cellSize
915
+ ];
902
916
  return /* @__PURE__ */ jsxRuntime.jsx("group", { position: projector.toWorld(node.position), children: /* @__PURE__ */ jsxRuntime.jsx(
903
917
  ModelLoader,
904
918
  {
905
- url: node.asset.url,
906
- scale: node.width ?? projector.cellSize,
919
+ url: asset.url,
920
+ scale,
907
921
  rotation: [0, node.rotation ?? 0, 0],
922
+ animation: node.animation,
908
923
  fallbackGeometry: "box",
909
924
  castShadow: true,
910
925
  receiveShadow: true
911
926
  }
912
927
  ) });
913
928
  }
914
- if (!node.asset.url) return null;
915
- return /* @__PURE__ */ jsxRuntime.jsx(SpriteBillboard, { node, world: projector.toWorld(node.position) });
929
+ return /* @__PURE__ */ jsxRuntime.jsx(SpriteBillboard, { node, world: projector.toWorld(node.position), cellSize: projector.cellSize });
916
930
  }
917
931
  function Shape3D({ node, projector }) {
932
+ if (!isValidScenePos(node.position)) return null;
918
933
  const world = projector.toWorld(node.position);
919
934
  const color = node.fill ?? node.stroke ?? "#ffffff";
920
935
  let geometry;
921
936
  switch (node.shape) {
922
937
  case "cell":
923
938
  case "rect": {
924
- const w = node.shape === "cell" ? projector.cellSize * 0.95 : node.width ?? projector.cellSize;
925
- const h = node.shape === "cell" ? projector.cellSize * 0.95 : node.height ?? projector.cellSize;
939
+ const w = node.shape === "cell" ? projector.cellSize * 0.95 : (node.width ?? 1) * projector.cellSize;
940
+ const h = node.shape === "cell" ? projector.cellSize * 0.95 : (node.height ?? 1) * projector.cellSize;
926
941
  geometry = /* @__PURE__ */ jsxRuntime.jsx("planeGeometry", { args: [w, h] });
927
942
  break;
928
943
  }
@@ -951,6 +966,7 @@ function Shape3D({ node, projector }) {
951
966
  ] });
952
967
  }
953
968
  function Text3D({ node, projector }) {
969
+ if (!isValidScenePos(node.position)) return null;
954
970
  const world = projector.toWorld(node.position);
955
971
  return /* @__PURE__ */ jsxRuntime.jsx(drei.Billboard, { position: [world[0], world[1] + 1.2, world[2]], children: /* @__PURE__ */ jsxRuntime.jsx(
956
972
  drei.Text,
@@ -1003,10 +1019,7 @@ function cn(...inputs) {
1003
1019
  return tailwindMerge.twMerge(clsx.clsx(inputs));
1004
1020
  }
1005
1021
  var DEFAULT_GRID_CONFIG = {
1006
- cellSize: 1,
1007
- offsetX: 0,
1008
- offsetZ: 0
1009
- };
1022
+ cellSize: 1};
1010
1023
  var Canvas3DHost = React3.forwardRef(
1011
1024
  ({
1012
1025
  cameraMode = "isometric",
@@ -1030,6 +1043,7 @@ var Canvas3DHost = React3.forwardRef(
1030
1043
  loadingMessage = "Loading 3D Scene...",
1031
1044
  keyMap,
1032
1045
  keyUpMap,
1046
+ pixelsPerUnit,
1033
1047
  children,
1034
1048
  drawables
1035
1049
  }, ref) => {
@@ -1090,21 +1104,22 @@ var Canvas3DHost = React3.forwardRef(
1090
1104
  }
1091
1105
  return { minX, maxX, minZ, maxZ };
1092
1106
  }, [scenePositions]);
1107
+ const cellSize = pixelsPerUnit !== void 0 && pixelsPerUnit > 0 ? 1 / pixelsPerUnit : DEFAULT_GRID_CONFIG.cellSize;
1093
1108
  const cameraTarget = React3.useMemo(
1094
1109
  () => [
1095
- (gridBounds.minX + gridBounds.maxX) / 2,
1110
+ (gridBounds.maxX - gridBounds.minX) / 2 * cellSize,
1096
1111
  0,
1097
- (gridBounds.minZ + gridBounds.maxZ) / 2
1112
+ (gridBounds.maxZ - gridBounds.minZ) / 2 * cellSize
1098
1113
  ],
1099
- [gridBounds]
1114
+ [gridBounds, cellSize]
1100
1115
  );
1101
1116
  const gridConfig = React3.useMemo(
1102
1117
  () => ({
1103
- ...DEFAULT_GRID_CONFIG,
1118
+ cellSize,
1104
1119
  offsetX: -(gridBounds.maxX - gridBounds.minX) / 2,
1105
1120
  offsetZ: -(gridBounds.maxZ - gridBounds.minZ) / 2
1106
1121
  }),
1107
- [gridBounds]
1122
+ [gridBounds, cellSize]
1108
1123
  );
1109
1124
  const drawableProjector = React3.useMemo(
1110
1125
  () => create3DProjector({
@@ -1149,13 +1164,13 @@ var Canvas3DHost = React3.forwardRef(
1149
1164
  }));
1150
1165
  const cameraConfig = React3.useMemo(() => {
1151
1166
  const size = Math.max(
1152
- gridBounds.maxX - gridBounds.minX,
1153
- gridBounds.maxZ - gridBounds.minZ,
1167
+ (gridBounds.maxX - gridBounds.minX) * cellSize,
1168
+ (gridBounds.maxZ - gridBounds.minZ) * cellSize,
1154
1169
  4
1155
1170
  // minimum framing distance so a tiny board isn't zoomed in
1156
1171
  );
1157
- const cx = (gridBounds.minX + gridBounds.maxX) / 2;
1158
- const cz = (gridBounds.minZ + gridBounds.maxZ) / 2;
1172
+ const cx = cameraTarget[0];
1173
+ const cz = cameraTarget[2];
1159
1174
  const d = size * 1;
1160
1175
  switch (cameraMode) {
1161
1176
  case "isometric":
@@ -1168,13 +1183,13 @@ var Canvas3DHost = React3.forwardRef(
1168
1183
  default:
1169
1184
  return { position: [cx + d, d, cz + d], fov: 45 };
1170
1185
  }
1171
- }, [cameraMode, gridBounds]);
1186
+ }, [cameraMode, gridBounds, cellSize, cameraTarget]);
1172
1187
  const followWorld = React3.useMemo(() => {
1173
1188
  if (followTarget) return drawableProjector.toWorld(followTarget);
1174
1189
  return cameraTarget;
1175
1190
  }, [followTarget, drawableProjector, cameraTarget]);
1176
1191
  const followOffset = React3.useMemo(
1177
- () => cameraMode === "chase" ? [0, 4, -7] : [5, 7, 5],
1192
+ () => cameraMode === "chase" ? [0, 4, -7] : [0, 12, 14],
1178
1193
  [cameraMode]
1179
1194
  );
1180
1195
  const handleGroundClick = React3.useCallback((e) => {
@@ -1258,13 +1273,13 @@ var Canvas3DHost = React3.forwardRef(
1258
1273
  drei.Grid,
1259
1274
  {
1260
1275
  args: [
1261
- Math.max(gridBounds.maxX - gridBounds.minX + 2, 10),
1262
- Math.max(gridBounds.maxZ - gridBounds.minZ + 2, 10)
1276
+ Math.max((gridBounds.maxX - gridBounds.minX + 2) * cellSize, 10),
1277
+ Math.max((gridBounds.maxZ - gridBounds.minZ + 2) * cellSize, 10)
1263
1278
  ],
1264
1279
  position: [
1265
- (gridBounds.maxX - gridBounds.minX) / 2 - 0.5,
1280
+ (gridBounds.maxX - gridBounds.minX) / 2 * cellSize - cellSize / 2,
1266
1281
  0,
1267
- (gridBounds.maxZ - gridBounds.minZ) / 2 - 0.5
1282
+ (gridBounds.maxZ - gridBounds.minZ) / 2 * cellSize - cellSize / 2
1268
1283
  ],
1269
1284
  cellSize: 1,
1270
1285
  cellThickness: 1,
@@ -1282,9 +1297,9 @@ var Canvas3DHost = React3.forwardRef(
1282
1297
  {
1283
1298
  rotation: [-Math.PI / 2, 0, 0],
1284
1299
  position: [
1285
- (gridBounds.maxX - gridBounds.minX) / 2 * gridConfig.cellSize,
1300
+ (gridBounds.maxX - gridBounds.minX) / 2 * cellSize,
1286
1301
  0,
1287
- (gridBounds.maxZ - gridBounds.minZ) / 2 * gridConfig.cellSize
1302
+ (gridBounds.maxZ - gridBounds.minZ) / 2 * cellSize
1288
1303
  ],
1289
1304
  onClick: handleGroundClick,
1290
1305
  children: [
@@ -1292,8 +1307,8 @@ var Canvas3DHost = React3.forwardRef(
1292
1307
  "planeGeometry",
1293
1308
  {
1294
1309
  args: [
1295
- (gridBounds.maxX - gridBounds.minX + 4) * gridConfig.cellSize,
1296
- (gridBounds.maxZ - gridBounds.minZ + 4) * gridConfig.cellSize
1310
+ (gridBounds.maxX - gridBounds.minX + 4) * cellSize,
1311
+ (gridBounds.maxZ - gridBounds.minZ + 4) * cellSize
1297
1312
  ]
1298
1313
  }
1299
1314
  ),
@@ -3,9 +3,10 @@ import React__default, { Component, ReactNode, ErrorInfo } from 'react';
3
3
  import { ScenePos, EventEmit, JsonObject, OrbitalSchema } from '@almadar/core';
4
4
  import * as THREE from 'three';
5
5
  import { QuadraticBezierCurve3 } from 'three';
6
- import { D as DrawableNode, I as IsometricTile, a as IsometricUnit, b as IsometricFeature, A as ApplicationLevelData, O as OrbitalLevelData, T as TraitLevelData, c as TransitionLevelData } from '../../../avl-schema-parser-DKRm-nyh.cjs';
7
- export { U as UnitAnimationState } from '../../../avl-schema-parser-DKRm-nyh.cjs';
6
+ import { D as DrawableNode } from '../../../paintDispatch-BXJgISot.cjs';
8
7
  import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
8
+ import { I as IsometricTile, a as IsometricUnit, b as IsometricFeature, A as ApplicationLevelData, O as OrbitalLevelData, T as TraitLevelData, c as TransitionLevelData } from '../../../avl-schema-parser-B8Onmfsu.cjs';
9
+ export { U as UnitAnimationState } from '../../../avl-schema-parser-B8Onmfsu.cjs';
9
10
 
10
11
  /**
11
12
  * Canvas3DHost — the thin 3D draw-host: the `canvas` host's 3D painter backend
@@ -150,7 +151,8 @@ interface Canvas3DHostProps {
150
151
  worldWidth?: number;
151
152
  /** Side-view world size in pixels (accepted for API parity). */
152
153
  worldHeight?: number;
153
- /** Pixel→world-unit divisor for side view (accepted for API parity). */
154
+ /** Pixel→world-unit divisor for pixel-authored (side-view) scenes: world size =
155
+ * scene size ÷ `pixelsPerUnit`. Omitted → 1 world unit per scene unit (grid boards). */
154
156
  pixelsPerUnit?: number;
155
157
  }
156
158
  /** Imperative handle for GameCanvas3D */
@@ -3,9 +3,10 @@ import React__default, { Component, ReactNode, ErrorInfo } from 'react';
3
3
  import { ScenePos, EventEmit, JsonObject, OrbitalSchema } from '@almadar/core';
4
4
  import * as THREE from 'three';
5
5
  import { QuadraticBezierCurve3 } from 'three';
6
- import { D as DrawableNode, I as IsometricTile, a as IsometricUnit, b as IsometricFeature, A as ApplicationLevelData, O as OrbitalLevelData, T as TraitLevelData, c as TransitionLevelData } from '../../../avl-schema-parser-DKRm-nyh.js';
7
- export { U as UnitAnimationState } from '../../../avl-schema-parser-DKRm-nyh.js';
6
+ import { D as DrawableNode } from '../../../paintDispatch-BXJgISot.js';
8
7
  import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
8
+ import { I as IsometricTile, a as IsometricUnit, b as IsometricFeature, A as ApplicationLevelData, O as OrbitalLevelData, T as TraitLevelData, c as TransitionLevelData } from '../../../avl-schema-parser-B8Onmfsu.js';
9
+ export { U as UnitAnimationState } from '../../../avl-schema-parser-B8Onmfsu.js';
9
10
 
10
11
  /**
11
12
  * Canvas3DHost — the thin 3D draw-host: the `canvas` host's 3D painter backend
@@ -150,7 +151,8 @@ interface Canvas3DHostProps {
150
151
  worldWidth?: number;
151
152
  /** Side-view world size in pixels (accepted for API parity). */
152
153
  worldHeight?: number;
153
- /** Pixel→world-unit divisor for side view (accepted for API parity). */
154
+ /** Pixel→world-unit divisor for pixel-authored (side-view) scenes: world size =
155
+ * scene size ÷ `pixelsPerUnit`. Omitted → 1 world unit per scene unit (grid boards). */
154
156
  pixelsPerUnit?: number;
155
157
  }
156
158
  /** Imperative handle for GameCanvas3D */
@@ -140,6 +140,11 @@ function useEmitEvent() {
140
140
  );
141
141
  }
142
142
 
143
+ // lib/drawable/contract.ts
144
+ function isValidScenePos(pos) {
145
+ return Number.isFinite(pos?.x) && Number.isFinite(pos?.y);
146
+ }
147
+
143
148
  // lib/drawable/hitTest.ts
144
149
  function collectDrawnItems(nodes) {
145
150
  const out = [];
@@ -148,12 +153,14 @@ function collectDrawnItems(nodes) {
148
153
  case "draw-sprite":
149
154
  case "draw-shape":
150
155
  case "draw-text":
151
- out.push({ pos: n.position, id: n.id });
156
+ if (isValidScenePos(n.position)) out.push({ pos: n.position, id: n.id });
152
157
  break;
153
158
  case "draw-sprite-layer":
154
159
  case "draw-shape-layer":
155
160
  case "draw-text-layer":
156
- for (const it of n.items) out.push({ pos: it.position, id: it.id });
161
+ for (const it of n.items) {
162
+ if (isValidScenePos(it.position)) out.push({ pos: it.position, id: it.id });
163
+ }
157
164
  break;
158
165
  }
159
166
  }
@@ -804,7 +811,7 @@ function useAtlasFrame(asset) {
804
811
  return { frame: { x: r.sx, y: r.sy, w: r.sw, h: r.sh }, ready: true };
805
812
  }, [asset, tick]);
806
813
  }
807
- function SpriteBillboard({ node, world }) {
814
+ function SpriteBillboard({ node, world, cellSize = 1 }) {
808
815
  const { texture, error: textureError } = useBillboardTexture(node.asset.url);
809
816
  const { frame: atlasFrame, ready: atlasReady } = useAtlasFrame(node.asset);
810
817
  const frame = node.frame ?? atlasFrame;
@@ -815,14 +822,14 @@ function SpriteBillboard({ node, world }) {
815
822
  const imgH = img?.height || 1;
816
823
  const aspect = frame ? frame.w / frame.h : imgW / imgH;
817
824
  if (anchor === "top-left") {
818
- const width2 = node.width ?? 1;
819
- const height2 = node.height ?? 1;
825
+ const width2 = (node.width ?? 1) * cellSize;
826
+ const height2 = (node.height ?? 1) * cellSize;
820
827
  return { width: width2, height: height2, imgW, imgH };
821
828
  }
822
829
  const height = node.height ?? 1;
823
830
  const width = node.width ?? height * aspect;
824
- return { width, height, imgW, imgH };
825
- }, [texture, frame, node.height, node.width, anchor]);
831
+ return { width: width * cellSize, height: height * cellSize, imgW, imgH };
832
+ }, [texture, frame, node.height, node.width, anchor, cellSize]);
826
833
  const groundGeometry = React3.useMemo(() => {
827
834
  if (anchor !== "top-left" || !texture || !atlasReady) return null;
828
835
  const g = new THREE9.PlaneGeometry(size.width, size.height);
@@ -874,31 +881,39 @@ function SpriteBillboard({ node, world }) {
874
881
  ] }) });
875
882
  }
876
883
  function Sprite3D({ node, projector }) {
877
- if (node.asset.dimension === "3d" && node.asset.url) {
884
+ const asset = node.asset;
885
+ if (!asset?.url || !isValidScenePos(node.position)) return null;
886
+ if (asset.dimension === "3d") {
887
+ const scale = node.width === void 0 ? 1 : node.height === void 0 ? node.width * projector.cellSize : [
888
+ node.width * projector.cellSize,
889
+ node.height * projector.cellSize,
890
+ node.width * projector.cellSize
891
+ ];
878
892
  return /* @__PURE__ */ jsx("group", { position: projector.toWorld(node.position), children: /* @__PURE__ */ jsx(
879
893
  ModelLoader,
880
894
  {
881
- url: node.asset.url,
882
- scale: node.width ?? projector.cellSize,
895
+ url: asset.url,
896
+ scale,
883
897
  rotation: [0, node.rotation ?? 0, 0],
898
+ animation: node.animation,
884
899
  fallbackGeometry: "box",
885
900
  castShadow: true,
886
901
  receiveShadow: true
887
902
  }
888
903
  ) });
889
904
  }
890
- if (!node.asset.url) return null;
891
- return /* @__PURE__ */ jsx(SpriteBillboard, { node, world: projector.toWorld(node.position) });
905
+ return /* @__PURE__ */ jsx(SpriteBillboard, { node, world: projector.toWorld(node.position), cellSize: projector.cellSize });
892
906
  }
893
907
  function Shape3D({ node, projector }) {
908
+ if (!isValidScenePos(node.position)) return null;
894
909
  const world = projector.toWorld(node.position);
895
910
  const color = node.fill ?? node.stroke ?? "#ffffff";
896
911
  let geometry;
897
912
  switch (node.shape) {
898
913
  case "cell":
899
914
  case "rect": {
900
- const w = node.shape === "cell" ? projector.cellSize * 0.95 : node.width ?? projector.cellSize;
901
- const h = node.shape === "cell" ? projector.cellSize * 0.95 : node.height ?? projector.cellSize;
915
+ const w = node.shape === "cell" ? projector.cellSize * 0.95 : (node.width ?? 1) * projector.cellSize;
916
+ const h = node.shape === "cell" ? projector.cellSize * 0.95 : (node.height ?? 1) * projector.cellSize;
902
917
  geometry = /* @__PURE__ */ jsx("planeGeometry", { args: [w, h] });
903
918
  break;
904
919
  }
@@ -927,6 +942,7 @@ function Shape3D({ node, projector }) {
927
942
  ] });
928
943
  }
929
944
  function Text3D({ node, projector }) {
945
+ if (!isValidScenePos(node.position)) return null;
930
946
  const world = projector.toWorld(node.position);
931
947
  return /* @__PURE__ */ jsx(Billboard, { position: [world[0], world[1] + 1.2, world[2]], children: /* @__PURE__ */ jsx(
932
948
  Text,
@@ -979,10 +995,7 @@ function cn(...inputs) {
979
995
  return twMerge(clsx(inputs));
980
996
  }
981
997
  var DEFAULT_GRID_CONFIG = {
982
- cellSize: 1,
983
- offsetX: 0,
984
- offsetZ: 0
985
- };
998
+ cellSize: 1};
986
999
  var Canvas3DHost = forwardRef(
987
1000
  ({
988
1001
  cameraMode = "isometric",
@@ -1006,6 +1019,7 @@ var Canvas3DHost = forwardRef(
1006
1019
  loadingMessage = "Loading 3D Scene...",
1007
1020
  keyMap,
1008
1021
  keyUpMap,
1022
+ pixelsPerUnit,
1009
1023
  children,
1010
1024
  drawables
1011
1025
  }, ref) => {
@@ -1066,21 +1080,22 @@ var Canvas3DHost = forwardRef(
1066
1080
  }
1067
1081
  return { minX, maxX, minZ, maxZ };
1068
1082
  }, [scenePositions]);
1083
+ const cellSize = pixelsPerUnit !== void 0 && pixelsPerUnit > 0 ? 1 / pixelsPerUnit : DEFAULT_GRID_CONFIG.cellSize;
1069
1084
  const cameraTarget = useMemo(
1070
1085
  () => [
1071
- (gridBounds.minX + gridBounds.maxX) / 2,
1086
+ (gridBounds.maxX - gridBounds.minX) / 2 * cellSize,
1072
1087
  0,
1073
- (gridBounds.minZ + gridBounds.maxZ) / 2
1088
+ (gridBounds.maxZ - gridBounds.minZ) / 2 * cellSize
1074
1089
  ],
1075
- [gridBounds]
1090
+ [gridBounds, cellSize]
1076
1091
  );
1077
1092
  const gridConfig = useMemo(
1078
1093
  () => ({
1079
- ...DEFAULT_GRID_CONFIG,
1094
+ cellSize,
1080
1095
  offsetX: -(gridBounds.maxX - gridBounds.minX) / 2,
1081
1096
  offsetZ: -(gridBounds.maxZ - gridBounds.minZ) / 2
1082
1097
  }),
1083
- [gridBounds]
1098
+ [gridBounds, cellSize]
1084
1099
  );
1085
1100
  const drawableProjector = useMemo(
1086
1101
  () => create3DProjector({
@@ -1125,13 +1140,13 @@ var Canvas3DHost = forwardRef(
1125
1140
  }));
1126
1141
  const cameraConfig = useMemo(() => {
1127
1142
  const size = Math.max(
1128
- gridBounds.maxX - gridBounds.minX,
1129
- gridBounds.maxZ - gridBounds.minZ,
1143
+ (gridBounds.maxX - gridBounds.minX) * cellSize,
1144
+ (gridBounds.maxZ - gridBounds.minZ) * cellSize,
1130
1145
  4
1131
1146
  // minimum framing distance so a tiny board isn't zoomed in
1132
1147
  );
1133
- const cx = (gridBounds.minX + gridBounds.maxX) / 2;
1134
- const cz = (gridBounds.minZ + gridBounds.maxZ) / 2;
1148
+ const cx = cameraTarget[0];
1149
+ const cz = cameraTarget[2];
1135
1150
  const d = size * 1;
1136
1151
  switch (cameraMode) {
1137
1152
  case "isometric":
@@ -1144,13 +1159,13 @@ var Canvas3DHost = forwardRef(
1144
1159
  default:
1145
1160
  return { position: [cx + d, d, cz + d], fov: 45 };
1146
1161
  }
1147
- }, [cameraMode, gridBounds]);
1162
+ }, [cameraMode, gridBounds, cellSize, cameraTarget]);
1148
1163
  const followWorld = useMemo(() => {
1149
1164
  if (followTarget) return drawableProjector.toWorld(followTarget);
1150
1165
  return cameraTarget;
1151
1166
  }, [followTarget, drawableProjector, cameraTarget]);
1152
1167
  const followOffset = useMemo(
1153
- () => cameraMode === "chase" ? [0, 4, -7] : [5, 7, 5],
1168
+ () => cameraMode === "chase" ? [0, 4, -7] : [0, 12, 14],
1154
1169
  [cameraMode]
1155
1170
  );
1156
1171
  const handleGroundClick = useCallback((e) => {
@@ -1234,13 +1249,13 @@ var Canvas3DHost = forwardRef(
1234
1249
  Grid,
1235
1250
  {
1236
1251
  args: [
1237
- Math.max(gridBounds.maxX - gridBounds.minX + 2, 10),
1238
- Math.max(gridBounds.maxZ - gridBounds.minZ + 2, 10)
1252
+ Math.max((gridBounds.maxX - gridBounds.minX + 2) * cellSize, 10),
1253
+ Math.max((gridBounds.maxZ - gridBounds.minZ + 2) * cellSize, 10)
1239
1254
  ],
1240
1255
  position: [
1241
- (gridBounds.maxX - gridBounds.minX) / 2 - 0.5,
1256
+ (gridBounds.maxX - gridBounds.minX) / 2 * cellSize - cellSize / 2,
1242
1257
  0,
1243
- (gridBounds.maxZ - gridBounds.minZ) / 2 - 0.5
1258
+ (gridBounds.maxZ - gridBounds.minZ) / 2 * cellSize - cellSize / 2
1244
1259
  ],
1245
1260
  cellSize: 1,
1246
1261
  cellThickness: 1,
@@ -1258,9 +1273,9 @@ var Canvas3DHost = forwardRef(
1258
1273
  {
1259
1274
  rotation: [-Math.PI / 2, 0, 0],
1260
1275
  position: [
1261
- (gridBounds.maxX - gridBounds.minX) / 2 * gridConfig.cellSize,
1276
+ (gridBounds.maxX - gridBounds.minX) / 2 * cellSize,
1262
1277
  0,
1263
- (gridBounds.maxZ - gridBounds.minZ) / 2 * gridConfig.cellSize
1278
+ (gridBounds.maxZ - gridBounds.minZ) / 2 * cellSize
1264
1279
  ],
1265
1280
  onClick: handleGroundClick,
1266
1281
  children: [
@@ -1268,8 +1283,8 @@ var Canvas3DHost = forwardRef(
1268
1283
  "planeGeometry",
1269
1284
  {
1270
1285
  args: [
1271
- (gridBounds.maxX - gridBounds.minX + 4) * gridConfig.cellSize,
1272
- (gridBounds.maxZ - gridBounds.minZ + 4) * gridConfig.cellSize
1286
+ (gridBounds.maxX - gridBounds.minX + 4) * cellSize,
1287
+ (gridBounds.maxZ - gridBounds.minZ + 4) * cellSize
1273
1288
  ]
1274
1289
  }
1275
1290
  ),
@@ -1,6 +1,7 @@
1
- export { C as ContentSegment, D as DEFAULT_CONFIG, a as DomEntityBox, b as DomLayoutData, c as DomOutputsBox, d as DomStateNode, e as DomTransitionLabel, f as DomTransitionPath, E as EntityDefinition, R as RenderOptions, S as StateDefinition, g as StateMachineDefinition, T as TraitSnapshotGetter, h as TransitionDefinition, V as VisualizerConfig, i as bindCanvasCapture, j as bindEventBus, k as bindLastDrawables, l as bindTraitStateGetter, m as clearVerification, n as cn, o as extractOutputsFromTransitions, p as extractStateMachine, q as formatGuard, r as getAllChecks, s as getBridgeHealth, t as getEffectSummary, u as getSnapshot, v as getSummary, w as getTraitSnapshots, x as getTransitions, y as getTransitionsForTrait, z as parseContentSegments, A as parseMarkdownWithCodeBlocks, B as recordServerResponse, F as recordTransition, G as registerCheck, H as registerTraitSnapshot, I as renderStateMachineToDomData, J as renderStateMachineToSvg, K as subscribeToVerification, L as updateAssetStatus, M as updateBridgeHealth, N as updateCheck, O as waitForTransition } from '../cn-BbPvQLHt.cjs';
1
+ export { C as ContentSegment, D as DEFAULT_CONFIG, a as DomEntityBox, b as DomLayoutData, c as DomOutputsBox, d as DomStateNode, e as DomTransitionLabel, f as DomTransitionPath, E as EntityDefinition, R as RenderOptions, S as StateDefinition, g as StateMachineDefinition, T as TraitSnapshotGetter, h as TransitionDefinition, V as VisualizerConfig, i as bindCanvasCapture, j as bindEventBus, k as bindLastDrawables, l as bindTraitStateGetter, m as clearVerification, n as cn, o as extractOutputsFromTransitions, p as extractStateMachine, q as formatGuard, r as getAllChecks, s as getBridgeHealth, t as getEffectSummary, u as getSnapshot, v as getSummary, w as getTraitSnapshots, x as getTransitions, y as getTransitionsForTrait, z as parseContentSegments, A as parseMarkdownWithCodeBlocks, B as recordServerResponse, F as recordTransition, G as registerCheck, H as registerTraitSnapshot, I as renderStateMachineToDomData, J as renderStateMachineToSvg, K as subscribeToVerification, L as updateAssetStatus, M as updateBridgeHealth, N as updateCheck, O as waitForTransition } from '../cn-D3H9UzCW.cjs';
2
2
  import { FieldValue, EntityRow, EventPayload } from '@almadar/core';
3
3
  export { AssetLoadStatus, BridgeHealth, CheckStatus, EffectTrace, EventLogEntry, OrbitalVerificationAPI, ServerResponseTrace, TraitStateSnapshot, TransitionTrace, VerificationCheck, VerificationSnapshot, VerificationSummary } from '@almadar/core';
4
+ import '../paintDispatch-BXJgISot.cjs';
4
5
  import 'clsx';
5
6
 
6
7
  /**
@@ -1,6 +1,7 @@
1
- export { C as ContentSegment, D as DEFAULT_CONFIG, a as DomEntityBox, b as DomLayoutData, c as DomOutputsBox, d as DomStateNode, e as DomTransitionLabel, f as DomTransitionPath, E as EntityDefinition, R as RenderOptions, S as StateDefinition, g as StateMachineDefinition, T as TraitSnapshotGetter, h as TransitionDefinition, V as VisualizerConfig, i as bindCanvasCapture, j as bindEventBus, k as bindLastDrawables, l as bindTraitStateGetter, m as clearVerification, n as cn, o as extractOutputsFromTransitions, p as extractStateMachine, q as formatGuard, r as getAllChecks, s as getBridgeHealth, t as getEffectSummary, u as getSnapshot, v as getSummary, w as getTraitSnapshots, x as getTransitions, y as getTransitionsForTrait, z as parseContentSegments, A as parseMarkdownWithCodeBlocks, B as recordServerResponse, F as recordTransition, G as registerCheck, H as registerTraitSnapshot, I as renderStateMachineToDomData, J as renderStateMachineToSvg, K as subscribeToVerification, L as updateAssetStatus, M as updateBridgeHealth, N as updateCheck, O as waitForTransition } from '../cn-BbPvQLHt.js';
1
+ export { C as ContentSegment, D as DEFAULT_CONFIG, a as DomEntityBox, b as DomLayoutData, c as DomOutputsBox, d as DomStateNode, e as DomTransitionLabel, f as DomTransitionPath, E as EntityDefinition, R as RenderOptions, S as StateDefinition, g as StateMachineDefinition, T as TraitSnapshotGetter, h as TransitionDefinition, V as VisualizerConfig, i as bindCanvasCapture, j as bindEventBus, k as bindLastDrawables, l as bindTraitStateGetter, m as clearVerification, n as cn, o as extractOutputsFromTransitions, p as extractStateMachine, q as formatGuard, r as getAllChecks, s as getBridgeHealth, t as getEffectSummary, u as getSnapshot, v as getSummary, w as getTraitSnapshots, x as getTransitions, y as getTransitionsForTrait, z as parseContentSegments, A as parseMarkdownWithCodeBlocks, B as recordServerResponse, F as recordTransition, G as registerCheck, H as registerTraitSnapshot, I as renderStateMachineToDomData, J as renderStateMachineToSvg, K as subscribeToVerification, L as updateAssetStatus, M as updateBridgeHealth, N as updateCheck, O as waitForTransition } from '../cn-Dm0VrLRG.js';
2
2
  import { FieldValue, EntityRow, EventPayload } from '@almadar/core';
3
3
  export { AssetLoadStatus, BridgeHealth, CheckStatus, EffectTrace, EventLogEntry, OrbitalVerificationAPI, ServerResponseTrace, TraitStateSnapshot, TransitionTrace, VerificationCheck, VerificationSnapshot, VerificationSummary } from '@almadar/core';
4
+ import '../paintDispatch-BXJgISot.js';
4
5
  import 'clsx';
5
6
 
6
7
  /**