@drawcall/charta 0.1.18 → 0.1.19

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.
@@ -1,13 +1,20 @@
1
- import { LoadingManager, Loader } from 'three';
1
+ import { LoadingManager, Loader, Vector3 } from 'three';
2
2
  import { Interpreter } from '../interpreter.js';
3
+ /**
4
+ * Function type for measuring a GLB model's bounding box size.
5
+ * Takes a URL and returns a Vector3 with the model's dimensions (x, y, z).
6
+ */
7
+ export type MeasureGlbFunction = (url: string) => Promise<Vector3>;
3
8
  export type AssetLoaderOptions = {
4
9
  /**
5
- * Mock mode: skips actual asset loading and creates placeholder objects.
6
- * Useful for server-side validation. Default: false.
10
+ * Measure-only mode: uses the provided function to measure model sizes,
11
+ * then creates lightweight PrefabBatchBuilders with correct sizes.
12
+ * The models themselves are not kept in memory.
13
+ * Useful for server-side validation where model sizes are needed.
7
14
  */
8
- mock?: boolean;
15
+ measureModelsOnlyWith?: MeasureGlbFunction;
9
16
  /**
10
- * Skip texture loading entirely. When true, loadTexture calls are ignored.
17
+ * Skip texture loading entirely. When true, loadTexture calls create placeholder textures.
11
18
  * Default: false.
12
19
  */
13
20
  skipTextureLoading?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/assets/loader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiC,cAAc,EAAqB,MAAM,EAAW,MAAM,OAAO,CAAA;AAEzG,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAM/C,MAAM,MAAM,kBAAkB,GAAG;IAC/B;;;OAGG;IACH,IAAI,CAAC,EAAE,OAAO,CAAA;IACd;;;OAGG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAA;CAC7B,CAAA;AAED,qBAAa,WAAY,SAAQ,MAAM,CAAC,IAAI,EAAE,WAAW,CAAC;IACxD,OAAO,CAAC,aAAa,CAAe;IACpC,OAAO,CAAC,UAAU,CAAY;IAC9B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoB;gBAEhC,OAAO,CAAC,EAAE,cAAc,EAAE,OAAO,GAAE,kBAAuB;IAOtE,IAAI,CACF,WAAW,EAAE,WAAW,EACxB,MAAM,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,EAC5B,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,EAC3C,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,GAC/B,IAAI;YAyBO,iBAAiB;YA8BjB,eAAe;CAuC9B"}
1
+ {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/assets/loader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiC,cAAc,EAAqB,MAAM,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AAEzG,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAM/C;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;AAElE,MAAM,MAAM,kBAAkB,GAAG;IAC/B;;;;;OAKG;IACH,qBAAqB,CAAC,EAAE,kBAAkB,CAAA;IAC1C;;;OAGG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAA;CAC7B,CAAA;AAED,qBAAa,WAAY,SAAQ,MAAM,CAAC,IAAI,EAAE,WAAW,CAAC;IACxD,OAAO,CAAC,aAAa,CAAe;IACpC,OAAO,CAAC,UAAU,CAAY;IAC9B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoB;gBAEhC,OAAO,CAAC,EAAE,cAAc,EAAE,OAAO,GAAE,kBAAuB;IAOtE,IAAI,CACF,WAAW,EAAE,WAAW,EACxB,MAAM,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,EAC5B,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,EAC3C,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,GAC/B,IAAI;YAuBO,iBAAiB;YA8BjB,eAAe;CAqD9B"}
@@ -1,4 +1,4 @@
1
- import { Texture, TextureLoader, Object3D, Loader, Vector3 } from 'three';
1
+ import { Texture, TextureLoader, Object3D, Loader } from 'three';
2
2
  import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
3
3
  import { ChartaError } from '../errors.js';
4
4
  import { InstancedMeshGroup } from '../utils/instanced-mesh-group.js';
@@ -27,9 +27,7 @@ export class AssetLoader extends Loader {
27
27
  basePath = args.path.length === 0 ? undefined : args.path;
28
28
  }
29
29
  else if (key === 'loadTexture') {
30
- if (!this.options.skipTextureLoading) {
31
- promises.push(this.handleLoadTexture(interpreter, args.name, basePath, args.path, location));
32
- }
30
+ promises.push(this.handleLoadTexture(interpreter, args.name, basePath, args.path, location));
33
31
  }
34
32
  else if (key === 'loadModel') {
35
33
  promises.push(this.handleLoadModel(interpreter, args.name, basePath, args.path, location));
@@ -40,7 +38,7 @@ export class AssetLoader extends Loader {
40
38
  .catch((reason) => onError?.(reason));
41
39
  }
42
40
  async handleLoadTexture(interpreter, name, basePath, path, location) {
43
- if (this.options.mock) {
41
+ if (this.options.skipTextureLoading) {
44
42
  const texture = new Texture();
45
43
  texture.name = name;
46
44
  interpreter.setAsset(`${name}BaseColorTexture`, texture);
@@ -57,12 +55,20 @@ export class AssetLoader extends Loader {
57
55
  }
58
56
  }
59
57
  async handleLoadModel(interpreter, name, basePath, path, location) {
60
- if (this.options.mock) {
61
- const batchBuilder = new PrefabBatchBuilder(new Vector3(), () => new Object3D());
62
- interpreter.setAsset(`${name}Prefab`, batchBuilder);
58
+ const fullPath = new URL(path, basePath).href;
59
+ // Measure-only mode: use custom function to get size, don't load full model
60
+ if (this.options.measureModelsOnlyWith) {
61
+ try {
62
+ const size = await this.options.measureModelsOnlyWith(fullPath);
63
+ const batchBuilder = new PrefabBatchBuilder(size, () => new Object3D());
64
+ interpreter.setAsset(`${name}Prefab`, batchBuilder);
65
+ }
66
+ catch (err) {
67
+ interpreter.reportError(new ChartaError(`Failed to measure model ${name} at ${fullPath}: ${err instanceof Error ? err.message : String(err)}`, interpreter.getSource(), location));
68
+ }
63
69
  return;
64
70
  }
65
- const fullPath = new URL(path, basePath).href;
71
+ // Full mode: load model with Three.js (browser only)
66
72
  try {
67
73
  const gltf = await this.gltfLoader.loadAsync(fullPath);
68
74
  const scene = gltf.scene;
@@ -19,10 +19,10 @@ export type GrassOptions = GrassMaterialOptions & {
19
19
  */
20
20
  variationStrength?: number;
21
21
  /**
22
- * Mock mode: validates grass() calls exist but skips expensive blade generation.
22
+ * Validate-only mode: validates grass() calls exist but skips expensive blade generation.
23
23
  * Useful for server-side validation. Default: false.
24
24
  */
25
- mock?: boolean;
25
+ validateOnly?: boolean;
26
26
  };
27
27
  export declare class GrassMesh extends InstancedMesh {
28
28
  constructor(interpreter: Interpreter, material?: Material, opts?: GrassOptions);
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/grass/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,aAAa,EACb,QAAQ,EAMT,MAAM,OAAO,CAAC;AACf,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAsB,KAAK,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAO9E,MAAM,MAAM,YAAY,GAAG,oBAAoB,GAAG;IAChD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;OAGG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,qBAAa,SAAU,SAAQ,aAAa;gBAExC,WAAW,EAAE,WAAW,EACxB,QAAQ,GAAE,QAAkC,EAC5C,IAAI,GAAE,YAAiB;CAoP1B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/grass/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,aAAa,EACb,QAAQ,EAMT,MAAM,OAAO,CAAC;AACf,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAsB,KAAK,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAO9E,MAAM,MAAM,YAAY,GAAG,oBAAoB,GAAG;IAChD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,CAAC;AAEF,qBAAa,SAAU,SAAQ,aAAa;gBAExC,WAAW,EAAE,WAAW,EACxB,QAAQ,GAAE,QAAkC,EAC5C,IAAI,GAAE,YAAiB;CAoP1B"}
@@ -6,8 +6,8 @@ const UpVector = new Vector3(0, 1, 0);
6
6
  const colorHelper = new Color();
7
7
  export class GrassMesh extends InstancedMesh {
8
8
  constructor(interpreter, material = new MeshPhongMaterial(), opts = {}) {
9
- // Mock mode: validate grass calls exist, skip geometry allocation entirely
10
- if (opts.mock) {
9
+ // Validate-only mode: validate grass calls exist, skip geometry allocation entirely
10
+ if (opts.validateOnly) {
11
11
  super(undefined, material, 0);
12
12
  // Still validate all grass() calls to catch errors
13
13
  const rows = interpreter.getRows();
package/dist/parser.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { ChartaError } from "./errors.js";
1
2
  export type KeyParam = [key: string, value: string];
2
3
  export type Call = {
3
4
  name: string;
@@ -5,5 +6,7 @@ export type Call = {
5
6
  keyParams: KeyParam[];
6
7
  offset: number;
7
8
  };
9
+ export type ParseResult = [result: Call[][][], error: null] | [result: null, error: ChartaError];
10
+ export declare function safeParse(input: string): ParseResult;
8
11
  export declare function parse(input: string): Call[][][];
9
12
  //# sourceMappingURL=parser.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,QAAQ,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;AACpD,MAAM,MAAM,IAAI,GAAG;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAKF,wBAAgB,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,EAAE,EAAE,CA0C/C"}
1
+ {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAiB,MAAM,aAAa,CAAC;AAEzD,MAAM,MAAM,QAAQ,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;AACpD,MAAM,MAAM,IAAI,GAAG;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAIF,MAAM,MAAM,WAAW,GACnB,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,GACjC,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,WAAW,CAAC,CAAC;AAYvC,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW,CAqDpD;AAED,wBAAgB,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,EAAE,EAAE,CAI/C"}
package/dist/parser.js CHANGED
@@ -2,7 +2,16 @@ import nearley from "nearley";
2
2
  const { Parser, Grammar } = nearley;
3
3
  import grammar from "./grammar.js";
4
4
  import { ChartaError } from "./errors.js";
5
- export function parse(input) {
5
+ function getLocation(input, offset) {
6
+ const clampedOffset = Math.max(0, Math.min(offset, input.length));
7
+ const prefix = input.slice(0, clampedOffset);
8
+ const lines = prefix.split("\n");
9
+ return {
10
+ line: lines.length,
11
+ column: lines[lines.length - 1].length + 1,
12
+ };
13
+ }
14
+ export function safeParse(input) {
6
15
  const normalized = input.replace(/\r\n?/g, "\n");
7
16
  const parser = new Parser(Grammar.fromCompiled(grammar));
8
17
  let rows;
@@ -10,38 +19,39 @@ export function parse(input) {
10
19
  rows = parser.feed(normalized).finish()[0];
11
20
  }
12
21
  catch (error) {
13
- const loc = typeof error.offset === 'number'
14
- ? getLoc(normalized, error.offset)
15
- : undefined;
16
- throw new ChartaError(error.message || "Parse error", input, loc);
22
+ const offset = typeof error.offset === "number" ? error.offset : 0;
23
+ return [
24
+ null,
25
+ new ChartaError(error.message || "Parse error", normalized, getLocation(normalized, offset)),
26
+ ];
17
27
  }
18
28
  if (!rows || rows.length === 0)
19
- return [];
20
- // Validate Meta Row (first row)
29
+ return [[], null];
30
+ // Validate Meta Row (first row must contain exactly one cell)
21
31
  if (rows[0].length !== 1) {
22
- throwError(input, rows[0].offset, `First row must contain exactly one cell (meta row), found ${rows[0].length}`);
32
+ return [
33
+ null,
34
+ new ChartaError(`First row must contain exactly one cell (meta row), found ${rows[0].length}`, normalized, getLocation(normalized, rows[0].offset)),
35
+ ];
23
36
  }
24
- // Validate Grid Rows (subsequent rows)
25
- // All grid rows must have the same number of cells as the first grid row
37
+ // Validate Grid Rows (all must have same width as first grid row)
26
38
  if (rows.length > 1) {
27
39
  const gridRows = rows.slice(1);
28
40
  const expectedWidth = gridRows[0].length;
29
- const badRowIdx = gridRows.findIndex(r => r.length !== expectedWidth);
41
+ const badRowIdx = gridRows.findIndex((r) => r.length !== expectedWidth);
30
42
  if (badRowIdx !== -1) {
31
- throwError(input, gridRows[badRowIdx].offset, `Row ${badRowIdx + 2} has ${gridRows[badRowIdx].length} cells; expected ${expectedWidth}`);
43
+ const badRow = gridRows[badRowIdx];
44
+ return [
45
+ null,
46
+ new ChartaError(`Row ${badRowIdx + 2} has ${badRow.length} cells; expected ${expectedWidth}`, normalized, getLocation(normalized, badRow.offset)),
47
+ ];
32
48
  }
33
49
  }
34
- return rows;
50
+ return [rows, null];
35
51
  }
36
- function throwError(input, offset, message) {
37
- throw new ChartaError(message, input, getLoc(input, offset));
38
- }
39
- function getLoc(input, offset) {
40
- const clampedOffset = Math.max(0, Math.min(offset, input.length));
41
- const prefix = input.slice(0, clampedOffset);
42
- const lines = prefix.split('\n');
43
- return {
44
- line: lines.length,
45
- column: lines[lines.length - 1].length + 1
46
- };
52
+ export function parse(input) {
53
+ const [result, error] = safeParse(input);
54
+ if (error)
55
+ throw error;
56
+ return result;
47
57
  }
@@ -3,10 +3,10 @@ import { Interpreter } from '../interpreter.js';
3
3
  export type PillarCorner = 'topleft' | 'topright' | 'bottomright' | 'bottomleft';
4
4
  export type PillarMeshOptions = {
5
5
  /**
6
- * Mock mode: validates pillar calls but skips geometry creation.
6
+ * Validate-only mode: validates pillar calls but skips geometry creation.
7
7
  * Useful for server-side validation. Default: false.
8
8
  */
9
- mock?: boolean;
9
+ validateOnly?: boolean;
10
10
  };
11
11
  export declare class PillarMesh extends InstancedMesh {
12
12
  constructor(interpreter: Interpreter, material?: Material, options?: PillarMeshOptions);
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/pillars/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,aAAa,EAEb,QAAQ,EAMT,MAAM,OAAO,CAAA;AACd,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAO/C,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,UAAU,GAAG,aAAa,GAAG,YAAY,CAAA;AAYhF,MAAM,MAAM,iBAAiB,GAAG;IAC9B;;;OAGG;IACH,IAAI,CAAC,EAAE,OAAO,CAAA;CACf,CAAA;AAED,qBAAa,UAAW,SAAQ,aAAa;gBAC/B,WAAW,EAAE,WAAW,EAAE,QAAQ,GAAE,QAAkC,EAAE,OAAO,GAAE,iBAAsB;CAoLpH"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/pillars/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,aAAa,EAEb,QAAQ,EAMT,MAAM,OAAO,CAAA;AACd,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAO/C,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,UAAU,GAAG,aAAa,GAAG,YAAY,CAAA;AAYhF,MAAM,MAAM,iBAAiB,GAAG;IAC9B;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAA;CACvB,CAAA;AAED,qBAAa,UAAW,SAAQ,aAAa;gBAC/B,WAAW,EAAE,WAAW,EAAE,QAAQ,GAAE,QAAkC,EAAE,OAAO,GAAE,iBAAsB;CAwLpH"}
@@ -9,29 +9,16 @@ export class PillarMesh extends InstancedMesh {
9
9
  const rows = interpreter.getRows();
10
10
  const cols = interpreter.getCols();
11
11
  const cellSize = interpreter.getCellSize();
12
- // Mock mode: validate calls exist but skip geometry creation
13
- if (options.mock) {
14
- // Use undefined geometry with 0 instances - no memory allocation
15
- super(undefined, material, 0);
16
- for (let row = 0; row < rows; row++) {
17
- for (let col = 0; col < cols; col++) {
18
- const entries = interpreter.getCalls([row, col], { pillar: pillarSchema });
19
- for (const [, parsed, , loc] of entries) {
20
- // Validate texture reference
21
- interpreter.getAsset(Texture, `${parsed.texture}BaseColorTexture`, loc);
22
- }
23
- }
24
- }
25
- return;
26
- }
27
- // Collect pillar instances with resolved heights
12
+ // Collect pillar instances only in non-validateOnly mode
28
13
  const pillars = [];
29
- // tilesGeometry is only required for height sampling; fetch lazily where needed
30
14
  const usedTextures = [];
31
- const getTextureId = (name) => {
32
- const texture = interpreter.getAsset(Texture, `${name}BaseColorTexture`);
15
+ const getTextureId = (name, loc) => {
16
+ const texture = interpreter.getAsset(Texture, `${name}BaseColorTexture`, loc);
33
17
  if (!texture)
34
18
  return 0;
19
+ // Only track textures in non-validateOnly mode
20
+ if (options.validateOnly)
21
+ return 0;
35
22
  let idx = usedTextures.indexOf(texture);
36
23
  if (idx === -1) {
37
24
  idx = usedTextures.length;
@@ -61,6 +48,7 @@ export class PillarMesh extends InstancedMesh {
61
48
  }
62
49
  }
63
50
  }
51
+ // Validation loop runs in both modes
64
52
  for (let row = 0; row < rows; row++) {
65
53
  for (let col = 0; col < cols; col++) {
66
54
  const entries = interpreter.getCalls([row, col], {
@@ -76,11 +64,13 @@ export class PillarMesh extends InstancedMesh {
76
64
  const [cx, cz] = interpreter.getWorldCellCenter(row, col);
77
65
  const offsetX = px - cx;
78
66
  const offsetZ = pz - cz;
79
- const textureId = getTextureId(parsed.texture);
67
+ // Validate texture reference (runs in both modes)
68
+ const textureId = getTextureId(parsed.texture, loc);
80
69
  // Derive default layers relative to call position (like walls)
81
70
  const isLayer = (c) => c.name === 'ground' || c.name === 'ceiling';
82
71
  let bottomY = parsed.bottomY;
83
72
  let topY = parsed.topY;
73
+ // Validate layer existence (runs in both modes)
84
74
  let bottomStackIndex;
85
75
  if (bottomY == null) {
86
76
  const layersBefore = interpreter.countCalls([row, col], isLayer, 0, idx);
@@ -89,10 +79,13 @@ export class PillarMesh extends InstancedMesh {
89
79
  interpreter.reportError(new ChartaError(`pillar at ${row}/${col}: missing bottomY and no preceding layer`, interpreter.getSource(), loc));
90
80
  continue;
91
81
  }
92
- const tilesGeometry = interpreter.getAsset(TilesGeometry, 'tilesGeometry', loc);
93
- if (!tilesGeometry)
94
- continue;
95
- bottomY = tilesGeometry.getHeight(row, col, bottomStackIndex, offsetX, offsetZ);
82
+ // Height sampling only in non-validateOnly mode
83
+ if (!options.validateOnly) {
84
+ const tilesGeometry = interpreter.getAsset(TilesGeometry, 'tilesGeometry', loc);
85
+ if (!tilesGeometry)
86
+ continue;
87
+ bottomY = tilesGeometry.getHeight(row, col, bottomStackIndex, offsetX, offsetZ);
88
+ }
96
89
  }
97
90
  let topStackIndex;
98
91
  if (topY == null) {
@@ -101,31 +94,42 @@ export class PillarMesh extends InstancedMesh {
101
94
  interpreter.reportError(new ChartaError(`pillar at ${row}/${col}: missing topY and no subsequent layer`, interpreter.getSource(), loc));
102
95
  continue;
103
96
  }
104
- const layersBefore = interpreter.countCalls([row, col], isLayer, 0, idx);
105
- topStackIndex = layersBefore;
106
- const tilesGeometry = interpreter.getAsset(TilesGeometry, 'tilesGeometry', loc);
107
- if (!tilesGeometry)
108
- continue;
109
- topY = tilesGeometry.getHeight(row, col, topStackIndex, offsetX, offsetZ);
97
+ // Height sampling only in non-validateOnly mode
98
+ if (!options.validateOnly) {
99
+ const layersBefore = interpreter.countCalls([row, col], isLayer, 0, idx);
100
+ topStackIndex = layersBefore;
101
+ const tilesGeometry = interpreter.getAsset(TilesGeometry, 'tilesGeometry', loc);
102
+ if (!tilesGeometry)
103
+ continue;
104
+ topY = tilesGeometry.getHeight(row, col, topStackIndex, offsetX, offsetZ);
105
+ }
110
106
  }
111
- // Support "negative pillars" by swapping
112
- if (topY < bottomY) {
113
- const tmp = bottomY;
114
- bottomY = topY;
115
- topY = tmp;
107
+ // Only collect pillar data in non-validateOnly mode
108
+ if (!options.validateOnly) {
109
+ // Support "negative pillars" by swapping
110
+ if (topY < bottomY) {
111
+ const tmp = bottomY;
112
+ bottomY = topY;
113
+ topY = tmp;
114
+ }
115
+ pillars.push({
116
+ x: px,
117
+ z: pz,
118
+ sizeX,
119
+ sizeZ,
120
+ bottomY: bottomY,
121
+ topY: topY,
122
+ textureId,
123
+ });
116
124
  }
117
- pillars.push({
118
- x: px,
119
- z: pz,
120
- sizeX,
121
- sizeZ,
122
- bottomY: bottomY,
123
- topY: topY,
124
- textureId,
125
- });
126
125
  }
127
126
  }
128
127
  }
128
+ // ValidateOnly mode: skip geometry creation
129
+ if (options.validateOnly) {
130
+ super(undefined, material, 0);
131
+ return;
132
+ }
129
133
  // Base cube with base at y=0 for easy scaling
130
134
  const geometry = new BoxGeometry(1, 1, 1);
131
135
  geometry.translate(0, 0.5, 0);
@@ -24,10 +24,10 @@ export declare class PrefabBatchBuilder {
24
24
  }
25
25
  export type PlaceGroupOptions = {
26
26
  /**
27
- * Mock mode: validates scatter/place calls and assets but skips expensive transform
27
+ * Validate-only mode: validates scatter/place calls and assets but skips expensive transform
28
28
  * computation and instance generation. Useful for server-side validation. Default: false.
29
29
  */
30
- mock?: boolean;
30
+ validateOnly?: boolean;
31
31
  skipModelInstantiation?: boolean;
32
32
  };
33
33
  export declare class PlaceGroup extends Group {
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/place/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,OAAO,EAAQ,MAAM,OAAO,CAAA;AACrF,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAO/C,MAAM,MAAM,eAAe,GAAG,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAA;AAEvD,MAAM,MAAM,mBAAmB,GAAG,CAAC,IAAI,EAAE;IACvC,QAAQ,EAAE,OAAO,CAAA;IACjB,QAAQ,EAAE,UAAU,CAAA;IACpB,KAAK,EAAE,OAAO,CAAA;IACd,WAAW,EAAE,WAAW,CAAA;IACxB,IAAI,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,CAAA;IAC5D,IAAI,EAAE,MAAM,CAAA;CACb,KAAK,IAAI,CAAA;AAKV,wBAAgB,aAAa,CAAC,MAAM,EAAE,QAAQ,GAAG,OAAO,CAOvD;AAED,qBAAa,kBAAkB;aAIX,UAAU,EAAE,OAAO;IACnC,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IAJpC,SAAgB,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,CAAK;gBAG3B,UAAU,EAAE,OAAO,EAClB,iBAAiB,EAAE,CAAC,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,QAAQ;IAG5E,GAAG,CAAC,MAAM,EAAE,OAAO;IAKZ,KAAK,IAAI,QAAQ;CAGzB;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B;;;OAGG;IACH,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,sBAAsB,CAAC,EAAE,OAAO,CAAA;CACjC,CAAA;AAED,qBAAa,UAAW,SAAQ,KAAK;IACnC,SAAgB,aAAa,kCAAwC;gBAEzD,WAAW,EAAE,WAAW,EAAE,iBAAiB,CAAC,EAAE,QAAQ,EAAE,IAAI,GAAE,iBAAsB;IAuPhG,OAAO;CAOR"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/place/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,OAAO,EAAQ,MAAM,OAAO,CAAA;AACrF,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAO/C,MAAM,MAAM,eAAe,GAAG,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAA;AAEvD,MAAM,MAAM,mBAAmB,GAAG,CAAC,IAAI,EAAE;IACvC,QAAQ,EAAE,OAAO,CAAA;IACjB,QAAQ,EAAE,UAAU,CAAA;IACpB,KAAK,EAAE,OAAO,CAAA;IACd,WAAW,EAAE,WAAW,CAAA;IACxB,IAAI,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,CAAA;IAC5D,IAAI,EAAE,MAAM,CAAA;CACb,KAAK,IAAI,CAAA;AAKV,wBAAgB,aAAa,CAAC,MAAM,EAAE,QAAQ,GAAG,OAAO,CAOvD;AAED,qBAAa,kBAAkB;aAIX,UAAU,EAAE,OAAO;IACnC,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IAJpC,SAAgB,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,CAAK;gBAG3B,UAAU,EAAE,OAAO,EAClB,iBAAiB,EAAE,CAAC,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,QAAQ;IAG5E,GAAG,CAAC,MAAM,EAAE,OAAO;IAKZ,KAAK,IAAI,QAAQ;CAGzB;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,sBAAsB,CAAC,EAAE,OAAO,CAAA;CACjC,CAAA;AAED,qBAAa,UAAW,SAAQ,KAAK;IACnC,SAAgB,aAAa,kCAAwC;gBAEzD,WAAW,EAAE,WAAW,EAAE,iBAAiB,CAAC,EAAE,QAAQ,EAAE,IAAI,GAAE,iBAAsB;IAuPhG,OAAO;CAOR"}
@@ -34,14 +34,14 @@ export class PlaceGroup extends Group {
34
34
  batchBuilders = new Map();
35
35
  constructor(interpreter, _materialFallback, opts = {}) {
36
36
  super();
37
- const { mock = false, skipModelInstantiation: skipBuild = false } = opts;
37
+ const { validateOnly = false, skipModelInstantiation = false } = opts;
38
38
  const rows = interpreter.getRows();
39
39
  const cols = interpreter.getCols();
40
40
  const cellSize = interpreter.getCellSize();
41
- // Only get tilesGeometry if not in mock mode (needed for height calculations)
42
- const tilesGeometry = mock ? null : interpreter.getAsset(TilesGeometry, 'tilesGeometry');
43
- // Return early if not mock and tilesGeometry is missing
44
- if (!mock && !tilesGeometry) {
41
+ // Only get tilesGeometry if not in validateOnly mode (needed for height calculations)
42
+ const tilesGeometry = validateOnly ? null : interpreter.getAsset(TilesGeometry, 'tilesGeometry');
43
+ // Return early if not validateOnly and tilesGeometry is missing
44
+ if (!validateOnly && !tilesGeometry) {
45
45
  return;
46
46
  }
47
47
  const computeScale = (usualSize, sizeX, sizeZ) => {
@@ -109,8 +109,8 @@ export class PlaceGroup extends Group {
109
109
  const batchBuilder = interpreter.getAsset(PrefabBatchBuilder, prefabName, loc);
110
110
  if (!batchBuilder)
111
111
  continue;
112
- // In mock mode, we only validate asset existence - skip expensive placement computation
113
- if (mock)
112
+ // In validateOnly mode, we only validate asset existence - skip expensive placement computation
113
+ if (validateOnly)
114
114
  continue;
115
115
  this.batchBuilders.set(parsed.model, batchBuilder);
116
116
  const usualSize = new Vector3();
@@ -176,7 +176,7 @@ export class PlaceGroup extends Group {
176
176
  const usualSize = batchBuilder.prefabSize;
177
177
  const px = cx + (parsed.offsetX ?? 0);
178
178
  const pz = cz + (parsed.offsetZ ?? 0);
179
- // Compute scale for footprint validation (needed for both mock and normal mode)
179
+ // Compute scale for footprint validation (needed for both validateOnly and normal mode)
180
180
  const scale = computeScale(usualSize, parsed.sizeX, parsed.sizeZ);
181
181
  const halfX = (usualSize.x * scale.x) / 2;
182
182
  const halfZ = (usualSize.z * scale.z) / 2;
@@ -186,8 +186,8 @@ export class PlaceGroup extends Group {
186
186
  interpreter.reportError(new ChartaError(`location is inside place(${parsed.model}) footprint`, interpreter.getSource(), loc));
187
187
  }
188
188
  }
189
- // In mock mode, skip expensive transform computation and placement
190
- if (mock)
189
+ // In validateOnly mode, skip expensive transform computation and placement
190
+ if (validateOnly)
191
191
  continue;
192
192
  this.batchBuilders.set(parsed.model, batchBuilder);
193
193
  const layerIndex = interpreter.countCalls([row, col], (c) => c.name === 'ground' || c.name === 'ceiling', 0, callIdx) - 1;
@@ -205,8 +205,8 @@ export class PlaceGroup extends Group {
205
205
  }
206
206
  }
207
207
  }
208
- // Build step: skip if mock or skipBuild is enabled
209
- if (!mock && !skipBuild) {
208
+ // Build step: skip if validateOnly or skipBuild is enabled
209
+ if (!validateOnly && !skipModelInstantiation) {
210
210
  for (const batchBuilder of this.batchBuilders.values()) {
211
211
  this.add(batchBuilder.build());
212
212
  }
@@ -5,10 +5,10 @@ import { groundSchema, ceilingSchema } from '../schemas.js';
5
5
  export { groundSchema, ceilingSchema };
6
6
  export type TilesMeshOptions = {
7
7
  /**
8
- * Mock mode: validates ground/ceiling/wall calls but skips geometry creation.
8
+ * Validate-only mode: validates ground/ceiling/wall calls but skips geometry creation.
9
9
  * Useful for server-side validation. Default: false.
10
10
  */
11
- mock?: boolean;
11
+ validateOnly?: boolean;
12
12
  skipTextureLoading?: boolean;
13
13
  };
14
14
  export declare class TilesMesh extends Mesh<TilesGeometry> {
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tiles/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,QAAQ,EACR,IAAI,EAOL,MAAM,OAAO,CAAA;AACd,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,OAAO,EAAE,aAAa,EAAuB,MAAM,eAAe,CAAA;AAIlE,OAAO,EAAE,YAAY,EAAE,aAAa,EAAc,MAAM,eAAe,CAAA;AAEvE,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,CAAA;AAEtC,MAAM,MAAM,gBAAgB,GAAG;IAC7B;;;OAGG;IACH,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,kBAAkB,CAAC,EAAE,OAAO,CAAA;CAC7B,CAAA;AAyCD,qBAAa,SAAU,SAAQ,IAAI,CAAC,aAAa,CAAC;gBACpC,WAAW,EAAE,WAAW,EAAE,QAAQ,GAAE,QAAkC,EAAE,OAAO,GAAE,gBAAqB;CAiInH"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tiles/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,QAAQ,EACR,IAAI,EAOL,MAAM,OAAO,CAAA;AACd,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,OAAO,EAAE,aAAa,EAAuB,MAAM,eAAe,CAAA;AAIlE,OAAO,EAAE,YAAY,EAAE,aAAa,EAAc,MAAM,eAAe,CAAA;AAEvE,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,CAAA;AAEtC,MAAM,MAAM,gBAAgB,GAAG;IAC7B;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,kBAAkB,CAAC,EAAE,OAAO,CAAA;CAC7B,CAAA;AAyCD,qBAAa,SAAU,SAAQ,IAAI,CAAC,aAAa,CAAC;gBACpC,WAAW,EAAE,WAAW,EAAE,QAAQ,GAAE,QAAkC,EAAE,OAAO,GAAE,gBAAqB;CAiInH"}
@@ -34,9 +34,9 @@ export class TilesMesh extends Mesh {
34
34
  constructor(interpreter, material = new MeshBasicMaterial(), options = {}) {
35
35
  const rows = interpreter.getRows();
36
36
  const cols = interpreter.getCols();
37
- const { mock = false, skipTextureLoading = false } = options;
38
- const createGeometry = !mock;
39
- const createMaterial = !mock && !skipTextureLoading;
37
+ const { validateOnly = false, skipTextureLoading = false } = options;
38
+ const createGeometry = !validateOnly;
39
+ const createMaterial = !validateOnly && !skipTextureLoading;
40
40
  // Texture index tracking (only used when creating materials)
41
41
  const textureIndex = new Map();
42
42
  const getTextureIndex = (name, loc) => {
@@ -111,7 +111,7 @@ export class TilesMesh extends Mesh {
111
111
  });
112
112
  }
113
113
  else if (!skipTextureLoading) {
114
- // Validate texture exists in mock mode (unless mockTexture is set)
114
+ // Validate texture exists in validateOnly mode (unless mockTexture is set)
115
115
  interpreter.getAsset(Texture, `${parsed.texture}BaseColorTexture`, loc);
116
116
  }
117
117
  }
@@ -47,7 +47,7 @@ export declare const WALL_CONFIG: {
47
47
  };
48
48
  };
49
49
  export declare const WALL_CONNECTION_TOLERANCE_RATIO: number;
50
- export declare function computeWallVerticalBounds(interpreter: Interpreter, tilesGeometry: TilesGeometry, row: number, col: number, wallIdx: number, parsed: {
50
+ export declare function computeWallVerticalBounds(interpreter: Interpreter, tilesGeometry: TilesGeometry | undefined, row: number, col: number, wallIdx: number, parsed: {
51
51
  bottomY?: number;
52
52
  topY?: number;
53
53
  dir: string;
@@ -59,10 +59,10 @@ export declare function computeWallVerticalBounds(interpreter: Interpreter, tile
59
59
  } | undefined;
60
60
  export type WallMeshOptions = {
61
61
  /**
62
- * Mock mode: validates wall/window/door calls but skips geometry creation.
62
+ * Validate-only mode: validates wall/window/door calls but skips geometry creation.
63
63
  * Useful for server-side validation. Default: false.
64
64
  */
65
- mock?: boolean;
65
+ validateOnly?: boolean;
66
66
  };
67
67
  export declare class WallMesh extends Mesh {
68
68
  constructor(interpreter: Interpreter, material?: Material, options?: WallMeshOptions);
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/walls/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,QAAQ,EAER,IAAI,EAGJ,YAAY,EAEZ,YAAY,EAOb,MAAM,OAAO,CAAA;AAEd,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAE/C,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAGpD,OAAO,EAAe,aAAa,EAAE,MAAM,cAAc,CAAA;AACzD,OAAO,EAGL,UAAU,EACV,YAAY,EACZ,UAAU,EACX,MAAM,eAAe,CAAA;AAEtB,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,UAAU,EAAE,CAAA;AAK/C,MAAM,MAAM,UAAU,GAAG;IACvB,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;AAED,MAAM,MAAM,QAAQ,GAAG;IACrB,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,MAAM,EAAE,YAAY,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,YAAY,CAAA;IACpB,IAAI,EAAE,YAAY,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,UAAU,EAAE,CAAA;CACtB,CAAA;AAED,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;CAed,CAAA;AAEV,eAAO,MAAM,+BAA+B,QAAQ,CAAA;AAEpD,wBAAgB,yBAAyB,CACvC,WAAW,EAAE,WAAW,EACxB,aAAa,EAAE,aAAa,EAC5B,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,MAAM,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,EACxD,MAAM,EAAE,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,OAAO,WAAW,CAAC,EACtD,GAAG,EAAE,aAAa;;;;;cAqInB;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B;;;OAGG;IACH,IAAI,CAAC,EAAE,OAAO,CAAA;CACf,CAAA;AAED,qBAAa,QAAS,SAAQ,IAAI;gBACpB,WAAW,EAAE,WAAW,EAAE,QAAQ,GAAE,QAAkC,EAAE,OAAO,GAAE,eAAoB;CA6SlH;AAED,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,WAGrG"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/walls/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,QAAQ,EAER,IAAI,EAGJ,YAAY,EAEZ,YAAY,EAOb,MAAM,OAAO,CAAA;AAEd,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAE/C,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAGpD,OAAO,EAAe,aAAa,EAAE,MAAM,cAAc,CAAA;AACzD,OAAO,EAGL,UAAU,EACV,YAAY,EACZ,UAAU,EACX,MAAM,eAAe,CAAA;AAEtB,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,UAAU,EAAE,CAAA;AAK/C,MAAM,MAAM,UAAU,GAAG;IACvB,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;AAED,MAAM,MAAM,QAAQ,GAAG;IACrB,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,MAAM,EAAE,YAAY,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,YAAY,CAAA;IACpB,IAAI,EAAE,YAAY,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,UAAU,EAAE,CAAA;CACtB,CAAA;AAED,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;CAed,CAAA;AAEV,eAAO,MAAM,+BAA+B,QAAQ,CAAA;AAEpD,wBAAgB,yBAAyB,CACvC,WAAW,EAAE,WAAW,EACxB,aAAa,EAAE,aAAa,GAAG,SAAS,EACxC,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,MAAM,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,EACxD,MAAM,EAAE,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,OAAO,WAAW,CAAC,EACtD,GAAG,EAAE,aAAa;YAsFO,YAAY;UACd,YAAY;;;cAwDpC;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAA;CACvB,CAAA;AAED,qBAAa,QAAS,SAAQ,IAAI;gBACpB,WAAW,EAAE,WAAW,EAAE,QAAQ,GAAE,QAAkC,EAAE,OAAO,GAAE,eAAoB;CA8SlH;AAED,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,WAGrG"}
@@ -92,6 +92,15 @@ export function computeWallVerticalBounds(interpreter, tilesGeometry, row, col,
92
92
  return undefined;
93
93
  if (topLayer === undefined && parsed.topY === undefined)
94
94
  return undefined;
95
+ // In validateOnly mode (no tilesGeometry), skip height sampling - validation is complete
96
+ if (!tilesGeometry) {
97
+ return {
98
+ yStart: [0, 0, 0],
99
+ yEnd: [0, 0, 0],
100
+ worldWallX,
101
+ worldWallZ,
102
+ };
103
+ }
95
104
  const sampleOffsets = [-cellSize / 2, 0, cellSize / 2];
96
105
  const yStart = [0, 0, 0];
97
106
  const yEnd = [0, 0, 0];
@@ -145,40 +154,15 @@ export class WallMesh extends Mesh {
145
154
  const rows = interpreter.getRows();
146
155
  const cols = interpreter.getCols();
147
156
  const cellSize = interpreter.getCellSize();
148
- // Mock mode: validate calls exist but skip geometry creation
149
- if (options.mock) {
157
+ // Get tilesGeometry only in non-validateOnly mode (needed for height sampling)
158
+ const tilesGeometry = options.validateOnly ? undefined : interpreter.getAsset(TilesGeometry, 'tilesGeometry');
159
+ // In non-validateOnly mode, if no tilesGeometry exists, early return
160
+ if (!options.validateOnly && !tilesGeometry) {
150
161
  super();
151
- for (let row = 0; row < rows; row++) {
152
- for (let col = 0; col < cols; col++) {
153
- // Validate all wall/window/door calls
154
- const entries = interpreter.getCalls([row, col], {
155
- wall: wallSchema,
156
- window: windowSchema,
157
- door: doorSchema,
158
- });
159
- // Validate texture references and wall ordering
160
- let hasWall = false;
161
- for (const [name, parsed, , loc] of entries) {
162
- if (name === 'wall') {
163
- hasWall = true;
164
- interpreter.getAsset(Texture, `${parsed.texture}BaseColorTexture`, loc);
165
- }
166
- else if (name === 'window' || name === 'door') {
167
- if (!hasWall) {
168
- interpreter.reportError(new ChartaError(`${name} without preceding wall`, interpreter.getSource(), loc));
169
- }
170
- }
171
- }
172
- }
173
- }
174
162
  return;
175
163
  }
176
- const tilesGeometry = interpreter.getAsset(TilesGeometry, 'tilesGeometry');
177
- if (!tilesGeometry) {
178
- super();
179
- return;
180
- }
181
- const wallData = [];
164
+ // Allocate data structures only in non-validateOnly mode
165
+ const wallData = options.validateOnly ? [] : [];
182
166
  const usedTextures = [];
183
167
  const getWallTextureId = (texture) => {
184
168
  let idx = usedTextures.indexOf(texture);
@@ -188,6 +172,7 @@ export class WallMesh extends Mesh {
188
172
  }
189
173
  return idx;
190
174
  };
175
+ // Validation loop runs in both modes
191
176
  for (let row = 0; row < rows; row++) {
192
177
  for (let col = 0; col < cols; col++) {
193
178
  const entries = interpreter.getCalls([row, col], {
@@ -196,8 +181,17 @@ export class WallMesh extends Mesh {
196
181
  door: doorSchema,
197
182
  });
198
183
  let currentWall;
184
+ // Track wall validity separately for window/door validation in validateOnly mode
185
+ let hasValidWall = false;
199
186
  for (const [name, winOrDoor, wallIdx, loc] of entries) {
200
187
  if (name === 'window' || name === 'door') {
188
+ // In validateOnly mode, we only validate wall ordering (not geometry bounds)
189
+ if (options.validateOnly) {
190
+ if (!hasValidWall) {
191
+ interpreter.reportError(new ChartaError(`${name} without preceding wall`, interpreter.getSource(), loc));
192
+ }
193
+ continue;
194
+ }
201
195
  if (!currentWall) {
202
196
  interpreter.reportError(new ChartaError(`${name} without preceding wall`, interpreter.getSource(), loc));
203
197
  continue;
@@ -260,31 +254,45 @@ export class WallMesh extends Mesh {
260
254
  }
261
255
  const wallParsed = winOrDoor; // Type assertion since it could be wall or window in loop
262
256
  const config = WALL_CONFIG[wallParsed.dir];
257
+ // computeWallVerticalBounds validates layer existence and reports errors
258
+ // In validateOnly mode (tilesGeometry=undefined), it skips height sampling but still validates
263
259
  const bounds = computeWallVerticalBounds(interpreter, tilesGeometry, row, col, wallIdx, wallParsed, config, loc);
264
260
  if (!bounds) {
265
261
  currentWall = undefined;
262
+ hasValidWall = false;
266
263
  continue;
267
264
  }
268
- const texture = interpreter.getAsset(Texture, `${wallParsed.texture}BaseColorTexture`);
265
+ // Validate texture reference (runs in both modes)
266
+ const texture = interpreter.getAsset(Texture, `${wallParsed.texture}BaseColorTexture`, loc);
269
267
  if (!texture) {
270
268
  currentWall = undefined;
269
+ hasValidWall = false;
271
270
  continue;
272
271
  }
273
- const newWall = {
274
- x: bounds.worldWallX,
275
- z: bounds.worldWallZ,
276
- rotationY: config.rotation,
277
- xzSize: [cellSize, 0.1], // Walls are always cellSize width, 0.1 depth
278
- yStart: bounds.yStart,
279
- yEnd: bounds.yEnd,
280
- textureId: getWallTextureId(texture),
281
- windows: [],
282
- };
283
- wallData.push(newWall);
284
- currentWall = newWall;
272
+ hasValidWall = true;
273
+ // Only build wall data in non-validateOnly mode
274
+ if (!options.validateOnly) {
275
+ const newWall = {
276
+ x: bounds.worldWallX,
277
+ z: bounds.worldWallZ,
278
+ rotationY: config.rotation,
279
+ xzSize: [cellSize, 0.1], // Walls are always cellSize width, 0.1 depth
280
+ yStart: bounds.yStart,
281
+ yEnd: bounds.yEnd,
282
+ textureId: getWallTextureId(texture),
283
+ windows: [],
284
+ };
285
+ wallData.push(newWall);
286
+ currentWall = newWall;
287
+ }
285
288
  }
286
289
  }
287
290
  }
291
+ // ValidateOnly mode: skip geometry creation
292
+ if (options.validateOnly) {
293
+ super();
294
+ return;
295
+ }
288
296
  // Build per-wall geometries (baked) and merge
289
297
  const bakedGeometries = [];
290
298
  const tmpMatrix = new Matrix4();
@@ -2,10 +2,10 @@ import { Mesh } from "three";
2
2
  import { Interpreter } from "../interpreter.js";
3
3
  export type WaterMeshOptions = {
4
4
  /**
5
- * Mock mode: validates water calls but skips geometry creation.
5
+ * Validate-only mode: validates water calls but skips geometry creation.
6
6
  * Useful for server-side validation. Default: false.
7
7
  */
8
- mock?: boolean;
8
+ validateOnly?: boolean;
9
9
  };
10
10
  export declare class WaterMesh extends Mesh {
11
11
  constructor(interpreter: Interpreter, options?: WaterMeshOptions);
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/water/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAY,MAAM,OAAO,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAIhD,MAAM,MAAM,gBAAgB,GAAG;IAC7B;;;OAGG;IACH,IAAI,CAAC,EAAE,OAAO,CAAA;CACf,CAAC;AAEF,qBAAa,SAAU,SAAQ,IAAI;gBACrB,WAAW,EAAE,WAAW,EAAE,OAAO,GAAE,gBAAqB;IAkDpE,OAAO,IAAI,IAAI;CAMhB;AAED,cAAc,cAAc,CAAA;AAC5B,cAAc,eAAe,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/water/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAY,MAAM,OAAO,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAIhD,MAAM,MAAM,gBAAgB,GAAG;IAC7B;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAA;CACvB,CAAC;AAEF,qBAAa,SAAU,SAAQ,IAAI;gBACrB,WAAW,EAAE,WAAW,EAAE,OAAO,GAAE,gBAAqB;IAoDpE,OAAO,IAAI,IAAI;CAMhB;AAED,cAAc,cAAc,CAAA;AAC5B,cAAc,eAAe,CAAA"}
@@ -5,37 +5,38 @@ export class WaterMesh extends Mesh {
5
5
  constructor(interpreter, options = {}) {
6
6
  const rows = interpreter.getRows();
7
7
  const cols = interpreter.getCols();
8
- // Mock mode: validate calls exist but skip geometry creation
9
- if (options.mock) {
10
- super();
11
- for (let row = 0; row < rows; row++) {
12
- for (let col = 0; col < cols; col++) {
13
- // Validate water calls
14
- interpreter.getCalls([row, col], { water: waterSchema });
15
- }
16
- }
17
- return;
8
+ // Build tiles only in non-validateOnly mode (expensive allocation)
9
+ let tiles;
10
+ if (!options.validateOnly) {
11
+ tiles = new Array(rows)
12
+ .fill(undefined)
13
+ .map(() => new Array(cols).fill(undefined).map(() => []));
18
14
  }
19
- // Build tiles[row][col] => Array<Tile>, containing only water tiles
20
- const tiles = new Array(rows)
21
- .fill(undefined)
22
- .map(() => new Array(cols).fill(undefined).map(() => []));
15
+ // Validation loop runs in both modes
23
16
  for (let row = 0; row < rows; row++) {
24
17
  for (let col = 0; col < cols; col++) {
25
18
  const entries = interpreter.getCalls([row, col], { water: waterSchema });
26
- const stack = [];
27
- for (const [, parsed] of entries) {
28
- stack.push({
29
- type: "water",
30
- y: parsed.y,
31
- textureId: 0,
32
- row,
33
- col,
34
- });
19
+ // Only build tile stack in non-validateOnly mode
20
+ if (!options.validateOnly) {
21
+ const stack = [];
22
+ for (const [, parsed] of entries) {
23
+ stack.push({
24
+ type: "water",
25
+ y: parsed.y,
26
+ textureId: 0,
27
+ row,
28
+ col,
29
+ });
30
+ }
31
+ tiles[row][col] = stack;
35
32
  }
36
- tiles[row][col] = stack;
37
33
  }
38
34
  }
35
+ // ValidateOnly mode: skip geometry creation
36
+ if (options.validateOnly) {
37
+ super();
38
+ return;
39
+ }
39
40
  const cellSize = interpreter.getCellSize();
40
41
  const mapSizeX = cols * cellSize;
41
42
  const mapSizeZ = rows * cellSize;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drawcall/charta",
3
- "version": "0.1.18",
3
+ "version": "0.1.19",
4
4
  "author": "Bela Bohlender",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "homepage": "https://drawcall.ai",