@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
@@ -59,6 +59,17 @@ export interface PreviewMeshPayload {
59
59
  */
60
60
  solid?: boolean;
61
61
  }
62
+ /**
63
+ * One unit a generation attempted and could not extract.
64
+ *
65
+ * `onPlacement` marks the subset waiting on a placement chain the prefix
66
+ * does not hold yet — the only cause worth a retry, because for any other
67
+ * cause a longer index does not change the answer.
68
+ */
69
+ export interface PreviewDeferral {
70
+ ordinal: number;
71
+ onPlacement: boolean;
72
+ }
62
73
  /**
63
74
  * A throwaway prefix extraction built by a {@link PreviewSchemaAdapter} —
64
75
  * one preview "generation" over a snapshot of the growing columnar index.
@@ -82,6 +93,19 @@ export interface PreviewPrefixGeneration {
82
93
  * @return {number} Units actually executed.
83
94
  */
84
95
  runUnits(from: number, count: number): number;
96
+ /**
97
+ * Drain the units {@link runUnits} has swallowed an exception for since
98
+ * the last call — the channel's only view of them, because runUnits
99
+ * reports a count and the causes stay inside the adapter.
100
+ *
101
+ * Cleared by the read, so a caller that drains per unit attributes each
102
+ * deferral to the ordinal it just ran. Left unset by adapters that cannot
103
+ * classify their failures; the channel then queues no retries for that
104
+ * schema and behaves exactly as it did before conway#542.
105
+ *
106
+ * @return {PreviewDeferral[]} Deferrals since the last drain.
107
+ */
108
+ takeDeferrals?(): PreviewDeferral[];
85
109
  /**
86
110
  * ExpressID identifying a walked geometry (payload identity / dedup).
87
111
  *
@@ -204,7 +228,41 @@ export declare class StreamedPreviewChannel {
204
228
  private stopped_;
205
229
  private timer_?;
206
230
  private emittedUnits_;
231
+ /**
232
+ * Preview meshes handed to `onMesh`. Tracked apart from
233
+ * {@link emittedUnits_} because the two genuinely diverge: a unit can
234
+ * place several meshes, and a unit with no representation extracts
235
+ * cleanly while placing none. Reporting only units let the load log say
236
+ * "no mesh, 20 emitted" about one load (codex round 2 on #543).
237
+ */
238
+ private meshesEmitted_;
207
239
  private emittedBytes_;
240
+ private deferredUnits_;
241
+ private deferredOnPlacement_;
242
+ /**
243
+ * Ordinals that deferred on a placement under the CURRENT generation,
244
+ * promoted to {@link retryQueue_} when the next one is built. Held
245
+ * separately so a retry always runs against a longer index than the
246
+ * attempt that failed — re-running one inside its own generation would
247
+ * fail identically and spin.
248
+ */
249
+ private deferredForRetry_;
250
+ private retryQueue_;
251
+ private retryCursor_;
252
+ private retriedUnits_;
253
+ /**
254
+ * Retry mode (adapter.retryEmptyUnits): units this generation ran that
255
+ * captured nothing. That schema's adapters cannot classify a failure —
256
+ * an assembly unit simply produces no instances until the solids it
257
+ * references are indexed — so an empty unit is its evidence that a
258
+ * longer index is the missing ingredient, standing in for
259
+ * {@link deferredForRetry_} in the preemption gate.
260
+ */
261
+ private emptyUnitsThisGeneration_;
262
+ /** Ms from channel construction to the first payload handed to `onMesh`
263
+ * — time-to-first-pixel, the number conway#542 exists to move. */
264
+ private firstMeshMs_?;
265
+ private readonly startedMs_;
208
266
  /** Ordinal cursor into the unit list (see the adapter's stability
209
267
  * notes). */
210
268
  private unitOrdinal_;
