@mark1russell7/layout-contract 0.2.14 → 0.2.15

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.
package/build/index.js CHANGED
@@ -260,6 +260,36 @@ const DEFAULT_TOLERANCE = Object.freeze({
260
260
  through: .5
261
261
  });
262
262
  // src/input.ts
263
+ /**
264
+ * This function gives the point of a port on the border of its box, relative to the top-left corner of the box.
265
+ * A port with `x` and `y` gives that point. A port with `side` and `along` gives the point at that fraction of the side.
266
+ * Another port gives `undefined`, because the router picks its point.
267
+ */
268
+ function portPoint(box, port) {
269
+ if (port.x !== void 0 && port.y !== void 0) return {
270
+ x: port.x,
271
+ y: port.y
272
+ };
273
+ if (port.side === void 0 || port.along === void 0) return void 0;
274
+ switch (port.side) {
275
+ case "top": return {
276
+ x: port.along * box.w,
277
+ y: 0
278
+ };
279
+ case "bottom": return {
280
+ x: port.along * box.w,
281
+ y: box.h
282
+ };
283
+ case "left": return {
284
+ x: 0,
285
+ y: port.along * box.h
286
+ };
287
+ case "right": return {
288
+ x: box.w,
289
+ y: port.along * box.h
290
+ };
291
+ }
292
+ }
263
293
  /** This function gives the packer input of a file. An input without a container gets `{ fixed: "none" }`, so the packer picks the size. */
