@bldrs-ai/conway 1.536.1507 → 1.543.1513

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.
Files changed (49) hide show
  1. package/compiled/examples/browser-bundled.cjs +39 -5
  2. package/compiled/examples/cli-bundled.cjs +197 -22
  3. package/compiled/examples/cli-step-bundled.cjs +39 -5
  4. package/compiled/examples/validator-bundled.cjs +39 -5
  5. package/compiled/src/compat/web-ifc/geometry_shard_coordination.test.d.ts +2 -0
  6. package/compiled/src/compat/web-ifc/geometry_shard_coordination.test.d.ts.map +1 -0
  7. package/compiled/src/compat/web-ifc/geometry_shard_coordination.test.js +236 -0
  8. package/compiled/src/compat/web-ifc/ifc_api.d.ts +26 -0
  9. package/compiled/src/compat/web-ifc/ifc_api.d.ts.map +1 -1
  10. package/compiled/src/compat/web-ifc/ifc_api.js +33 -0
  11. package/compiled/src/compat/web-ifc/ifc_api_model_passthrough.d.ts +6 -0
  12. package/compiled/src/compat/web-ifc/ifc_api_model_passthrough.d.ts.map +1 -1
  13. package/compiled/src/compat/web-ifc/ifc_api_preview_channel.test.js +264 -1
  14. package/compiled/src/compat/web-ifc/ifc_api_proxy_ifc.d.ts +116 -5
  15. package/compiled/src/compat/web-ifc/ifc_api_proxy_ifc.d.ts.map +1 -1
  16. package/compiled/src/compat/web-ifc/ifc_api_proxy_ifc.js +256 -85
  17. package/compiled/src/compat/web-ifc/store_preview_channel.d.ts +79 -0
  18. package/compiled/src/compat/web-ifc/store_preview_channel.d.ts.map +1 -1
  19. package/compiled/src/compat/web-ifc/store_preview_channel.js +237 -14
  20. package/compiled/src/compat/web-ifc/store_preview_channel.test.js +270 -1
  21. package/compiled/src/compat/web-ifc/streamed_preview_channel.d.ts +106 -1
  22. package/compiled/src/compat/web-ifc/streamed_preview_channel.d.ts.map +1 -1
  23. package/compiled/src/compat/web-ifc/streamed_preview_channel.js +269 -17
  24. package/compiled/src/core/progress_log.d.ts +75 -2
  25. package/compiled/src/core/progress_log.d.ts.map +1 -1
  26. package/compiled/src/core/progress_log.js +59 -2
  27. package/compiled/src/core/progress_log.test.js +75 -1
  28. package/compiled/src/ifc/dangling_placement_error.d.ts +35 -0
  29. package/compiled/src/ifc/dangling_placement_error.d.ts.map +1 -0
  30. package/compiled/src/ifc/dangling_placement_error.js +38 -0
  31. package/compiled/src/ifc/geometry_dispatch.d.ts +74 -3
  32. package/compiled/src/ifc/geometry_dispatch.d.ts.map +1 -1
  33. package/compiled/src/ifc/geometry_dispatch.js +261 -5
  34. package/compiled/src/ifc/geometry_dispatch.test.js +147 -1
  35. package/compiled/src/ifc/ifc_geometry_extraction.d.ts +54 -0
  36. package/compiled/src/ifc/ifc_geometry_extraction.d.ts.map +1 -1
  37. package/compiled/src/ifc/ifc_geometry_extraction.js +153 -20
  38. package/compiled/src/index.d.ts +1 -1
  39. package/compiled/src/index.d.ts.map +1 -1
  40. package/compiled/src/index.js +1 -1
  41. package/compiled/src/step/dangling_reference_error.d.ts +34 -0
  42. package/compiled/src/step/dangling_reference_error.d.ts.map +1 -0
  43. package/compiled/src/step/dangling_reference_error.js +36 -0
  44. package/compiled/src/step/step_entity_base.d.ts +17 -0
  45. package/compiled/src/step/step_entity_base.d.ts.map +1 -1
  46. package/compiled/src/step/step_entity_base.js +28 -4
  47. package/compiled/src/version/version.js +1 -1
  48. package/compiled/tsconfig.tsbuildinfo +1 -1
  49. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  import { ConwayGeometry, } from "../../index.js";
2
- import { geometryDispatchKey, shardOfDispatchKey } from "../../ifc/geometry_dispatch.js";
2
+ import { computeDispatchKeys, computeRelatingLocalIDs, geometryDispatchKey, relatingLocalIDOf, shardOfDispatchKey, } from "../../ifc/geometry_dispatch.js";
3
3
  import IfcStepModel from "../../ifc/ifc_step_model.js";
4
4
  import { WindowedStepBufferProvider } from "../../step/step_buffer_provider.js";