@@ -241,6 +299,7 @@ export declare class StreamedPreviewChannel {
241
299
  private lastInlineTick_;
242
300
  private tickIntervalMs_;
243
301
  private tickBudgetMs_;
302
+ private tickMaxAttempts_;
244
303
  /** Begin ticking (call just before awaiting the parse). */
245
304
  start(): void;
246
305
  /**
@@ -261,12 +320,48 @@ export declare class StreamedPreviewChannel {
261
320
  stop(): void;
262
321
  /** True when a cap was hit and the channel retired itself early. */
263
322
  get capped(): boolean;
323
+ /**
324
+ * What the preview delivered, and how fast.
325
+ *
326
+ * `firstMeshMs` is time-to-first-pixel measured from channel construction
327
+ * (immediately before the parse starts), undefined when nothing was ever
328
+ * emitted. `meshes` counts what actually reached the consumer, `emitted`
329
+ * counts the units extraction accepted — a unit can place several meshes
330
+ * and a unit with no representation places none, so the line reports
331
+ * both. `deferred` counts units a tick attempted and could not
332
+ * extract, `deferredOnPlacement` the subset waiting on a placement chain
333
+ * the prefix does not hold, and `retried` the attempts that were second
334
+ * looks at an earlier deferral — a retry path that has silently stopped
335
+ * firing shows up here rather than merely as a slower load.
336
+ *
337
+ * The same shape as {@link StorePreviewChannel.previewYield}, so both
338
+ * paths render through one formatter (core/progress_log).
339
+ *
340
+ * @return {object} `{firstMeshMs, meshes, emitted, deferred,
341
+ * deferredOnPlacement, retried}`
342
+ */
343
+ get previewYield(): {
344
+ firstMeshMs?: number;
345
+ meshes: number;
346
+ emitted: number;
347
+ deferred: number;
348
+ deferredOnPlacement: number;
349
+ retried: number;
350
+ };
264
351
  private schedule_;
265
352
  /**
266
353
  * One pump tick: ensure a generation with pending units exists, then
267
354
  * extract + capture under the time budget.
268
355
  */
269
356
  private tick_;
357
+ /**
358
+ * Take the deferrals the generation accumulated for the unit just run and
359
+ * fold them into the counters, queueing the placement ones for a second
360
+ * look at the next generation.
361
+ *
362
+ * @param generation The generation that ran the unit.
363
+ */
364
+ private drainDeferrals_;
270
365
  /**
271
366
  * Ensure a generation with pending units: keep the current one while
272
367
  * it has work; otherwise snapshot a fresh prefix model once the index
@@ -275,10 +370,20 @@ export declare class StreamedPreviewChannel {
275
370
  * @return {boolean} True when a generation with pending units exists.
276
371
  */
277
372
  private ensureGeneration_;
373
+ /**
374
+ * Whether the current generation has produced evidence that a longer
375
+ * index would help it — the preemption gate. Classic mode: units queued
376
+ * for a retry. Retry mode: units that ran and captured nothing, since
377
+ * that schema's adapter cannot classify a failure.
378
+ *
379
+ * @return {boolean} True when this generation is deferring.
380
+ */
381
+ private hasDeferred_;
278
382
  /**
279
383
  * Does the given generation still have units this channel would run —
280
384
  * retry mode: not-yet-captured ordinals ahead of the scan; classic
281
- * mode: ordinals beyond the global forward-only cursor.
385
+ * mode: queued retries, or ordinals beyond the global forward-only
386
+ * cursor.
282
387
  *
283
388
  * @param generation The generation to check.
284
389
  * @return {boolean} True when a tick can make progress on it.
@@ -1 +1 @@
1
- {"version":3,"file":"streamed_preview_channel.d.ts","sourceRoot":"","sources":["../../../../src/compat/web-ifc/streamed_preview_channel.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"streamed_preview_channel.d.ts","sourceRoot":"","sources":["../../../../src/compat/web-ifc/streamed_preview_channel.ts"],"names":[],"mappings":"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"}
@@ -7,6 +7,9 @@ import { AP214GeometryExtraction, } from "../../AP214E3_2010/ap214_geometry_extr
7
7
  import { cursorIterator } from "../../indexing/cursor_utilities.js";
8
8
  import * as glmatrix from "gl-matrix";
9
9
  import { composeTransformF64, deriveCoordinationF64, NORMALIZE_MAT_F64, } from "./coordination_f64.js";
10
+ import { DanglingPlacementError } from "../../ifc/dangling_placement_error.js";
11
+ import { formatPreviewLine } from "../../core/progress_log.js";
12
+ import Logger from "../../logging/logger.js";
10
13
  /* eslint-disable no-magic-numbers */
11
14
  /** Initial ms between preview pump ticks (interleaves with the parse's
12
15
  * yields). The interval decays (TICK_INTERVAL_GROWTH per tick, capped at
@@ -25,6 +28,26 @@ const FIRST_GENERATION_MIN_RECORDS = 1024;
25
28
  /** A new generation only when the index grew this much past the previous
26
29
  * snapshot (bounds snapshot copies to O(GROWTH/(GROWTH-1)) of the file). */
27
30
  const GENERATION_GROWTH_FACTOR = 2;
31
+ /**
32
+ * Deferred units carried into the next generation for a second look.
33
+ *
34
+ * Twin of {@link StorePreviewChannel}'s queue, for the same defect: the
35
+ * channel was strictly forward-only, so a unit whose placement chain lay
36
+ * beyond the prefix was attempted once — at the earliest and therefore most
37
+ * index-starved generation it appeared in — and never again, because
38
+ * `unitOrdinal_` only ever advances and a rebuilt generation regenerates the
39
+ * unit list in the same dense parse order. On a file whose placement chains
40
+ * resolve late that throws nearly everything away: DOWA has 19,854 of its
41
+ * 45,860 products emittable once 70% of the file is indexed, and the store
42
+ * path emitted 1 before this queue existed (conway#542).
43
+ *
44
+ * A retry is affordable in a way a first attempt is not: a deferring attempt
45
+ * fails on an index lookup before any geometry is paged (~0.17 ms measured
46
+ * on the store path, against ~700 ms for a generation rebuild). The cap is
47
+ * here so the queue cannot grow with the model — it only has to keep
48
+ * DEFAULT_MAX_PREVIEW_UNITS worth of candidates in play.
49
+ */
50
+ const RETRY_QUEUE_MAX = 4096;
28
51
  /** Default cap on units the preview channel ever extracts. Preview
29
52
  * generations are throwaway extractions whose native geometry is not
30
53
  * reclaimed until page teardown (the shim never frees classic scenes
@@ -78,6 +101,13 @@ export function ifcPreviewAdapter() {
78
101
  // Preview-only preparation: skip the relationship sweeps whose
79
102
  // entity materialization dominates per-generation cost.
80
103
  extraction.prepareDemandExtraction(true);
104
+ // Deferrals since the channel last drained them. Product ORDINALS,
105
+ // not localIDs: the channel replays them against a later generation,
106
+ // and `products` is enumerated off the type index in dense parse
107
+ // order, which a longer prefix only extends (see
108
+ // ColumnarIndexSink.snapshot) — so an ordinal means the same product
109
+ // in every generation that holds it.
110
+ let deferrals = [];
81
111
  return {
82
112
  scene: extraction.scene,
83
113
  unitCount: products.length,
@@ -93,13 +123,25 @@ export function ifcPreviewAdapter() {
93
123
  ++executed;
94
124
  }
95
125
  }
96
- catch {
126
+ catch (error) {
97
127
  // Unparsed forward reference — the durable pump extracts
98
- // this product from the full model later.
128
+ // this product from the full model later. Recorded by cause:
129
+ // a dangling placement is a product the index will be able
130
+ // to place once it is longer, and is the one case worth
131
+ // attempting again (conway#542).
132
+ deferrals.push({
133
+ ordinal: where,
134
+ onPlacement: error instanceof DanglingPlacementError,
135
+ });
99
136
  }
100
137
  }
101
138
  return executed;
102
139
  },
140
+ takeDeferrals: () => {
141
+ const drained = deferrals;
142
+ deferrals = [];
143
+ return drained;
144
+ },
103
145
  geometryExpressID: (geometryLocalID) => model.getElementByLocalID(geometryLocalID)?.expressID,
104
146
  recenter: true,
105
147
  dispose: () => {
@@ -226,7 +268,42 @@ export class StreamedPreviewChannel {
226
268
  this.firstGenerationMinRecords = firstGenerationMinRecords;
227
269
  this.stopped_ = false;
228
270
  this.emittedUnits_ = 0;
271
+ /**
272
+ * Preview meshes handed to `onMesh`. Tracked apart from
273
+ * {@link emittedUnits_} because the two genuinely diverge: a unit can
274
+ * place several meshes, and a unit with no representation extracts
275
+ * cleanly while placing none. Reporting only units let the load log say
276
+ * "no mesh, 20 emitted" about one load (codex round 2 on #543).
277
+ */
278
+ this.meshesEmitted_ = 0;
229
279
  this.emittedBytes_ = 0;
280
+ /* Units a tick attempted and could not extract, and how many of those were
281
+ * specifically waiting on a placement chain the prefix does not hold yet.
282
+ * Reported on the way out so a blank first load is attributable rather
283
+ * than merely observed (conway#542). */
284
+ this.deferredUnits_ = 0;
285
+ this.deferredOnPlacement_ = 0;
286
+ /**
287
+ * Ordinals that deferred on a placement under the CURRENT generation,
288
+ * promoted to {@link retryQueue_} when the next one is built. Held
289
+ * separately so a retry always runs against a longer index than the
290
+ * attempt that failed — re-running one inside its own generation would
291
+ * fail identically and spin.
292
+ */
293
+ this.deferredForRetry_ = [];
294
+ this.retryQueue_ = [];
295
+ this.retryCursor_ = 0;
296
+ this.retriedUnits_ = 0;
297
+ /**
298
+ * Retry mode (adapter.retryEmptyUnits): units this generation ran that
299
+ * captured nothing. That schema's adapters cannot classify a failure —
300
+ * an assembly unit simply produces no instances until the solids it
301
+ * references are indexed — so an empty unit is its evidence that a
302
+ * longer index is the missing ingredient, standing in for
303
+ * {@link deferredForRetry_} in the preemption gate.
304
+ */
305
+ this.emptyUnitsThisGeneration_ = 0;
306
+ this.startedMs_ = Date.now();
230
307
  /** Ordinal cursor into the unit list (see the adapter's stability
231
308
  * notes). */
232
309
  this.unitOrdinal_ = 0;
@@ -252,6 +329,14 @@ export class StreamedPreviewChannel {
252
329
  // a loaded runner: the retryEmptyUnits test expected [0,1,2] and got [0,1]
253
330
  // in CI while passing locally. Production behaviour is unchanged.
254
331
  this.tickBudgetMs_ = TICK_BUDGET_MS;
332
+ // Companion seam to tickBudgetMs_, and unbounded in production: a resident
333
+ // unit attempt pages nothing (the store channel's TICK_MAX_ATTEMPTS exists
334
+ // because its attempts page source through a windowed provider), so the
335
+ // wall clock is the only bound worth paying for here. A test pins it so
336
+ // "this tick left units unattempted" — which is what separates a
337
+ // preemptive rebuild from an exhaustion one — is a fact rather than a race
338
+ // against the budget.
339
+ this.tickMaxAttempts_ = Number.MAX_SAFE_INTEGER;
255
340
  }
256
341
  /** Begin ticking (call just before awaiting the parse). */
257
342
  start() {
@@ -306,12 +391,57 @@ export class StreamedPreviewChannel {
306
391
  // Never let a free break the open.
307
392
  }
308
393
  this.generation_ = void 0;
394
+ // Say what the preview delivered on the way out. The channel is a local
395
+ // of the open call — nothing outside holds it — so a counter not
396
+ // reported here is a counter nobody can read. Same formatter as the
397
+ // store path, and the same one Share renders (core/progress_log), so a
398
+ // pasted browser log and a pasted CLI run read identically.
399
+ //
400
+ // Unconditional, not gated on emittedUnits_ or deferredUnits_ being
401
+ // nonzero: a channel that never reached firstGenerationMinRecords, or
402
+ // whose every generation build threw, is precisely the worst-case
403
+ // blank-first-load this issue exists to make diagnosable, and
404
+ // formatPreviewLine already renders that case as "no mesh, 0 emitted,
405
+ // 0 deferred". Suppressing the line there made an enabled preview that
406
+ // produced nothing indistinguishable from a preview that never ran
407
+ // (codex round 1 on #543).
408
+ Logger.info(formatPreviewLine(this.previewYield));
309
409
  }
310
410
  /** True when a cap was hit and the channel retired itself early. */
311
411
  get capped() {
312
412
  return this.emittedUnits_ >= this.maxUnits ||
313
413
  this.emittedBytes_ >= this.maxBytes;
314
414
  }
415
+ /**
416
+ * What the preview delivered, and how fast.
417
+ *
418
+ * `firstMeshMs` is time-to-first-pixel measured from channel construction
419
+ * (immediately before the parse starts), undefined when nothing was ever
420
+ * emitted. `meshes` counts what actually reached the consumer, `emitted`
421
+ * counts the units extraction accepted — a unit can place several meshes
422
+ * and a unit with no representation places none, so the line reports
423
+ * both. `deferred` counts units a tick attempted and could not
424
+ * extract, `deferredOnPlacement` the subset waiting on a placement chain
425
+ * the prefix does not hold, and `retried` the attempts that were second
426
+ * looks at an earlier deferral — a retry path that has silently stopped
427
+ * firing shows up here rather than merely as a slower load.
428
+ *
429
+ * The same shape as {@link StorePreviewChannel.previewYield}, so both
430
+ * paths render through one formatter (core/progress_log).
431
+ *
432
+ * @return {object} `{firstMeshMs, meshes, emitted, deferred,
433
+ * deferredOnPlacement, retried}`
434
+ */
435
+ get previewYield() {
436
+ return {
437
+ firstMeshMs: this.firstMeshMs_,
438
+ meshes: this.meshesEmitted_,
439
+ emitted: this.emittedUnits_,
440
+ deferred: this.deferredUnits_,
441
+ deferredOnPlacement: this.deferredOnPlacement_,
442
+ retried: this.retriedUnits_,
443
+ };
444
+ }
315
445
  // eslint-disable-next-line require-jsdoc
316
446
  schedule_() {
317
447
  if (this.stopped_ || this.capped) {
@@ -341,6 +471,7 @@ export class StreamedPreviewChannel {
341
471
  }
342
472
  const active = this.generation_;
343
473
  const { generation } = active;
474
+ let attempts = 0;
344
475
  if (this.adapter.retryEmptyUnits === true) {
345
476
  // Retry mode: scan the fixed unit list, skipping units that
346
477
  // already captured instances on an earlier (or this) generation.
@@ -348,32 +479,70 @@ export class StreamedPreviewChannel {
348
479
  // counts here are small by construction (assembly roots).
349
480
  while (this.retryScan_ < generation.unitCount &&
350
481
  this.emittedUnits_ < this.maxUnits &&
482
+ attempts < this.tickMaxAttempts_ &&
351
483
  Date.now() < deadline) {
352
484
  const ordinal = this.retryScan_++;
353
485
  if (this.completedUnits_.has(ordinal)) {
354
486
  continue;
355
487
  }
488
+ ++attempts;
356
489
  const executed = generation.runUnits(ordinal, 1);
357
490
  if (executed > 0 && this.captureNewInstances_() > 0) {
358
491
  this.completedUnits_.add(ordinal);
359
492
  ++this.emittedUnits_;
360
493
  }
494
+ else {
495
+ ++this.emptyUnitsThisGeneration_;
496
+ }
361
497
  }
362
498
  return;
363
499
  }
364
500
  let extractedThisTick = 0;
365
- while (this.unitOrdinal_ < generation.unitCount &&
501
+ while (this.hasPendingUnits_(generation) &&
366
502
  this.emittedUnits_ + extractedThisTick < this.maxUnits &&
503
+ attempts < this.tickMaxAttempts_ &&
367
504
  Date.now() < deadline) {
368
- const executed = generation.runUnits(this.unitOrdinal_, 1);
369
- ++this.unitOrdinal_;
370
- extractedThisTick += executed;
505
+ ++attempts;
506
+ // Retries first: they are the units the index has most recently
507
+ // become able to place, and they are cheaper than a fresh attempt
508
+ // whichever way they go.
509
+ const isRetry = this.retryCursor_ < this.retryQueue_.length;
510
+ const ordinal = isRetry ?
511
+ this.retryQueue_[this.retryCursor_++] : this.unitOrdinal_++;
512
+ if (isRetry) {
513
+ ++this.retriedUnits_;
514
+ }
515
+ extractedThisTick += generation.runUnits(ordinal, 1);
516
+ this.drainDeferrals_(generation);
371
517
  }
372
518
  if (extractedThisTick > 0) {
373
519
  this.captureNewInstances_();
374
520
  this.emittedUnits_ += extractedThisTick;
375
521
  }
376
522
  }
523
+ /**
524
+ * Take the deferrals the generation accumulated for the unit just run and
525
+ * fold them into the counters, queueing the placement ones for a second
526
+ * look at the next generation.
527
+ *
528
+ * @param generation The generation that ran the unit.
529
+ */
530
+ drainDeferrals_(generation) {
531
+ const deferrals = generation.takeDeferrals?.();
532
+ if (deferrals === void 0) {
533
+ return;
534
+ }
535
+ for (const deferral of deferrals) {
536
+ ++this.deferredUnits_;
537
+ if (!deferral.onPlacement) {
538
+ continue;
539
+ }
540
+ ++this.deferredOnPlacement_;
541
+ if (this.deferredForRetry_.length < RETRY_QUEUE_MAX) {
542
+ this.deferredForRetry_.push(deferral.ordinal);
543
+ }
544
+ }
545
+ }
377
546
  /**
378
547
  * Ensure a generation with pending units: keep the current one while
379
548
  * it has work; otherwise snapshot a fresh prefix model once the index
@@ -383,10 +552,41 @@ export class StreamedPreviewChannel {
383
552
  */
384
553
  ensureGeneration_() {
385
554
  const active = this.generation_;
386
- if (active !== void 0 && this.hasPendingUnits_(active.generation)) {
555
+ const records = this.sink.topLevelCount;
556
+ // Rebuild on INDEX GROWTH, not on running out of units. The order of
557
+ // these two tests is the whole behaviour of the channel on a large file.
558
+ //
559
+ // It used to keep the current generation for as long as that generation
560
+ // still had an unattempted unit, and only then consider rebuilding. A
561
+ // generation snapshotted at FIRST_GENERATION_MIN_RECORDS already lists
562
+ // thousands of products, and a tick attempts a handful of them per
563
+ // cadence interval, so on anything large the list never emptied and the
564
+ // rebuild never fired — the store path's twin of this gate left DOWA
565
+ // extracting against a snapshot of its first 1024 records for all 9 s of
566
+ // its parse, which is why 100% of its attempts deferred and its first
567
+ // mesh landed at 98.3% of the file (conway#542).
568
+ //
569
+ // Preempting makes generations arrive on the index's schedule —
570
+ // O(log records) of them, GENERATION_GROWTH_FACTOR apart — and the units
571
+ // the old generation had not reached carry over untouched, because
572
+ // `unitOrdinal_` indexes a dense parse-order list that a longer snapshot
573
+ // only extends.
574
+ // Eligibility to rebuild at all — the original growth gate, unchanged.
575
+ const growthReady = records >= this.lastSnapshotRecords_ * GENERATION_GROWTH_FACTOR &&
576
+ records >= this.lastFailedSnapshotRecords_ * GENERATION_GROWTH_FACTOR;
577
+ // Whether to rebuild EARLY, without waiting for the current generation's
578
+ // units to run out. Only worth it for a generation that is actually
579
+ // deferring: a longer index cannot help one whose units extract fine,
580
+ // and preempting anyway costs the case that already works best — on PSB,
581
+ // which defers nothing, unconditional preemption pushed the store path's
582
+ // first mesh from 269 ms out to 495 ms. Units waiting for a retry (or,
583
+ // in retry mode, units that captured nothing) are precisely the evidence
584
+ // that more index is the missing ingredient.
585
+ const preempt = active !== void 0 && growthReady && this.hasDeferred_();
586
+ if (!preempt && active !== void 0 &&
587
+ this.hasPendingUnits_(active.generation)) {
387
588
  return true;
388
589
  }
389
- const records = this.sink.topLevelCount;
390
590
  if (records < this.firstGenerationMinRecords) {
391
591
  return false;
392
592
  }
@@ -419,10 +619,14 @@ export class StreamedPreviewChannel {
419
619
  // Prefix grew but cannot build yet — wait for more records.
420
620
  return false;
421
621
  }
622
+ // A generation that adds no new units is still worth building when
623
+ // deferred ones are waiting: what changed is the INDEX behind them,
624
+ // which is the only reason a retry could now succeed.
422
625
  const newGenerationPending = this.adapter.retryEmptyUnits === true ?
423
626
  generation.unitCount > 0 &&
424
627
  this.completedUnits_.size < generation.unitCount :
425
- generation.unitCount > this.unitOrdinal_;
628
+ generation.unitCount > this.unitOrdinal_ ||
629
+ this.deferredForRetry_.length > 0;
426
630
  if (!newGenerationPending) {
427
631
  // Nothing this prefix can add — free it and wait for more records.
428
632
  // (The active generation, if any, keeps its scan state untouched.)
@@ -436,9 +640,26 @@ export class StreamedPreviewChannel {
436
640
  }
437
641
  // Retry mode: a fresh generation re-runs every not-yet-captured unit.
438
642
  this.retryScan_ = 0;
439
- // The outgoing generation's instances are all captured (a
440
- // generation is only replaced once exhausted + captured) — free its
441
- // native scenes before adopting the new one.
643
+ this.emptyUnitsThisGeneration_ = 0;
644
+ // Deferrals of the outgoing generation become this one's retry queue —
645
+ // the whole point of building it early. Carry the OLD queue's
646
+ // unconsumed suffix forward too, not just this generation's fresh
647
+ // deferrals: preemption can fire mid-drain of retryQueue_ (a tick's
648
+ // attempt budget against an index that grew enough to trigger a
649
+ // rebuild before the queue emptied), and every un-popped entry is a
650
+ // unit whose ordinal unitOrdinal_ has already passed — replacing the
651
+ // queue outright, rather than concatenating, would strand them exactly
652
+ // the way abandoned deferrals were stranded before this fix existed
653
+ // (conway#542, codex round 1 on #543).
654
+ this.retryQueue_ = [
655
+ ...this.retryQueue_.slice(this.retryCursor_),
656
+ ...this.deferredForRetry_,
657
+ ].slice(0, RETRY_QUEUE_MAX);
658
+ this.retryCursor_ = 0;
659
+ this.deferredForRetry_ = [];
660
+ // The outgoing generation's instances are all captured (capture runs
661
+ // per tick, after the units it ran) — free its native scenes before
662
+ // adopting the new one.
442
663
  try {
443
664
  active?.generation.dispose();
444
665
  }
@@ -451,10 +672,25 @@ export class StreamedPreviewChannel {
451
672
  };
452
673
  return true;
453
674
  }
675
+ /**
676
+ * Whether the current generation has produced evidence that a longer
677
+ * index would help it — the preemption gate. Classic mode: units queued
678
+ * for a retry. Retry mode: units that ran and captured nothing, since
679
+ * that schema's adapter cannot classify a failure.
680
+ *
681
+ * @return {boolean} True when this generation is deferring.
682
+ */
683
+ hasDeferred_() {
684
+ if (this.adapter.retryEmptyUnits === true) {
685
+ return this.emptyUnitsThisGeneration_ > 0;
686
+ }
687
+ return this.deferredForRetry_.length > 0;
688
+ }
454
689
  /**
455
690
  * Does the given generation still have units this channel would run —
456
691
  * retry mode: not-yet-captured ordinals ahead of the scan; classic
457
- * mode: ordinals beyond the global forward-only cursor.
692
+ * mode: queued retries, or ordinals beyond the global forward-only
693
+ * cursor.
458
694
  *
459
695
  * @param generation The generation to check.
460
696
  * @return {boolean} True when a tick can make progress on it.
@@ -464,7 +700,8 @@ export class StreamedPreviewChannel {
464
700
  return this.retryScan_ < generation.unitCount &&
465
701
  this.completedUnits_.size < generation.unitCount;
466
702
  }
467
- return this.unitOrdinal_ < generation.unitCount;
703
+ return this.retryCursor_ < this.retryQueue_.length ||
704
+ this.unitOrdinal_ < generation.unitCount;
468
705
  }
469
706
  /**
470
707
  * Walk the current generation's scene and emit every not-yet-captured
@@ -547,7 +784,9 @@ export class StreamedPreviewChannel {
547
784
  this.emittedBytes_ +=
548
785
  (vertexData.length + indexData.length) * BYTES_PER_FLOAT;
549
786
  }
787
+ this.firstMeshMs_ ??= Date.now() - this.startedMs_;
550
788
  this.onMesh(payload);
789
+ ++this.meshesEmitted_;
551
790
  ++emitted;
552
791
  if (this.emittedBytes_ >= this.maxBytes) {
553
792
  return emitted;
@@ -566,11 +805,24 @@ export class StreamedPreviewChannel {
566
805
  return;
567
806
  }
568
807
  const active = this.generation_;
569
- if (this.unitOrdinal_ >= active.generation.unitCount) {
808
+ // The classic-mode pending test, spelled out rather than shared with
809
+ // hasPendingUnits_: this seam walks the unit list forward whatever
810
+ // the adapter's retry semantics are (ap214_streamed_open.test.ts
811
+ // drains a retryEmptyUnits adapter through it), and retry mode's
812
+ // `retryScan_` is advanced by ticks only, so borrowing that test here
813
+ // would never terminate.
814
+ if (this.retryCursor_ >= this.retryQueue_.length &&
815
+ this.unitOrdinal_ >= active.generation.unitCount) {
570
816
  return;
571
817
  }
572
- const executed = active.generation.runUnits(this.unitOrdinal_, 1);
573
- ++this.unitOrdinal_;
818
+ const isRetry = this.retryCursor_ < this.retryQueue_.length;
819
+ const ordinal = isRetry ?
820
+ this.retryQueue_[this.retryCursor_++] : this.unitOrdinal_++;
821
+ if (isRetry) {
822
+ ++this.retriedUnits_;
823
+ }
824
+ const executed = active.generation.runUnits(ordinal, 1);
825
+ this.drainDeferrals_(active.generation);
574
826
  if (executed > 0) {
575
827
  this.captureNewInstances_();
576
828
  ++this.emittedUnits_;