264
294
  function toPackInput(input) {
265
295
  return {
@@ -271,7 +301,7 @@ function toPackInput(input) {
271
301
  };
272
302
  }
273
303
  /** This function compares two strings by their UTF-16 code units, as `Array.prototype.sort` does without a compare function. */
274
- const byCode = (x, y) => x < y ? -1 : x > y ? 1 : 0;
304
+ const byCode$1 = (x, y) => x < y ? -1 : x > y ? 1 : 0;
275
305
  /**
276
306
  * This function gives the canonical form of an input. `documentHash` uses it for each `layout-input/1` document.
277
307
  * Each pair of `pairGaps` gets `a` before `b`, and the list is sorted by `a` and then by `b`.
@@ -279,11 +309,11 @@ const byCode = (x, y) => x < y ? -1 : x > y ? 1 : 0;
279
309
  */
280
310
  function canonicalInput(input) {
281
311
  if (!Array.isArray(input.pairGaps)) return input;
282
- const pairGaps = input.pairGaps.map((p) => byCode(p.a, p.b) > 0 ? {
312
+ const pairGaps = input.pairGaps.map((p) => byCode$1(p.a, p.b) > 0 ? {
283
313
  ...p,
284
314
  a: p.b,
285
315
  b: p.a
286
- } : p).sort((x, y) => byCode(x.a, y.a) || byCode(x.b, y.b));
316
+ } : p).sort((x, y) => byCode$1(x.a, y.a) || byCode$1(x.b, y.b));
287
317
  return {
288
318
  ...input,
289
319
  pairGaps
@@ -298,14 +328,20 @@ function turnSide(side) {
298
328
  function turnPoint(x, y, h) {
299
329
  return [h - y, x];
300
330
  }
301
- /** This function gives a port of a box of height `h` after the turn. Each value of the port turns, also a port with only `x` or only `y`. */
331
+ /**
332
+ * This function gives a port of a box of height `h` after the turn. Each value of the port turns, also a port with only `x` or only `y`.
333
+ * The start of a side is its top end or its left end. Thus `left` goes to `top` and `right` goes to `bottom` with `along` changed to `1 - along`.
334
+ * `top` goes to `right` and `bottom` goes to `left` with the same `along`.
335
+ */
302
336
  function turnPort(port, h) {
303
- const { side, x, y, ...rest } = port;
337
+ const { side, x, y, along, ...rest } = port;
338
+ const flips = side === "left" || side === "right";
304
339
  return {
305
340
  ...rest,
306
341
  ...side === void 0 ? {} : { side: turnSide(side) },
307
342
  ...y === void 0 ? {} : { x: h - y },
308
- ...x === void 0 ? {} : { y: x }
343
+ ...x === void 0 ? {} : { y: x },
344
+ ...along === void 0 ? {} : { along: flips ? 1 - along : along }
309
345
  };
310
346
  }
311
347
  /** This function gives a margin after the turn: the value of each side moves one side clockwise. */
@@ -764,6 +800,7 @@ function packHard(input, output, tolerance = DEFAULT_TOLERANCE.overlap, options
764
800
  * This function gives the problems of an input file. An empty list tells that the file is good.
765
801
  * A problem is a bad input, not a break of a hard rule. Thus an engine refuses the input.
766
802
  * In `pairGaps`, an unknown box, a pair of one box, a repeated pair in either order, a bad `gap` and bad `lanes` are problems.
803
+ * A port with `along` and no `side`, with `x` or `y`, or with a value outside [0, 1] is a problem.
767
804
  */
768
805
  function checkInput(input) {
769
806
  const problems = [];
@@ -775,12 +812,23 @@ function checkInput(input) {
775
812
  if (box.locked === true && (box.x === void 0 || box.y === void 0)) problems.push(`box ${box.id}: a locked box has no position`);
776
813
  const shape = shapeKindProblem(box);
777
814
  if (shape !== void 0) problems.push(shape);
815
+ for (const port of box.ports ?? []) problems.push(...alongProblems(box.id, port));
778
816
  }
779
817
  if (countPortsOffBorder(input.boxes) > 0) problems.push("a fixed port is not on the border of its box");
780
818
  for (const edge of input.edges ?? []) if (!ids.has(edge.source) || !ids.has(edge.target)) problems.push(`edge ${edge.id}: a box of the edge is not in the input`);
781
819
  problems.push(...pairGapProblems(input.pairGaps, ids));
782
820
  return problems;
783
821
  }
822
+ /** This function gives the problems of the `along` of a port: no side, a value outside [0, 1], or `x` or `y` with it. A port without `along` has no problem. */
823
+ function alongProblems(boxId, port) {
824
+ if (port.along === void 0) return [];
825
+ const at = `box ${boxId} port ${port.id}`;
826
+ const problems = [];
827
+ if (port.side === void 0) problems.push(`${at}: a port with along has no side`);
828
+ if (typeof port.along !== "number" || !Number.isFinite(port.along) || port.along < 0 || port.along > 1) problems.push(`${at}: the along value ${String(port.along)} is not a finite number from 0 to 1`);
829
+ if (port.x !== void 0 || port.y !== void 0) problems.push(`${at}: a port with along also has x or y`);
830
+ return problems;
831
+ }
784
832
  /** This function gives the problems of the `pairGaps` of an input. `ids` has the ids of the boxes. */
785
833
  function pairGapProblems(pairGaps, ids) {
786
834
  if (pairGaps === void 0) return [];
@@ -2993,6 +3041,8 @@ function verdict(records, options) {
2993
3041
  };
2994
3042
  }
2995
3043
  // src/families.ts
3044
+ /** The name of the output field that lists the fields that `generate` of a registry removed. */
3045
+ const INVALIDATED_FIELD = "invalidated";
2996
3046
  /** The random numbers of the default factory: `mulberry32` of `random.ts`. Its stream is pinned in `families.test.ts`. */
2997
3047
  const DEFAULT_RNG = mulberry32;
2998
3048
  /** This function gives the `kind` of the cells of a version of a family: `<name>@<version>`, for example `rect-grid@2`. Thus two versions do not share a family key. */
@@ -3141,15 +3191,118 @@ const CORPUS_DIMENSIONS = Object.freeze({
3141
3191
  value: 0
3142
3192
  }, "The free space between two items, in units.", [P, J])
3143
3193
  });
3144
- /** This function gives a dimension of `CORPUS_DIMENSIONS`, with the fields of `patch` over it. It throws a `RangeError` for an unknown name. */
3194
+ /**
3195
+ * This function gives a dimension of `CORPUS_DIMENSIONS`, with the fields of `patch` over it. It throws a `RangeError` for an unknown name.
3196
+ * With `supported` or `invalidates` in the patch, it also throws a `RangeError` for a bad dimension, as `ownerDimension` does.
3197
+ */
3145
3198
  function sharedDimension(name, patch = {}) {
3146
3199
  const base = Object.hasOwn(CORPUS_DIMENSIONS, name) ? CORPUS_DIMENSIONS[name] : void 0;
3147
3200
  if (base === void 0) throw new RangeError(`The shared dimension table has no dimension "${name}".`);
3148
- return Object.freeze({
3201
+ const dim = Object.freeze({
3149
3202
  ...base,
3150
3203
  ...patch,
3151
3204
  name
3152
3205
  });
3206
+ if (patch.supported !== void 0 || patch.invalidates !== void 0) throwProblems(`sharedDimension: the dimension "${name}"`, ownProblems(dim));
3207
+ return dim;
3208
+ }
3209
+ /**
3210
+ * This function gives a dimension that only its owner declares, for example `cut` of a family of `packing`.
3211
+ * It throws a `RangeError` for a name of `CORPUS_DIMENSIONS`, the reserved name `kind`, or a bad default, level, `supported` or `invalidates`.
3212
+ * The registry examines the `when` of the dimension, because it needs the earlier dimensions.
3213
+ */
3214
+ function ownerDimension(name, spec) {
3215
+ const where = `ownerDimension: the dimension "${name}"`;
3216
+ if (typeof name !== "string" || name === "") throw new RangeError("ownerDimension: the name of a dimension is a text that is not empty.");
3217
+ if (name === "kind") throw new RangeError(`${where} has a reserved name: the cell of \`cellOf\` has the \`familyKind\` of the family in \`kind\`.`);
3218
+ if (Object.hasOwn(CORPUS_DIMENSIONS, name)) throw new RangeError(`${where} is in the shared dimension table. Use sharedDimension("${name}") for it.`);
3219
+ const dim = Object.freeze({
3220
+ ...spec,
3221
+ name
3222
+ });
3223
+ throwProblems(where, ownProblems(dim));
3224
+ return dim;
3225
+ }
3226
+ /** This function throws a `RangeError` with each problem of a list. An empty list throws nothing. */
3227
+ function throwProblems(where, problems) {
3228
+ if (problems.length > 0) throw new RangeError(`${where} is not good: ${problems.join("; ")}.`);
3229
+ }
3230
+ /** This function tells if a value of the stable form of a dimension is in its `supported` values. Without `supported`, each value is in them. */
3231
+ function inSupported(dim, value) {
3232
+ const s = dim.supported;
3233
+ if (s === void 0) return true;
3234
+ if (isSupportedList(s)) return s.some((v) => paramValue(dim, v) === value);
3235
+ if (typeof value !== "number") return false;
3236
+ return (s.min === void 0 || value >= s.min) && (s.max === void 0 || value <= s.max);
3237
+ }
3238
+ /** This function tells if `SupportedValues` has the list form. */
3239
+ const isSupportedList = (s) => Array.isArray(s);
3240
+ /**
3241
+ * This function tells if a family accepts a value of a dimension. A site uses it to show the values that a family does not support.
3242
+ * It gives `false` for a bad value, and for a value outside `supported`. Without `supported`, it gives `true` for each good value.
3243
+ */
3244
+ function dimensionSupports(dim, value) {
3245
+ if (paramProblem(dim, value) !== void 0) return false;
3246
+ return inSupported(dim, paramValue(dim, value));
3247
+ }
3248
+ /** This function gives the text of the supported values of a dimension, for a message. */
3249
+ function supportedText(dim) {
3250
+ const s = dim.supported;
3251
+ if (s === void 0) return "each value";
3252
+ if (isSupportedList(s)) return s.map((v) => JSON.stringify(v)).join(", ");
3253
+ return `the values from ${s.min ?? dim.min} to ${s.max ?? dim.max}`;
3254
+ }
3255
+ /** This function gives the problems of one dimension that need no other dimension: the default, the levels, `supported` and `invalidates`. */
3256
+ function ownProblems(d) {
3257
+ const out = [];
3258
+ const bad = paramProblem(d, d.default);
3259
+ if (bad !== void 0) out.push(`the default of "${d.name}" is bad: ${bad}`);
3260
+ for (const v of d.levels ?? []) {
3261
+ const p = paramProblem(d, v);
3262
+ if (p !== void 0) out.push(`a level of "${d.name}" is bad: ${p}`);
3263
+ }
3264
+ out.push(...supportProblems(d, bad === void 0));
3265
+ out.push(...invalidatesProblems(d));
3266
+ return out;
3267
+ }
3268
+ /** This function gives the problems of the `supported` values of a dimension. `goodDefault` tells that the default is a good value. */
3269
+ function supportProblems(d, goodDefault) {
3270
+ const s = d.supported;
3271
+ if (s === void 0) return [];
3272
+ const out = [];
3273
+ const numeric = d.kind === "int" || d.kind === "real";
3274
+ if (isSupportedList(s)) {
3275
+ if (numeric) return [`the supported values of "${d.name}" are a range with min and max, because the dimension is numeric`];
3276
+ if (s.length === 0) return [`the supported values of "${d.name}" are an empty list`];
3277
+ for (const v of s) {
3278
+ const p = paramProblem(d, v);
3279
+ if (p !== void 0) out.push(`a supported value of "${d.name}" is bad: ${p}`);
3280
+ }
3281
+ if (new Set(s).size !== s.length) out.push(`the supported values of "${d.name}" have a value two times`);
3282
+ } else {
3283
+ if (s === null || typeof s !== "object") return [`the supported values of "${d.name}" are a list or a range`];
3284
+ if (!numeric) return [`the supported values of "${d.name}" are a list, because the dimension is not numeric`];
3285
+ for (const end of [s.min, s.max]) {
3286
+ if (end === void 0) continue;
3287
+ const p = paramProblem(d, end);
3288
+ if (p !== void 0) out.push(`an end of the supported range of "${d.name}" is bad: ${p}`);
3289
+ }
3290
+ if (s.min !== void 0 && s.max !== void 0 && s.min > s.max) out.push(`the supported range of "${d.name}" has a min above its max`);
3291
+ }
3292
+ if (out.length > 0) return out;
3293
+ if (goodDefault && !inSupported(d, paramValue(d, d.default))) out.push(`the default of "${d.name}" is not a supported value`);
3294
+ for (const v of d.levels ?? []) if (paramProblem(d, v) === void 0 && !inSupported(d, paramValue(d, v))) out.push(`the level ${JSON.stringify(v)} of "${d.name}" is not a supported value`);
3295
+ return out;
3296
+ }
3297
+ /** This function gives the problems of the `invalidates` fields of a dimension. */
3298
+ function invalidatesProblems(d) {
3299
+ const list = d.invalidates;
3300
+ if (list === void 0) return [];
3301
+ if (!Array.isArray(list) || !list.every((f) => typeof f === "string" && f !== "")) return [`the invalidates of "${d.name}" is not a list of field names`];
3302
+ const out = [];
3303
+ if (new Set(list).size !== list.length) out.push(`the invalidates of "${d.name}" has a field two times`);
3304
+ for (const f of ["hash", INVALIDATED_FIELD]) if (list.includes(f)) out.push(`the invalidates of "${d.name}" has the field "${f}", which the registry writes`);
3305
+ return out;
3153
3306
  }
3154
3307
  /** This function tells if a log scale of a range starts at 0. Then the scale is the log of `1 + x - min`. */
3155
3308
  const fromZero = (d) => d.scale === "log" && (d.min ?? 0) <= 0;
@@ -3178,30 +3331,45 @@ function dimensionUnit(dim, value) {
3178
3331
  if (fromZero(dim)) return Math.log1p(v - min) / Math.log1p(max - min);
3179
3332
  return (Math.log(v) - Math.log(min)) / (Math.log(max) - Math.log(min));
3180
3333
  }
3334
+ /** This function gives the range of the supported values of a numeric dimension: its own `min` and `max` in place of the ends of `supported`. */
3335
+ function supportedSpan(dim) {
3336
+ const s = dim.supported;
3337
+ const range = s === void 0 || isSupportedList(s) ? {} : s;
3338
+ return {
3339
+ min: range.min ?? dim.min,
3340
+ max: range.max ?? dim.max
3341
+ };
3342
+ }
3181
3343
  /**
3182
3344
  * This function gives the values of a sweep of a dimension. `levels` of the dimension come first. Then an `enum` gives its values, and a `bool` gives `false` and `true`.
3183
3345
  * With `steps`, a numeric dimension gives `steps` points from `min` to `max` on its scale. Else it gives its default.
3346
+ * With `supported`, an `enum` and a `bool` give only the supported values, and the points of a numeric dimension stay in the supported range.
3184
3347
  */
3185
3348
  function levelsOf(dim, steps) {
3186
3349
  if (dim.levels !== void 0) return [...dim.levels];
3187
- if (dim.kind === "enum" || dim.kind === "bool" || steps === void 0) return paramLevels(dim, 1);
3350
+ if (dim.kind === "enum" || dim.kind === "bool") return paramLevels(dim, 1).filter((v) => inSupported(dim, paramValue(dim, v)));
3351
+ if (steps === void 0) return paramLevels(dim, 1);
3352
+ if (dim.supported !== void 0) {
3353
+ const { supported: _supported, ...rest } = dim;
3354
+ const span = supportedSpan(dim);
3355
+ return levelsOf({
3356
+ ...rest,
3357
+ ...span.min === void 0 ? {} : { min: span.min },
3358
+ ...span.max === void 0 ? {} : { max: span.max }
3359
+ }, steps);
3360
+ }
3188
3361
  if (!fromZero(dim)) return paramLevels(dim, steps);
3189
3362
  if (!Number.isInteger(steps) || steps < 1) throw new RangeError(`The steps of a sweep are a whole number of 1 or more, not ${steps}.`);
3190
3363
  if (steps === 1) return [paramValue(dim, dim.default)];
3191
3364
  return [...new Set(Array.from({ length: steps }, (_, j) => dimensionAt(dim, j / (steps - 1))))];
3192
3365
  }
3193
- /** This function gives the problems of the dimensions of a family: a repeated name, a bad default, a bad level or a bad `when`. */
3366
+ /** This function gives the problems of the dimensions of a family: a repeated name, a bad default, level, `supported`, `invalidates` or `when`. */
3194
3367
  function dimensionProblems(dims) {
3195
3368
  const out = [];
3196
3369
  const names = dims.map((d) => d.name);
3197
3370
  dims.forEach((d, i) => {
3198
3371
  if (names.indexOf(d.name) !== i) out.push(`the dimension "${d.name}" is in the list two times`);
3199
- const bad = paramProblem(d, d.default);
3200
- if (bad !== void 0) out.push(`the default of "${d.name}" is bad: ${bad}`);
3201
- for (const v of d.levels ?? []) {
3202
- const p = paramProblem(d, v);
3203
- if (p !== void 0) out.push(`a level of "${d.name}" is bad: ${p}`);
3204
- }
3372
+ out.push(...ownProblems(d));
3205
3373
  if (d.when === void 0) return;
3206
3374
  const j = names.indexOf(d.when.param);
3207
3375
  if (j < 0 || j >= i) out.push(`the when of "${d.name}" names "${d.when.param}", which is not an earlier dimension`);
@@ -3213,9 +3381,11 @@ function dimensionProblems(dims) {
3213
3381
  /**
3214
3382
  * This function gives the full dimension values of an input. A missing dimension gets its default. An inactive dimension of `when` also gets its default.
3215
3383
  * Thus a default that the caller gives, a missing value and an inactive value give the same values. A real number gets its stable form.
3384
+ *
3216
3385
  * The function throws a `RangeError` for an unknown dimension or a bad value, also for a bad value of an inactive dimension.
3386
+ * It also throws a `RangeError` for a value outside `supported`, with the text "not supported by" and `by`.
3217
3387
  */
3218
- function resolveDimensions(dimensions, dims = {}, where = "The family") {
3388
+ function resolveDimensions(dimensions, dims = {}, where = "The family", by = "the family") {
3219
3389
  const known = new Set(dimensions.map((d) => d.name));
3220
3390
  for (const key of Object.keys(dims)) if (!known.has(key)) throw new RangeError(`${where} has no dimension "${key}".`);
3221
3391
  const active = {};
@@ -3225,6 +3395,7 @@ function resolveDimensions(dimensions, dims = {}, where = "The family") {
3225
3395
  if (given !== void 0) {
3226
3396
  const bad = paramProblem(d, given);
3227
3397
  if (bad !== void 0) throw new RangeError(`${where}: the dimension "${d.name}" is bad: ${bad}.`);
3398
+ if (!inSupported(d, paramValue(d, given))) throw new RangeError(`${where}: the value ${JSON.stringify(given)} of the dimension "${d.name}" is not supported by ${by}. The supported values are ${supportedText(d)}.`);
3228
3399
  }
3229
3400
  const on = paramActive(d, active);
3230
3401
  const value = paramValue(d, on && given !== void 0 ? given : d.default);
@@ -3274,7 +3445,7 @@ function countOf(dimensions, levels, i = 0, values = {}) {
3274
3445
  return sum;
3275
3446
  }
3276
3447
  /** This function gives the levels of each dimension of a sweep. A dimension outside the sweep has `undefined`, so it keeps its default. */
3277
- function sweepLevels(dimensions, options, where) {
3448
+ function sweepLevels(dimensions, options, where, by) {
3278
3449
  const names = new Set(dimensions.map((d) => d.name));
3279
3450
  for (const name of [...options.sweep ?? [], ...Object.keys(options.levels ?? {})]) if (!names.has(name)) throw new RangeError(`${where} has no dimension "${name}".`);
3280
3451
  return dimensions.map((d) => {
@@ -3283,10 +3454,46 @@ function sweepLevels(dimensions, options, where) {
3283
3454
  for (const v of lv) {
3284
3455
  const bad = paramProblem(d, v);
3285
3456
  if (bad !== void 0) throw new RangeError(`${where}: a level of "${d.name}" is bad: ${bad}.`);
3457
+ if (!inSupported(d, paramValue(d, v))) throw new RangeError(`${where}: the level ${JSON.stringify(v)} of "${d.name}" is not supported by ${by}.`);
3286
3458
  }
3287
3459
  return lv;
3288
3460
  });
3289
3461
  }
3462
+ /** This function gives the output fields that the values make wrong: the `invalidates` of each dimension with a value other than its default, sorted. */
3463
+ function invalidFields(dimensions, full) {
3464
+ const out = /* @__PURE__ */ new Set();
3465
+ for (const d of dimensions) {
3466
+ if (d.invalidates === void 0 || d.invalidates.length === 0) continue;
3467
+ if (full[d.name] !== paramValue(d, d.default)) for (const f of d.invalidates) out.add(f);
3468
+ }
3469
+ return [...out].sort(byCode);
3470
+ }
3471
+ /** This function compares two strings by their UTF-16 code units. */
3472
+ const byCode = (x, y) => x < y ? -1 : x > y ? 1 : 0;
3473
+ /**
3474
+ * This function removes the fields of `fields` from an output, and adds their names to its field `invalidated`, sorted.
3475
+ * Only a field of the output counts. The `hash` of text gets the value of `hash` again. An output with none of the fields is the same object.
3476
+ */
3477
+ function dropFields(output, fields, hash, where) {
3478
+ if (fields.length === 0) return output;
3479
+ if (output === null || typeof output !== "object" || Array.isArray(output)) throw new RangeError(`${where} invalidates the fields ${fields.join(", ")}, but its output is not an object.`);
3480
+ const o = output;
3481
+ const dropped = fields.filter((f) => Object.hasOwn(o, f));
3482
+ if (dropped.length === 0) return output;
3483
+ const before = o[INVALIDATED_FIELD];
3484
+ const prior = Array.isArray(before) ? before.filter((f) => typeof f === "string") : [];
3485
+ const body = {};
3486
+ for (const key of Object.keys(o)) if (key !== "hash" && key !== "invalidated" && !dropped.includes(key)) body[key] = o[key];
3487
+ body[INVALIDATED_FIELD] = [.../* @__PURE__ */ new Set([...prior, ...dropped])].sort(byCode);
3488
+ if (typeof o["hash"] !== "string") return Object.hasOwn(o, "hash") ? {
3489
+ ...body,
3490
+ hash: o["hash"]
3491
+ } : body;
3492
+ return {
3493
+ ...body,
3494
+ hash: hash(body)
3495
+ };
3496
+ }
3290
3497
  /** This function gives the values that make a dimension active: the first value of its `when`, and the values that make that dimension active. */
3291
3498
  function activation(dimensions, dim) {
3292
3499
  const out = {};
@@ -3322,6 +3529,8 @@ function createFamilyRegistry(families = []) {
3322
3529
  const { kind: _kind, ...rest } = dims;
3323
3530
  return rest;
3324
3531
  };
3532
+ /** This function gives the full dimension values of an input of `f`. A message names the family as `familyKind` gives it. */
3533
+ const resolveIn = (f, dims) => resolveDimensions(f.dimensions, dims, label(f), familyKind(f.name, f.version));
3325
3534
  const streamOf = (f, seed, full) => {
3326
3535
  const rng = f.rng ?? DEFAULT_RNG;
3327
3536
  return {
@@ -3348,46 +3557,49 @@ function createFamilyRegistry(families = []) {
3348
3557
  families: () => [...list],
3349
3558
  resolve(name, dims, version) {
3350
3559
  const f = find(name, version);
3351
- return resolveDimensions(f.dimensions, ownDims(f, dims), label(f));
3560
+ return resolveIn(f, ownDims(f, dims));
3352
3561
  },
3353
3562
  generate(name, seed, dims, options = {}) {
3354
3563
  checkSeed(seed);
3355
3564
  const f = find(name, options.version);
3356
- const full = resolveDimensions(f.dimensions, ownDims(f, dims), label(f));
3565
+ const full = resolveIn(f, ownDims(f, dims));
3357
3566
  const context = {
3358
3567
  ...streamOf(f, seed, full),
3359
3568
  ...options.set === void 0 ? {} : { set: options.set }
3360
3569
  };
3361
- return f.generate(seed, full, context);
3570
+ const output = f.generate(seed, full, context);
3571
+ const hash = f.hash ?? documentHash;
3572
+ return dropFields(output, invalidFields(f.dimensions, full), hash, label(f));
3362
3573
  },
3363
3574
  cellOf(name, dims, version) {
3364
3575
  const f = find(name, version);
3365
3576
  return {
3366
3577
  kind: familyKind(f.name, f.version),
3367
- ...activeValues(f.dimensions, resolveDimensions(f.dimensions, ownDims(f, dims), label(f)))
3578
+ ...activeValues(f.dimensions, resolveIn(f, ownDims(f, dims)))
3368
3579
  };
3369
3580
  },
3370
3581
  *cells(name, options = {}) {
3371
3582
  const f = find(name, options.version);
3372
- for (const values of sweepOf(f.dimensions, sweepLevels(f.dimensions, options, label(f)))) yield resolveDimensions(f.dimensions, values, label(f));
3583
+ for (const values of sweepOf(f.dimensions, sweepLevels(f.dimensions, options, label(f), familyKind(f.name, f.version)))) yield resolveIn(f, values);
3373
3584
  },
3374
3585
  cellCount(name, options = {}) {
3375
3586
  const f = find(name, options.version);
3376
- return countOf(f.dimensions, sweepLevels(f.dimensions, options, label(f)));
3587
+ return countOf(f.dimensions, sweepLevels(f.dimensions, options, label(f), familyKind(f.name, f.version)));
3377
3588
  },
3378
3589
  boundaryCells(name, version) {
3379
3590
  const f = find(name, version);
3380
- const base = resolveDimensions(f.dimensions, {}, label(f));
3591
+ const base = resolveIn(f, {});
3381
3592
  const out = [base];
3382
3593
  const seen = /* @__PURE__ */ new Set([JSON.stringify(base)]);
3383
3594
  for (const d of f.dimensions) {
3384
3595
  if (d.kind !== "int" && d.kind !== "real") continue;
3385
- for (const end of [d.min, d.max]) {
3596
+ const span = supportedSpan(d);
3597
+ for (const end of [span.min, span.max]) {
3386
3598
  if (end === void 0) continue;
3387
- const cell = resolveDimensions(f.dimensions, {
3599
+ const cell = resolveIn(f, {
3388
3600
  ...activation(f.dimensions, d),
3389
3601
  [d.name]: end
3390
- }, label(f));
3602
+ });
3391
3603
  const key = JSON.stringify(cell);
3392
3604
  if (!seen.has(key)) {
3393
3605
  seen.add(key);
@@ -3401,7 +3613,7 @@ function createFamilyRegistry(families = []) {
3401
3613
  const f = find(name, version);
3402
3614
  return (seed, dims) => {
3403
3615
  checkSeed(seed);
3404
- return streamOf(f, seed, resolveDimensions(f.dimensions, ownDims(f, dims), label(f))).random;
3616
+ return streamOf(f, seed, resolveIn(f, ownDims(f, dims))).random;
3405
3617
  };
3406
3618
  },
3407
3619
  familyBy() {
@@ -3440,6 +3652,7 @@ function composeFamily(base, steps, options = {}) {
3440
3652
  ...base.rng === void 0 ? {} : { rng: base.rng },
3441
3653
  ...base.rngSeed === void 0 ? {} : { rngSeed: (seed, full) => base.rngSeed(seed, pick(base.dimensions, full)) },
3442
3654
  ...base.familyBy === void 0 ? {} : { familyBy: base.familyBy },
3655
+ ...options.hash === void 0 ? {} : { hash: options.hash },
3443
3656
  generate(seed, full, context) {
3444
3657
  let input = base.generate(seed, pick(base.dimensions, full), context);
3445
3658
  const parts = [];
@@ -3582,6 +3795,6 @@ const RECT_SCATTERED = Object.freeze({
3582
3795
  };
3583
3796
  }
3584
3797
  });
3585
- export { CORPUS_DIMENSIONS, DEFAULT_FAMILY_BY, DEFAULT_RNG, DEFAULT_SHAPES, DEFAULT_TOLERANCE, EDGES_STEP, EDGE_DENSITY, EDGE_STEP_VERSION, EPS, FAMILY_ANY_KIND, JOINT_MEASURES, JOINT_MOVED_MIN, PACK_HARD_RULES, RECORD_EPS, RECORD_SCALE, RECT_SCATTERED, SHAPE_KINDS, SHAPE_KIND_IDS, SIDES, SPLIT_SCHEMA, STRATEGY_SPACE_SCHEMA, VERDICT_DRAWS, VERDICT_EXACT_CAP, VERDICT_EXACT_MAX, VERDICT_RUNS, VERDICT_SEED, applyPack, boundsOf, canonicalConfig, canonicalInput, canonicalJson, checkConfig, checkDeclared, checkInput, checkShapes, checkSpace, checkSplit, compareMeasure, composeFamily, configId, corpusHashOf, countObstacleHits, countOutside, countOverlaps, countPortsOffBorder, createFamilyRegistry, declaredClaimOf, declaredCounts, dimensionAt, dimensionUnit, documentHash, edgeStep, enumerateSpace, exercised, familyKind, familyOf, fnv1a32, grow, isShapeKind, itemShapeOf, joinConfigs, leaveOneSeedOut, levelsOf, marginSides, minEffectOf, mulberry32, outputGroups, outputHash, overlapOf, packHard, paramActive, paramLevels, paramProblem, paramValue, pickMoves, placedRect, portfolio, productSpace, randomInt, rectInRegion, regionHitsRect, resolveDimensions, selectBest, sha256Hex, shapeKindOf, shapeKindProblem, shapeKindsOf, shapesOf, sharedDimension, spaceShapeKinds, spaceSize, splitConfig, splitFileName, splitFolds, stepRandom, toLayoutRecord, toPackInput, translate, turnBox, turnMargin, turnPoint, turnPort, turnRegion, turnSide, verdict };
3798
+ export { CORPUS_DIMENSIONS, DEFAULT_FAMILY_BY, DEFAULT_RNG, DEFAULT_SHAPES, DEFAULT_TOLERANCE, EDGES_STEP, EDGE_DENSITY, EDGE_STEP_VERSION, EPS, FAMILY_ANY_KIND, INVALIDATED_FIELD, JOINT_MEASURES, JOINT_MOVED_MIN, PACK_HARD_RULES, RECORD_EPS, RECORD_SCALE, RECT_SCATTERED, SHAPE_KINDS, SHAPE_KIND_IDS, SIDES, SPLIT_SCHEMA, STRATEGY_SPACE_SCHEMA, VERDICT_DRAWS, VERDICT_EXACT_CAP, VERDICT_EXACT_MAX, VERDICT_RUNS, VERDICT_SEED, applyPack, boundsOf, canonicalConfig, canonicalInput, canonicalJson, checkConfig, checkDeclared, checkInput, checkShapes, checkSpace, checkSplit, compareMeasure, composeFamily, configId, corpusHashOf, countObstacleHits, countOutside, countOverlaps, countPortsOffBorder, createFamilyRegistry, declaredClaimOf, declaredCounts, dimensionAt, dimensionSupports, dimensionUnit, documentHash, edgeStep, enumerateSpace, exercised, familyKind, familyOf, fnv1a32, grow, isShapeKind, itemShapeOf, joinConfigs, leaveOneSeedOut, levelsOf, marginSides, minEffectOf, mulberry32, outputGroups, outputHash, overlapOf, ownerDimension, packHard, paramActive, paramLevels, paramProblem, paramValue, pickMoves, placedRect, portPoint, portfolio, productSpace, randomInt, rectInRegion, regionHitsRect, resolveDimensions, selectBest, sha256Hex, shapeKindOf, shapeKindProblem, shapeKindsOf, shapesOf, sharedDimension, spaceShapeKinds, spaceSize, splitConfig, splitFileName, splitFolds, stepRandom, toLayoutRecord, toPackInput, translate, turnBox, turnMargin, turnPoint, turnPort, turnRegion, turnSide, verdict };
3586
3799
 
3587
3800
  //# sourceMappingURL=index.js.map