5
5
  import * as glmatrix from "gl-matrix";
@@ -186,6 +186,18 @@ export class IfcApiProxyIfc {
186
186
  * large-coordinate budget.
187
187
  */
188
188
  this.demandCoordinationFromPreview_ = false;
189
+ /**
190
+ * True when the coordination frame was HANDED to this instance rather than
191
+ * derived from its own first geometry (see setCoordinationFrame).
192
+ *
193
+ * The distinction is what makes a recentred model shardable. A derived
194
+ * frame is anchored on whichever product this instance happened to extract
195
+ * first, so N workers derive N frames and their outputs merge shifted by
196
+ * whole recentre cells; a supplied one is the same for every worker by
197
+ * construction, which is the only property the refusals in
198
+ * checkShardPreconditions_ were ever protecting.
199
+ */
200
+ this.coordinationSupplied_ = false;
189
201
  this._isCoordinated = false;
190
202
  this.linearScalingFactor = 1;
191
203
  this.identity = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
@@ -1483,7 +1495,7 @@ export class IfcApiProxyIfc {
1483
1495
  return { extracted: 0, remaining: 0 };
1484
1496
  }
1485
1497
  this.checkShardPreconditions_();
1486
- this.ensureDemandWorklists_();
1498
+ await this.ensureDemandWorklistsAsync_();
1487
1499
  if (!this.model[0].isSourceExternal) {
1488
1500
  return this.pumpGeometryBatch_(batchSize, meshCallback);
1489
1501
  }
@@ -1607,6 +1619,97 @@ export class IfcApiProxyIfc {
1607
1619
  if (this.demandProducts_ !== void 0) {
1608
1620
  return;
1609
1621
  }
1622
+ const { products, aggregates } = this.collectDemandCandidates_();
1623
+ if (this.shard_ === void 0) {
1624
+ this.adoptDemandWorklists_(products, aggregates);
1625
+ return;
1626
+ }
1627
+ // Reachable only on a resident source: the sync pump refuses a windowed
1628
+ // one outright, and the async pump routes a sharded model through
1629
+ // ensureDemandWorklistsAsync_. Asserted rather than assumed, because the
1630
+ // failure it guards against is a silently wrong partition — a walk that
1631
+ // hit a non-resident record here would throw out of the load instead
1632
+ // (see geometryDispatchKey), but the diagnostic would name a record
1633
+ // rather than the missing await.
1634
+ if (this.model[0].isSourceExternal) {
1635
+ throw new Error("a sharded windowed model needs the async worklist build, which " +
1636
+ "pages each product's dispatch closure \u2014 reached the synchronous " +
1637
+ "one instead");
1638
+ }
1639
+ const productKeys = new Uint32Array(products.length);
1640
+ for (let where = 0; where < products.length; ++where) {
1641
+ productKeys[where] = geometryDispatchKey(this.model[0], products[where]);
1642
+ }
1643
+ const aggregateKeys = new Uint32Array(aggregates.length);
1644
+ for (let where = 0; where < aggregates.length; ++where) {
1645
+ aggregateKeys[where] = geometryDispatchKey(this.model[0], relatingLocalIDOf(this.model[0], aggregates[where].localID));
1646
+ }
1647
+ this.adoptShardedWorklists_(products, aggregates, productKeys, aggregateKeys);
1648
+ }
1649
+ /**
1650
+ * Worklist build for a windowed source — the same partition, with the
1651
+ * dispatch walk's records paged before they are read.
1652
+ *
1653
+ * Sharding a windowed model rests on every worker computing the SAME key
1654
+ * for a product, and an inline walk cannot promise that: whether a record
1655
+ * read resolves depends on which chunks THIS worker holds, so workers
1656
+ * disagree and both moduli select a product (extracted twice) or neither
1657
+ * (dropped). computeDispatchKeys pages each hop of the walk first, which
1658
+ * is what makes the keys a function of the file rather than of paging —
1659
+ * and is why the pool can finally serve a store-backed open, the only kind
1660
+ * Share performs.
1661
+ *
1662
+ * Resident sources take the same route with the paging short-circuited, so
1663
+ * there is one partition rather than a windowed one and a resident one.
1664
+ */
1665
+ async ensureDemandWorklistsAsync_() {
1666
+ if (this.demandProducts_ !== void 0) {
1667
+ return;
1668
+ }
1669
+ // BEFORE collecting: the aggregate-target set is read from the
1670
+ // IfcRelAggregates records themselves, and on a windowed source those
1671
+ // pages are long gone by the first pump. The sync walk swallows the
1672
+ // non-resident read and caches the incomplete set for the model's life,
1673
+ // so a child it missed is extracted BOTH by the per-product worklist
1674
+ // (with the uncut content that deferral exists to suppress) and again by
1675
+ // the aggregates pass. Measured at 0 targets against a resident open's 1
1676
+ // on aggregate_master_voids.ifc behind 512-byte windows.
1677
+ //
1678
+ // Not a sharding bug — it costs an unsharded windowed load the same
1679
+ // duplicate — but this is the pump entry every windowed model reaches,
1680
+ // so it is the place that can page first.
1681
+ await this.conwayGeometry_.ensureAggregateTargetLocalIDs();
1682
+ const { products, aggregates } = this.collectDemandCandidates_();
1683
+ if (this.shard_ === void 0) {
1684
+ this.adoptDemandWorklists_(products, aggregates);
1685
+ return;
1686
+ }
1687
+ const productKeys = await computeDispatchKeys(this.model[0], products);
1688
+ const aggregateKeys = await computeDispatchKeys(this.model[0], await computeRelatingLocalIDs(this.model[0], aggregates.map((aggregate) => aggregate.localID)));
1689
+ // Re-checked after the awaits: a concurrent pump could have built the
1690
+ // worklists while this one was paging, and adopting a second set would
1691
+ // reset the cursors under it, re-extracting everything already pumped.
1692
+ if (this.demandProducts_ !== void 0) {
1693
+ return;
1694
+ }
1695
+ this.adoptShardedWorklists_(products, aggregates, productKeys, aggregateKeys);
1696
+ }
1697
+ /**
1698
+ * Everything the pump could extract, before any shard narrows it.
1699
+ *
1700
+ * Unchanged from what the pump enumerated before sharding existed, and
1701
+ * deliberately shared by both key computations so they start from the same
1702
+ * lists in the same order — that order is what makes the keys align to the
1703
+ * worklists.
1704
+ *
1705
+ * The product walk itself reads no record bytes (the type index and the
1706
+ * local-ID column answer it). `aggregateTargetLocalIDs` does scan the
1707
+ * IfcRelAggregates records, so a windowed caller must have paged them —
1708
+ * `ensureDemandWorklistsAsync_` does, and carries the reasoning.
1709
+ *
1710
+ * @return {object} The unsharded product and rel-aggregates worklists.
1711
+ */
1712
+ collectDemandCandidates_() {
1610
1713
  // Aggregate-target products are extracted ONLY by the
1611
1714
  // rel-aggregates pass below (with the relating object's master
1612
1715
  // rel-voids), never by the per-product pass: extracting them here
@@ -1627,28 +1730,51 @@ export class IfcApiProxyIfc {
1627
1730
  for (const relAggregate of this.model[0].types(IfcRelAggregates)) {
1628
1731
  aggregates.push(relAggregate);
1629
1732
  }
1733
+ return { products, aggregates };
1734
+ }
1735
+ /**
1736
+ * Install the whole model's worklists — the unsharded case.
1737
+ *
1738
+ * @param products Every extractable product, in index order.
1739
+ * @param aggregates Every rel-aggregates entry, in index order.
1740
+ */
1741
+ adoptDemandWorklists_(products, aggregates) {
1742
+ this.demandProducts_ = products;
1743
+ this.demandAggregates_ = aggregates;
1744
+ this.demandCursor_ = 0;
1745
+ this.demandAggregatesCursor_ = 0;
1746
+ }
1747
+ /**
1748
+ * Install the worklists narrowed to this instance's shard.
1749
+ *
1750
+ * Keys are passed in rather than computed here because where they come
1751
+ * from is the whole difference between a resident and a windowed source —
1752
+ * one walks the attributes directly, the other pages the walk's closure
1753
+ * first — while the narrowing itself is identical. Separate from
1754
+ * {@link adoptDemandWorklists_} so the keys are required by the signature
1755
+ * rather than by a comment.
1756
+ *
1757
+ * @param products Every extractable product, in index order.
1758
+ * @param aggregates Every rel-aggregates entry, in index order.
1759
+ * @param productKeys Placement keys aligned to `products`.
1760
+ * @param aggregateKeys Placement keys aligned to `aggregates`.
1761
+ */
1762
+ adoptShardedWorklists_(products, aggregates, productKeys, aggregateKeys) {
1763
+ const shard = this.shard_;
1630
1764
  let placed = 0;
1631
1765
  let positional = 0;
1632
- this.demandProducts_ = this.shard_ === void 0 ?
1633
- products : products.filter((localID) => {
1634
- const key = geometryDispatchKey(this.model[0], localID);
1766
+ this.demandProducts_ = products.filter((localID, where) => {
1767
+ const key = productKeys[where];
1635
1768
  // A key equal to the product's own local ID means the walk found
1636
1769
  // nothing to place by — a product with no representation. That
1637
1770
  // costs affinity, not correctness: the key is still a pure
1638
1771
  // function of the product, so every worker computes the same one
1639
1772
  // and the partition holds; it just stops avoiding duplication.
1640
- //
1641
- // Correctness would only break if the SAME product keyed
1642
- // differently in different workers, which is why windowed sources
1643
- // are refused outright (checkShardPreconditions_) rather than
1644
- // merely warned about — an earlier version of this comment had
1645
- // that wrong.
1646
1773
  key === localID ? ++positional : ++placed;
1647
- return shardOfDispatchKey(key, this.shard_.count) ===
1648
- this.shard_.index;
1774
+ return shardOfDispatchKey(key, shard.count) === shard.index;
1649
1775
  });
1650
- if (this.shard_ !== void 0 && positional > placed) {
1651
- Logger.warning(`[shard ${this.shard_.index}/${this.shard_.count}] ` +
1776
+ if (positional > placed) {
1777
+ Logger.warning(`[shard ${shard.index}/${shard.count}] ` +
1652
1778
  `${positional} of ${positional + placed} products have no ` +
1653
1779
  "placement key, so sharding is mostly positional and shared " +
1654
1780
  "geometry will be rebuilt per shard.");
@@ -1664,25 +1790,94 @@ export class IfcApiProxyIfc {
1664
1790
  // geometry, so getting it wrong leaves placement with almost nothing
1665
1791
  // to control (measured on D3D, where that mistake made every strategy
1666
1792
  // look identical).
1667
- this.demandAggregates_ = this.shard_ === void 0 ?
1668
- aggregates : aggregates.filter((relAggregate) => {
1669
- // Guarded even though sharding now refuses windowed sources:
1670
- // this getter reads a record synchronously, and geometryDispatchKey's
1671
- // own try/catch is INSIDE the call, so a throw here would abort
1672
- // worklist construction for the whole model rather than place one
1673
- // aggregate by fallback. Depth-in-depth for a total function.
1674
- let relating;
1675
- try {
1676
- relating = relAggregate.RelatingObject?.localID;
1677
- }
1678
- catch {
1679
- relating = void 0;
1680
- }
1681
- return shardOfDispatchKey(geometryDispatchKey(this.model[0], relating ?? relAggregate.localID), this.shard_.count) === this.shard_.index;
1682
- });
1793
+ this.demandAggregates_ = aggregates.filter((relAggregate, where) => shardOfDispatchKey(aggregateKeys[where], shard.count) === shard.index);
1683
1794
  this.demandCursor_ = 0;
1684
1795
  this.demandAggregatesCursor_ = 0;
1685
1796
  }
1797
+ /**
1798
+ * Hand this model the recentre frame to apply, instead of letting it
1799
+ * derive one — the other half of what a worker pool needs.
1800
+ *
1801
+ * `COORDINATE_TO_ORIGIN` normally recentres a model onto an anchor taken
1802
+ * from the FIRST geometry the instance captures. That is fine for one
1803
+ * instance and fatal for N: each worker starts on a different product, so
1804
+ * each derives a different frame, and a model spanning more than one
1805
+ * recentre cell reassembles with its shards offset by whole cells. Nothing
1806
+ * downstream can detect it — the placements are individually plausible.
1807
+ *
1808
+ * So the frame becomes an input. A coordinator derives it once (Share's
1809
+ * parse-time preview channel already does, and
1810
+ * {@link getAppliedCoordination} reports what any instance applied), hands
1811
+ * the same matrix to every worker, and the shards agree by construction
1812
+ * rather than by luck. This is what lifts the COORDINATE_TO_ORIGIN refusal
1813
+ * in {@link setGeometryShard}.
1814
+ *
1815
+ * A supplied frame is FINAL. The adopted-preview path re-derives when the
1816
+ * first durable placement lands outside the large-coordinate budget
1817
+ * (Share#1634); that check is disabled here, because a worker that
1818
+ * re-derived would silently leave the frame its siblings are still using.
1819
+ * A coordinator that wants that validation runs it on its own instance,
1820
+ * before it hands the frame out.
1821
+ *
1822
+ * Deferred models only, like sharding itself: a classic open has already
1823
+ * placed everything by the time this could be called.
1824
+ *
1825
+ * Supply it BEFORE claiming a shard: {@link setGeometryShard} refuses a
1826
+ * COORDINATE_TO_ORIGIN model that has no frame yet, so the reverse order
1827
+ * fails at the claim rather than at the pump.
1828
+ *
1829
+ * @param matrix Column-major mat4 of 16 finite numbers, or undefined to
1830
+ * drop a previously supplied frame and go back to deriving.
1831
+ */
1832
+ setCoordinationFrame(matrix) {
1833
+ // Same "before the first pump" rule as setGeometryShard, and for a
1834
+ // sharper reason: placements already emitted carry the old frame, and
1835
+ // nothing re-places them, so a late call would leave one model in two
1836
+ // coordinate systems.
1837
+ if (this.demandProducts_ !== void 0) {
1838
+ throw new Error("SetCoordinationFrame must be called before the first " +
1839
+ "ExtractGeometryBatch: placements already emitted carry the frame " +
1840
+ "that was in force when they were captured, and are not re-placed.");
1841
+ }
1842
+ if (!this.deferredMode_) {
1843
+ throw new Error("SetCoordinationFrame requires DEFER_GEOMETRY: a classic open has " +
1844
+ "already placed every product, so a frame supplied now would be " +
1845
+ "accepted and then ignored.");
1846
+ }
1847
+ if (matrix === void 0) {
1848
+ if (this.coordinationSupplied_) {
1849
+ this.demandCoordination_ = void 0;
1850
+ this.coordinationSupplied_ = false;
1851
+ this._isCoordinated = false;
1852
+ }
1853
+ return;
1854
+ }
1855
+ if (matrix.length !== IDENTITY_MAT4.length ||
1856
+ !matrix.every((element) => Number.isFinite(element))) {
1857
+ throw new Error(`invalid coordination frame: expected ${IDENTITY_MAT4.length} ` +
1858
+ `finite numbers (column-major mat4), got ${matrix.length}`);
1859
+ }
1860
+ // A frame this instance DERIVED (or adopted from its own preview) is not
1861
+ // one a coordinator can reconcile with — geometry has already been placed
1862
+ // in it. Overwriting would leave that geometry in the old frame and
1863
+ // everything after it in the new one.
1864
+ if (this.demandCoordination_ !== void 0 && !this.coordinationSupplied_) {
1865
+ throw new Error("this model has already adopted a coordination frame of its own " +
1866
+ "(a preview channel, or a first durable capture), so it cannot be " +
1867
+ "given another: geometry placed in the old frame is not re-placed.");
1868
+ }
1869
+ // Copied, not retained: a coordinator handing one array to N instances
1870
+ // and mutating it between calls would otherwise leave every instance
1871
+ // pointing at the last frame written. Same aliasing class as the shard
1872
+ // descriptor below.
1873
+ this.demandCoordination_ = [...matrix];
1874
+ this.coordinationSupplied_ = true;
1875
+ // Stops the capture path from deriving over the top of it (see
1876
+ // streamNewMeshes_), and stops the adopted-preview revalidation from
1877
+ // replacing it.
1878
+ this._isCoordinated = true;
1879
+ this.demandCoordinationFromPreview_ = false;
1880
+ }
1686
1881
  /**
1687
1882
  * Extract only one shard of this model's geometry — the across-product
1688
1883
  * parallelism seam.
@@ -1782,29 +1977,22 @@ export class IfcApiProxyIfc {
1782
1977
  "preview payloads.");
1783
1978
  }
1784
1979
  // Already-adopted frame, not just the current flag — see
1785
- // checkShardPreconditions_, which carries the reasoning.
1786
- if (this.demandCoordination_ !== void 0) {
1980
+ // checkShardPreconditions_, which carries the reasoning. A SUPPLIED
1981
+ // frame is the exception both refusals exist to allow: it is the same
1982
+ // matrix in every worker, so there is nothing left to disagree about.
1983
+ if (this.demandCoordination_ !== void 0 && !this.coordinationSupplied_) {
1787
1984
  throw new Error("SetGeometryShard cannot be used on a model that has already " +
1788
1985
  "adopted a recentre frame (for example via ON_PREVIEW_MESH): the " +
1789
1986
  "frame is derived from whichever geometry this instance saw " +
1790
1987
  "first, so workers would not share it.");
1791
1988
  }
1792
- if (this.settings?.COORDINATE_TO_ORIGIN === true) {
1793
- throw new Error("SetGeometryShard cannot be combined with COORDINATE_TO_ORIGIN: " +
1794
- "each shard would derive its own recentre anchor from whichever " +
1795
- "product it happens to extract first, so merged output can be " +
1796
- "shifted between shards. Open without COORDINATE_TO_ORIGIN, or " +
1797
- "wait for a shared coordination frame.");
1798
- }
1799
- // Refused at claim time as well as at every pump (see
1800
- // checkShardPreconditions_, which carries the reasoning): whether the
1801
- // dispatch key resolves depends on which pages are resident in this
1802
- // worker, so workers disagree and the partition stops being a partition.
1803
- if (this.model[0].isSourceExternal) {
1804
- throw new Error("SetGeometryShard cannot be used with a windowed external " +
1805
- "source: the dispatch key walks attribute records, whose " +
1806
- "residency differs per worker, so workers would disagree about " +
1807
- "which products they own.");
1989
+ if (this.settings?.COORDINATE_TO_ORIGIN === true && !this.coordinationSupplied_) {
1990
+ throw new Error("SetGeometryShard cannot be combined with COORDINATE_TO_ORIGIN " +
1991
+ "unless a coordination frame was supplied: each shard would " +
1992
+ "otherwise derive its own recentre anchor from whichever product " +
1993
+ "it happens to extract first, so merged output can be shifted " +
1994
+ "between shards. Call SetCoordinationFrame with one frame for the " +
1995
+ "whole pool, or open without COORDINATE_TO_ORIGIN.");
1808
1996
  }
1809
1997
  // Snapshotted, not retained. A coordinator configuring several engine
1810
1998
  // instances from one descriptor object — mutating `index` between calls
@@ -1851,12 +2039,19 @@ export class IfcApiProxyIfc {
1851
2039
  * it is the third such window found in this review; patching each one as
1852
2040
  * it turns up is losing strategy.
1853
2041
  *
1854
- * So a sharded model never recentres, whatever the flag says at the
1855
- * instant the frame would be derived. The refusals still fire first in
1856
- * every observable case, which is what a caller should see; this is what
1857
- * makes a missed one merely refused-late rather than silently wrong.
2042
+ * So a sharded model never DERIVES a frame, whatever the flag says at the
2043
+ * instant one would be derived. The refusals still fire first in every
2044
+ * observable case, which is what a caller should see; this is what makes a
2045
+ * missed one merely refused-late rather than silently wrong.
1858
2046
  *
1859
- * @return {boolean} Whether to derive and apply a recentre frame.
2047
+ * Deriving is all this governs. A frame handed in by
2048
+ * {@link setCoordinationFrame} is applied by the capture path
2049
+ * unconditionally — that is the point of supplying one — and this staying
2050
+ * false for a sharded model is exactly what keeps a worker from deriving
2051
+ * over the top of it.
2052
+ *
2053
+ * @return {boolean} Whether to derive a recentre frame from this
2054
+ * instance's first geometry.
1860
2055
  */
1861
2056
  recentreEnabled_() {
1862
2057
  return this.shard_ === void 0 && this.settings?.COORDINATE_TO_ORIGIN === true;
@@ -1881,42 +2076,18 @@ export class IfcApiProxyIfc {
1881
2076
  "be sharded: the preview runs during open, before a shard " +
1882
2077
  "exists, so its payloads are not partitioned.");
1883
2078
  }
1884
- if (this.demandCoordination_ !== void 0) {
2079
+ if (this.demandCoordination_ !== void 0 && !this.coordinationSupplied_) {
1885
2080
  throw new Error("a recentre frame has already been adopted on this model, so it " +
1886
2081
  "cannot be sharded: the frame came from whichever geometry this " +
1887
2082
  "instance saw first, and other workers will have adopted their " +
1888
2083
  "own.");
1889
2084
  }
1890
- if (this.settings?.COORDINATE_TO_ORIGIN === true) {
1891
- throw new Error("COORDINATE_TO_ORIGIN was enabled on a sharded model: each shard " +
1892
- "would derive its own recentre anchor from whichever product it " +
1893
- "extracts first, so merged output can be shifted between shards.");
1894
- }
1895
- // A windowed source breaks the property the whole partition rests on:
1896
- // that every worker computes the SAME key for a product. geometryDispatchKey
1897
- // walks attribute records, and on a windowed source whether that walk
1898
- // succeeds depends on which pages happen to be resident in THIS worker —
1899
- // so one worker returns the mapped-source key while another catches the
1900
- // non-resident read and falls back to the product's local ID.
1901
- //
1902
- // That is not weaker affinity, it is a wrong partition. Two workers
1903
- // disagreeing about a product's key can have both modulo results select
1904
- // it (extracted twice) or neither (dropped silently). The union check in
1905
- // m3_worker_pool.mjs cannot see it either, because it runs on resident
1906
- // buffers where the walk always succeeds.
1907
- //
1908
- // Making this work needs a key that does not depend on residency —
1909
- // derived from the reference columns, or from a closure paged
1910
- // deterministically before the split. Until then it is refused: workers
1911
- // on a windowed store were already out of scope for this change (they
1912
- // need the OPFS-backed source, not a resident buffer per worker), so
1913
- // this closes a path that has no caller rather than removing a feature.
1914
- if (this.model[0].isSourceExternal) {
1915
- throw new Error("a geometry shard cannot be claimed on a windowed external " +
1916
- "source: the dispatch key walks attribute records, so whether it " +
1917
- "resolves depends on which pages are resident in each worker, and " +
1918
- "workers that disagree about a product would extract it twice or " +
1919
- "not at all.");
2085
+ if (this.settings?.COORDINATE_TO_ORIGIN === true && !this.coordinationSupplied_) {
2086
+ throw new Error("COORDINATE_TO_ORIGIN was enabled on a sharded model with no " +
2087
+ "supplied coordination frame: each shard would derive its own " +
2088
+ "recentre anchor from whichever product it extracts first, so " +
2089
+ "merged output can be shifted between shards. Supply one frame " +
2090
+ "for the pool with SetCoordinationFrame.");
1920
2091
  }
1921
2092
  }
1922
2093
  pumpGeometryBatch_(batchSize, meshCallback) {
@@ -28,6 +28,40 @@ export declare class StorePreviewChannel {
28
28
  coordinationMatrix?: number[];
29
29
  private stopped_;
30
30
  private emittedUnits_;
31
+ /**
32
+ * Preview meshes handed to `onMesh_`. Tracked apart from
33
+ * {@link emittedUnits_} because the two genuinely diverge: a product can
34
+ * place several meshes, and a product with no Representation extracts
35
+ * cleanly while placing none — `extractProductGeometry` returns early on
36
+ * a null Representation and `extractProductGeometryByLocalID` still
37
+ * answers true. Reporting only units let the load log say "no mesh, 20
38
+ * emitted" about one load (codex round 2 on #543).
39
+ */
40
+ private meshesEmitted_;
41
+ private deferredUnits_;
42
+ private deferredOnPlacement_;
43
+ /**
44
+ * Products that deferred under the CURRENT generation, promoted to
45
+ * {@link retryQueue_} when the next one is built. Held separately so a
46
+ * retry always runs against a longer index than the attempt that failed —
47
+ * re-running one inside its own generation would fail identically and
48
+ * spin.
49
+ */
50
+ private deferredForRetry_;
51
+ private retryQueue_;
52
+ private retryCursor_;
53
+ private retriedUnits_;
54
+ /**
55
+ * Ms from channel construction to the first PLACED PRODUCT payload —
56
+ * time-to-first-pixel, the number conway#542 exists to move. The spatial
57
+ * plates are deliberately not counted: they are the building's wireframe
58
+ * skeleton, they go out on a different schedule (see
59
+ * {@link maybeEmitEarlySpatialPlates_}), and counting them would report
60
+ * first pixels for a load whose products are all still deferring.
61
+ * {@link earlyPlateCount} is where those are visible.
62
+ */
63
+ private firstMeshMs_?;
64
+ private readonly startedMs_;
31
65
  private emittedBytes_;
32
66
  private unitOrdinal_;
33
67
  private lastInlineTick_;
@@ -61,6 +95,35 @@ export declare class StorePreviewChannel {
61
95
  constructor(store_: StepExternalByteStore, sink_: ColumnarIndexSink<number>, conwaywasm_: ConwayGeometry, coordinateToOrigin_: boolean, onMesh_: (mesh: PreviewMeshPayload) => void, maxUnits?: number, maxBytes?: number, firstGenerationMinRecords?: number);
62
96
  get capped(): boolean;
63
97
  get emittedUnits(): number;
98
+ /**
99
+ * What the preview delivered, and how fast.
100
+ *
101
+ * `firstMeshMs` is time-to-first-pixel measured from channel
102
+ * construction, undefined when no placed product was ever emitted.
103
+ * `meshes` counts what actually reached the consumer, `emitted` counts
104
+ * the products extraction accepted — they are not the same number in
105
+ * either direction, so the line reports both.
106
+ * `deferred` counts products a tick attempted and could not extract;
107
+ * `deferredOnPlacement` is the subset still waiting on a placement chain
108
+ * the prefix does not hold; `retried` counts attempts that were second
109
+ * looks at an earlier deferral rather than first attempts, so a channel
110
+ * whose retry path has silently stopped firing is visible rather than
111
+ * merely slower. A first load that shows nothing is a very
112
+ * different problem depending on that ratio — near 1.0 is the file-layout
113
+ * case a sharded parse attacks, anything else is not — and inferring it
114
+ * from a code comment is what conway#542 exists to stop.
115
+ *
116
+ * @return {object} `{firstMeshMs, meshes, emitted, deferred,
117
+ * deferredOnPlacement, retried}`
118
+ */
119
+ get previewYield(): {
120
+ firstMeshMs?: number;
121
+ meshes: number;
122
+ emitted: number;
123
+ deferred: number;
124
+ deferredOnPlacement: number;
125
+ retried: number;
126
+ };
64
127
  /** Products the current generation would run (test seam). */
65
128
  get productCount(): number;
66
129
  /**
@@ -120,6 +183,20 @@ export declare class StorePreviewChannel {
120
183
  * @return {Promise<boolean>} True when a product was attempted.
121
184
  */
122
185
  private tickOnce_;
186
+ /**
187
+ * Whether either queue still has a product to attempt.
188
+ *
189
+ * Static and fully parameterised so the two callers — the tick and the
190
+ * generation gate — cannot drift apart on what "pending" means, which is
191
+ * the bug that would silently strand the retry queue.
192
+ *
193
+ * @param retryCursor How far into the retry queue this generation is.
194
+ * @param retryLength The retry queue's length.
195
+ * @param ordinal The forward cursor into the generation's products.
196
+ * @param productCount That generation's product count.
197
+ * @return {boolean} True when something is left to attempt.
198
+ */
199
+ private static hasPending_;
123
200
  /**
124
201
  * @return {Promise<boolean>} True when a generation with pending units exists.
125
202
  */
@@ -181,6 +258,8 @@ export declare class StorePreviewChannel {
181
258
  /**
182
259
  * Copy newly extracted instances out of wasm — same placement math
183
260
  * as {@link StreamedPreviewChannel}.
261
+ *
262
+ * @return {number} Meshes emitted by this pass.
184
263
  */
185
264
  private captureNewInstances_;
186
265
  }
@@ -1 +1 @@
1
- {"version":3,"file":"store_preview_channel.d.ts","sourceRoot":"","sources":["../../../../src/compat/web-ifc/store_preview_channel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAY5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAA;AAGrE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAA;AAU5E,OAAO,EACL,kBAAkB,EAEnB,MAAM,4BAA4B,CAAA;AAuCnC;;;;;;;;;;;;;GAaG;AACH,qBAAa,mBAAmB;IAoD1B,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,mBAAmB;IACpC,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,yBAAyB;IAzDvC,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAA;IAEpC,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,aAAa,CAAI;IACzB,OAAO,CAAC,aAAa,CAAI;IACzB,OAAO,CAAC,YAAY,CAAI;IACxB,OAAO,CAAC,eAAe,CAAI;IAC3B,OAAO,CAAC,eAAe,CAAmB;IAC1C,OAAO,CAAC,oBAAoB,CAAI;IAChC,OAAO,CAAC,0BAA0B,CAAI;IACtC,cAAc,CAAC,EAAE,MAAM,CAAA;IAMvB,OAAO,CAAC,aAAa,CAAiB;IACtC,OAAO,CAAC,gBAAgB,CAAoB;IAE5C,qEAAqE;IACrE,OAAO,CAAC,mBAAmB,CAAI;IAE/B;;;;;OAKG;IACH,OAAO,CAAC,6BAA6B,CAAQ;IAE7C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAsB;IAEvD,OAAO,CAAC,WAAW,CAAC,CAKnB;IAED;;;;;;;;;OASG;gBAEkB,MAAM,EAAE,qBAAqB,EAC7B,KAAK,EAAE,iBAAiB,CAAE,MAAM,CAAE,EAClC,WAAW,EAAE,cAAc,EAC3B,mBAAmB,EAAE,OAAO,EAC5B,OAAO,EAAE,CAAE,IAAI,EAAE,kBAAkB,KAAM,IAAI,EAC7C,QAAQ,GAAE,MAAkC,EAC5C,QAAQ,GAAE,MAAkC,EAC5C,yBAAyB,GAAE,MAChB;IAGhC,IAAW,MAAM,IAAI,OAAO,CAG3B;IAED,IAAW,YAAY,IAAI,MAAM,CAEhC;IAED,6DAA6D;IAC7D,IAAW,YAAY,IAAI,MAAM,CAEhC;IAED;;;OAGG;IACH,IAAW,eAAe,IAAI,MAAM,CAEnC;IAED;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACU,cAAc,IAAI,OAAO,CAAE,IAAI,CAAE;IAyB9C;;;;;OAKG;YACW,aAAa;IAmB3B;;;;;;OAMG;IACU,UAAU,IAAI,OAAO,CAAE,IAAI,CAAE;IAuB1C,wDAAwD;IACjD,IAAI,IAAI,IAAI;IAMnB;;;;OAIG;IACU,YAAY,IAAI,OAAO,CAAE,IAAI,CAAE;IAmB5C;;OAEG;YACW,SAAS;IAyCvB;;OAEG;YACW,iBAAiB;IAgF/B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAmDG;YACW,4BAA4B;IAsE1C,OAAO,CAAC,kBAAkB;IAiB1B;;;OAGG;IACH,OAAO,CAAC,oBAAoB;CA+H7B"}
1
+ {"version":3,"file":"store_preview_channel.d.ts","sourceRoot":"","sources":["../../../../src/compat/web-ifc/store_preview_channel.ts"],"names":[],"mappings":"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"}