@blockcast/mmt-container 0.5.0-main.9c437a834da9 → 0.5.0-main.dd44e35d4e29

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.
@@ -13502,6 +13502,9 @@ var FECManager = class _FECManager {
13502
13502
  // Receiver-local first-accepted-symbol time per block, bounded like
13503
13503
  // #blockPathCounts (see blockAnchorAtMs).
13504
13504
  this.#blockAnchorsMs = /* @__PURE__ */ new Map();
13505
+ // The receipt time of the packet being fed, when it is not now: a datagram
13506
+ // held on a miss and admitted later (withReceiptTime).
13507
+ this.#receiptAtMs = void 0;
13505
13508
  // Exact catalog identity observed on accepted symbols for each FEC block.
13506
13509
  // Conflicting identities collapse to null so recovery fails closed.
13507
13510
  this.#blockCatalogTrackNames = /* @__PURE__ */ new Map();
@@ -13549,6 +13552,9 @@ var FECManager = class _FECManager {
13549
13552
  // Worker recovery emits all K source symbols. Track recovery once per block
13550
13553
  // while routing only ESI slots that were absent from the direct source path.
13551
13554
  this.#workerRecoveredBlocks = /* @__PURE__ */ new Set();
13555
+ // Worker-recovered blocks a recovered packet of which admission refused,
13556
+ // until the decode's final symbol (7.2.7: the block is unrecovered).
13557
+ this.#refusedRecoveries = /* @__PURE__ */ new Set();
13552
13558
  // How the multicast leg delivers symbols (see MulticastSymbolDelivery). The
13553
13559
  // provider declares it per transport join; absence keeps datagram semantics.
13554
13560
  this.#multicastSymbolDelivery = "datagram";
@@ -13687,6 +13693,11 @@ var FECManager = class _FECManager {
13687
13693
  /** A stream's SBN went backwards (or jumped implausibly): the source restarted. */
13688
13694
  this.onSourceDiscontinuity = null;
13689
13695
  // Worker-path callbacks — async recovery and stats from FecWorkerClient.
13696
+ /**
13697
+ * A recovered source symbol. Return false when admission refused the
13698
+ * packet it carries (bc-provenance v2, draft-ramadan-moq-multicast-01
13699
+ * 7.2.7): the block is then unrecovered and stays open to its FEC deadline.
13700
+ */
13690
13701
  this.onBlockRecovered = null;
13691
13702
  this.onFecStatsUpdate = null;
13692
13703
  }
@@ -13739,6 +13750,7 @@ var FECManager = class _FECManager {
13739
13750
  #seenSymbols;
13740
13751
  #blockPathCounts;
13741
13752
  #blockAnchorsMs;
13753
+ #receiptAtMs;
13742
13754
  #blockCatalogTrackNames;
13743
13755
  #startupAlignedPackets;
13744
13756
  #startupPartialBlocks;
@@ -13751,6 +13763,7 @@ var FECManager = class _FECManager {
13751
13763
  #repairDeadlines;
13752
13764
  #repairDeadlineAtMs;
13753
13765
  #workerRecoveredBlocks;
13766
+ #refusedRecoveries;
13754
13767
  #multicastSymbolDelivery;
13755
13768
  #orderedFrontiers;
13756
13769
  #orderedPendingBlocks;
@@ -13890,6 +13903,7 @@ var FECManager = class _FECManager {
13890
13903
  this.#completedBlocks.delete(record.key);
13891
13904
  this.#cleanlyCompletedBlocks.delete(record.key);
13892
13905
  this.#workerRecoveredBlocks.delete(record.key);
13906
+ this.#refusedRecoveries.delete(record.key);
13893
13907
  this.#failedBlocks.delete(record.key);
13894
13908
  this.#resolvedBlockClasses.delete(record.key);
13895
13909
  this.#repairedBlocks.delete(record.key);
@@ -14228,7 +14242,7 @@ var FECManager = class _FECManager {
14228
14242
  * remainder, so repeated source progress costs no timer operation.
14229
14243
  */
14230
14244
  #armRepairDeadline(key, sbn, packetId, catalogTrackName, delayMs) {
14231
- const deadlineMs = performance.now() + delayMs;
14245
+ const deadlineMs = this.#notEarlierUnderReceipt(this.#receiptNow() + delayMs, this.#repairDeadlineAtMs.get(key));
14232
14246
  this.#repairDeadlineAtMs.set(key, deadlineMs);
14233
14247
  const armed = this.#repairDeadlines.get(key);
14234
14248
  if (armed?.handle != null) {
@@ -14248,7 +14262,7 @@ var FECManager = class _FECManager {
14248
14262
  timer.handle = null;
14249
14263
  this.#requestRepair(sbn, packetId, catalogTrackName);
14250
14264
  };
14251
- timer.handle = setTimeout(onFire, delayMs);
14265
+ timer.handle = setTimeout(onFire, Math.max(0, deadlineMs - performance.now()));
14252
14266
  this.#repairDeadlines.set(key, timer);
14253
14267
  }
14254
14268
  #requestRepair(sbn, packetId, catalogTrackName, settledByOrder = false) {
@@ -14652,7 +14666,10 @@ var FECManager = class _FECManager {
14652
14666
  }
14653
14667
  #armBlockTimeout(sbn, packetId, catalogTrackName, delayMs) {
14654
14668
  const key = this.#blockKey(sbn, packetId, catalogTrackName);
14655
- const waitAtMs = performance.now() + Math.max(1, Math.ceil(delayMs));
14669
+ const waitAtMs = this.#notEarlierUnderReceipt(
14670
+ this.#receiptNow() + Math.max(1, Math.ceil(delayMs)),
14671
+ this.#blockWaitDeadlinesMs.get(key)
14672
+ );
14656
14673
  const holdAtMs = this.#repairHoldUntilMs.get(key);
14657
14674
  this.#blockWaitDeadlinesMs.set(key, holdAtMs === void 0 ? waitAtMs : Math.min(waitAtMs, holdAtMs));
14658
14675
  this.#applyBlockDeadline(key, sbn, packetId, catalogTrackName);
@@ -15041,6 +15058,7 @@ var FECManager = class _FECManager {
15041
15058
  resetSet(this.#cleanlyCompletedBlocks);
15042
15059
  resetSet(this.#repairRequestedBlocks);
15043
15060
  resetSet(this.#workerRecoveredBlocks);
15061
+ resetSet(this.#refusedRecoveries);
15044
15062
  resetSet(this.#startupAlignedPackets);
15045
15063
  resetSet(this.#startupPartialBlocks);
15046
15064
  resetSet(this.#startupRepairReadyPackets);
@@ -15167,6 +15185,7 @@ var FECManager = class _FECManager {
15167
15185
  dropSet(this.#cleanlyCompletedBlocks);
15168
15186
  dropSet(this.#repairRequestedBlocks);
15169
15187
  dropSet(this.#workerRecoveredBlocks);
15188
+ dropSet(this.#refusedRecoveries);
15170
15189
  dropSet(this.#startupAlignedPackets);
15171
15190
  dropSet(this.#startupPartialBlocks);
15172
15191
  dropSet(this.#startupRepairReadyPackets);
@@ -15280,7 +15299,9 @@ var FECManager = class _FECManager {
15280
15299
  moqSymbols: 0,
15281
15300
  complete: false
15282
15301
  };
15283
- if (!this.#blockAnchorsMs.has(blockKey)) this.#blockAnchorsMs.set(blockKey, performance.now());
15302
+ const receivedAtMs = this.#receiptNow();
15303
+ const anchorMs = this.#blockAnchorsMs.get(blockKey);
15304
+ if (anchorMs === void 0 || receivedAtMs < anchorMs) this.#blockAnchorsMs.set(blockKey, receivedAtMs);
15284
15305
  if (source === "multicast") counts.multicastSymbols++;
15285
15306
  else counts.moqSymbols++;
15286
15307
  this.#blockPathCounts.set(blockKey, counts);
@@ -15953,7 +15974,25 @@ var FECManager = class _FECManager {
15953
15974
  const blockKey = this.#blockKey(meta.sbn, packetId, recoveredCatalogTrackName);
15954
15975
  const cleanlyCompleted = this.#cleanlyCompletedBlocks.has(blockKey);
15955
15976
  if (!cleanlyCompleted) {
15956
- if (!this.#workerRecoveredBlocks.has(blockKey)) {
15977
+ const symbolKey = `${blockKey}:${esi}`;
15978
+ if (!this.#seenSymbols.has(symbolKey)) {
15979
+ this.#seenSymbols.add(symbolKey);
15980
+ const admitted = this.onBlockRecovered?.(
15981
+ meta.sbn,
15982
+ packetId,
15983
+ data,
15984
+ meta,
15985
+ recoveredCatalogTrackName
15986
+ );
15987
+ if (admitted === false) {
15988
+ this.#refusedRecoveries.add(blockKey);
15989
+ if (this.#refusedRecoveries.size > 1e3) dropOldest(this.#refusedRecoveries, 500);
15990
+ }
15991
+ }
15992
+ }
15993
+ const refused = esi >= meta.ssbLength - 1 && this.#refusedRecoveries.delete(blockKey);
15994
+ if (esi >= meta.ssbLength - 1 && !refused && !this.#completedBlocks.has(blockKey)) {
15995
+ if (!cleanlyCompleted && !this.#workerRecoveredBlocks.has(blockKey)) {
15957
15996
  this.#traceLifecycle(
15958
15997
  "recovered",
15959
15998
  meta.sbn,
@@ -15968,19 +16007,6 @@ var FECManager = class _FECManager {
15968
16007
  }
15969
16008
  this.#noteBlockRepaired(blockKey, meta.sbn, packetId, recoveredCatalogTrackName);
15970
16009
  }
15971
- const symbolKey = `${blockKey}:${esi}`;
15972
- if (!this.#seenSymbols.has(symbolKey)) {
15973
- this.#seenSymbols.add(symbolKey);
15974
- this.onBlockRecovered?.(
15975
- meta.sbn,
15976
- packetId,
15977
- data,
15978
- meta,
15979
- recoveredCatalogTrackName
15980
- );
15981
- }
15982
- }
15983
- if (esi >= meta.ssbLength - 1 && !this.#completedBlocks.has(blockKey)) {
15984
16010
  this.cancelBlockTimeout(meta.sbn, packetId, recoveredCatalogTrackName);
15985
16011
  if (recoveredCatalogTrackName === void 0) {
15986
16012
  this.onBlockComplete?.(meta.sbn, packetId);
@@ -16550,6 +16576,33 @@ var FECManager = class _FECManager {
16550
16576
  }
16551
16577
  queueMicrotask(() => this.#syncDecoderBlockOwners.delete(key));
16552
16578
  }
16579
+ /**
16580
+ * Run `fn` with `receivedAtMs` (performance.now() clock) as the receipt
16581
+ * time of what it feeds. Within a consumer the bc-provenance profile
16582
+ * governs, a datagram held on a miss and admitted later keeps its receipt
16583
+ * time (fec-01 9): it anchors its block's ts and arms the block's
16584
+ * deadlines from then, never later than they are already armed.
16585
+ */
16586
+ withReceiptTime(receivedAtMs, fn) {
16587
+ const outer = this.#receiptAtMs;
16588
+ this.#receiptAtMs = receivedAtMs;
16589
+ try {
16590
+ fn();
16591
+ } finally {
16592
+ this.#receiptAtMs = outer;
16593
+ }
16594
+ }
16595
+ #receiptNow() {
16596
+ return this.#receiptAtMs ?? performance.now();
16597
+ }
16598
+ /**
16599
+ * A held packet admitted late arms from its (earlier) receipt time; that
16600
+ * never pulls in a deadline later activity already armed. Outside a
16601
+ * receipt scope a re-arm replaces the deadline, as a narrower bound may.
16602
+ */
16603
+ #notEarlierUnderReceipt(deadlineMs, armedMs) {
16604
+ return this.#receiptAtMs === void 0 || armedMs === void 0 ? deadlineMs : Math.max(deadlineMs, armedMs);
16605
+ }
16553
16606
  /** Refresh deadlines after accepted block activity. */
16554
16607
  noteBlockActivity(sbn, packetId, catalogTrackName, sourceProgress = false) {
16555
16608
  if (!this.#trackConfigs.has(packetId)) return;
@@ -16728,6 +16781,7 @@ var FECManager = class _FECManager {
16728
16781
  this.#cleanlyCompletedBlocks.clear();
16729
16782
  this.#repairRequestedBlocks.clear();
16730
16783
  this.#workerRecoveredBlocks.clear();
16784
+ this.#refusedRecoveries.clear();
16731
16785
  this.#syncDecoderBlockOwners.clear();
16732
16786
  this.#startupAlignedPackets.clear();
16733
16787
  this.#startupPartialBlocks.clear();
@@ -19434,6 +19488,8 @@ var MmtpRouter = class {
19434
19488
  #unicastRepair = false;
19435
19489
  /** How to route a held unicast (MoQ) datagram once a record admits it. */
19436
19490
  #heldReplay = /* @__PURE__ */ new WeakMap();
19491
+ /** When a held datagram was received: admitted later, it keeps that receipt time (fec-01 9). */
19492
+ #heldAt = /* @__PURE__ */ new WeakMap();
19437
19493
  /** The flow whose receiver admitted a stream's source datagrams, by packetId then catalog track name: recovered packets answer to it. */
19438
19494
  #provenanceStreamFlow = /* @__PURE__ */ new Map();
19439
19495
  #unregisteredFlowDrops = 0;
@@ -20219,6 +20275,7 @@ var MmtpRouter = class {
20219
20275
  if (verdict === "admitted") return "admitted";
20220
20276
  if (verdict === "held") {
20221
20277
  this.#heldReplay.set(packet, replay);
20278
+ this.#heldAt.set(packet, performance.now());
20222
20279
  return "held";
20223
20280
  }
20224
20281
  this.#unadmittedListedDrops++;
@@ -20237,9 +20294,14 @@ var MmtpRouter = class {
20237
20294
  this.#noteFlowAdmitted(flow);
20238
20295
  this.#withDeferredFecPokes(() => {
20239
20296
  for (const datagram of result.admitted) {
20240
- const replay = this.#heldReplay.get(datagram);
20241
- if (replay) replay();
20242
- else this.#processMMTP(datagram, "multicast", true, this.#heldIngress.get(datagram) ?? entry.ingress, false, true);
20297
+ const route = () => {
20298
+ const replay = this.#heldReplay.get(datagram);
20299
+ if (replay) replay();
20300
+ else this.#processMMTP(datagram, "multicast", true, this.#heldIngress.get(datagram) ?? entry.ingress, false, true);
20301
+ };
20302
+ const heldAt = this.#heldAt.get(datagram);
20303
+ if (heldAt !== void 0 && this.#fecManager?.withReceiptTime) this.#fecManager.withReceiptTime(heldAt, route);
20304
+ else route();
20243
20305
  }
20244
20306
  });
20245
20307
  }
@@ -20490,10 +20552,11 @@ var MmtpRouter = class {
20490
20552
  catalogTrackName
20491
20553
  );
20492
20554
  if (recovered && !alreadyCompleted) {
20493
- fec.cancelBlockTimeout(blockStart, ownerPacketId, catalogTrackName);
20494
- fec.countRecovery(ownerPacketId, blockStart, catalogTrackName);
20495
- fec.markBlockCompleted(blockStart, ownerPacketId, catalogTrackName);
20496
- this.#routeRecoveredBlock(blockStart, recovered, ownerPacketId, catalogTrackName);
20555
+ this.#routeRecoveredBlock(blockStart, recovered, ownerPacketId, catalogTrackName, () => {
20556
+ fec.cancelBlockTimeout(blockStart, ownerPacketId, catalogTrackName);
20557
+ fec.countRecovery(ownerPacketId, blockStart, catalogTrackName);
20558
+ fec.markBlockCompleted(blockStart, ownerPacketId, catalogTrackName);
20559
+ });
20497
20560
  }
20498
20561
  }
20499
20562
  /**
@@ -20534,10 +20597,11 @@ var MmtpRouter = class {
20534
20597
  const alreadyCompleted = fec.isBlockCompleted(blockStart, ownerPacketId, catalogTrackName);
20535
20598
  const recovered = fec.feedSourceSymbol(ownerPacketId, ssId, payload.symbolData, catalogTrackName);
20536
20599
  if (!recovered || alreadyCompleted) continue;
20537
- fec.cancelBlockTimeout(blockStart, ownerPacketId, catalogTrackName);
20538
- fec.countRecovery(ownerPacketId, blockStart, catalogTrackName);
20539
- fec.markBlockCompleted(blockStart, ownerPacketId, catalogTrackName);
20540
- this.#routeRecoveredBlock(blockStart, recovered, ownerPacketId, catalogTrackName);
20600
+ this.#routeRecoveredBlock(blockStart, recovered, ownerPacketId, catalogTrackName, () => {
20601
+ fec.cancelBlockTimeout(blockStart, ownerPacketId, catalogTrackName);
20602
+ fec.countRecovery(ownerPacketId, blockStart, catalogTrackName);
20603
+ fec.markBlockCompleted(blockStart, ownerPacketId, catalogTrackName);
20604
+ });
20541
20605
  }
20542
20606
  }
20543
20607
  #resolveRepairOwnerPacketId(packetId, ingress) {
@@ -20560,14 +20624,18 @@ var MmtpRouter = class {
20560
20624
  * Sync WASM decoders return the block's K' = SSB_length fixed-width
20561
20625
  * ssbg_mode1 source symbols concatenated. */
20562
20626
  routeRecoveredBlock(blockId, data, packetId, catalogTrackName) {
20563
- this.#withDeferredFecPokes(() => this.#routeRecoveredBlock(blockId, data, packetId, catalogTrackName));
20627
+ let recovered = false;
20628
+ this.#withDeferredFecPokes(() => {
20629
+ recovered = this.#routeRecoveredBlock(blockId, data, packetId, catalogTrackName);
20630
+ });
20631
+ return recovered;
20564
20632
  }
20565
20633
  /** Route a single recovered source symbol through the parser + dedup pipeline.
20566
20634
  * Worker-backed decoders emit one padded source symbol per frame event. */
20567
20635
  routeRecoveredSymbol(blockId, data, packetId, esi = 0, catalogTrackName) {
20568
20636
  const blockProfile = this.#blockProfileFor(packetId);
20569
20637
  const result = processFecSymbol(data, { ownerPacketId: packetId, index: esi, blockProfile });
20570
- this.#routeVerifiedFecSymbols(blockId, result, packetId, catalogTrackName);
20638
+ return this.#routeVerifiedFecSymbols(blockId, result, packetId, catalogTrackName);
20571
20639
  }
20572
20640
  // ── Keyframe timer cancellation ─────────────────────────────────
20573
20641
  /** Cancel the active keyframe loss timer.
@@ -21239,7 +21307,10 @@ var MmtpRouter = class {
21239
21307
  if (verdict === "admitted") {
21240
21308
  this.#noteFlowAdmitted(ingress.flow);
21241
21309
  this.#processMMTP(packet, source, true, ingress, overlapCopy);
21242
- } else if (verdict === "held") this.#heldIngress.set(packet, ingress);
21310
+ } else if (verdict === "held") {
21311
+ this.#heldIngress.set(packet, ingress);
21312
+ this.#heldAt.set(packet, performance.now());
21313
+ }
21243
21314
  return;
21244
21315
  }
21245
21316
  if (source === "moq" && !manifestAdmitted) {
@@ -21422,36 +21493,22 @@ var MmtpRouter = class {
21422
21493
  sourceFec.symbolData,
21423
21494
  catalogTrackName
21424
21495
  );
21425
- if (recovered) {
21426
- if (!alreadyCompleted && this.#classifyPacketId(info.packetId) !== null) {
21496
+ if (recovered && !alreadyCompleted) {
21497
+ const fec = this.#fecManager;
21498
+ const sbn = sourceFec.sbn;
21499
+ const packetId = info.packetId;
21500
+ const tracked = this.#classifyPacketId(packetId) !== null;
21501
+ this.#routeRecoveredBlock(sbn, recovered, packetId, catalogTrackName, () => {
21427
21502
  if (catalogTrackName === void 0) {
21428
- this.#fecManager.cancelBlockTimeout(sourceFec.sbn, info.packetId);
21503
+ if (tracked) fec.cancelBlockTimeout(sbn, packetId);
21504
+ fec.countRecovery(packetId, sbn, catalogTrackName);
21505
+ fec.markBlockCompleted(sbn, packetId);
21429
21506
  } else {
21430
- this.#fecManager.cancelBlockTimeout(
21431
- sourceFec.sbn,
21432
- info.packetId,
21433
- catalogTrackName
21434
- );
21507
+ if (tracked) fec.cancelBlockTimeout(sbn, packetId, catalogTrackName);
21508
+ fec.countRecovery(packetId, sbn, catalogTrackName);
21509
+ fec.markBlockCompleted(sbn, packetId, catalogTrackName);
21435
21510
  }
21436
- }
21437
- if (!alreadyCompleted) {
21438
- this.#fecManager.countRecovery(info.packetId, sourceFec.sbn, catalogTrackName);
21439
- if (catalogTrackName === void 0) {
21440
- this.#fecManager.markBlockCompleted(sourceFec.sbn, info.packetId);
21441
- } else {
21442
- this.#fecManager.markBlockCompleted(
21443
- sourceFec.sbn,
21444
- info.packetId,
21445
- catalogTrackName
21446
- );
21447
- }
21448
- this.#routeRecoveredBlock(
21449
- sourceFec.sbn,
21450
- recovered,
21451
- info.packetId,
21452
- catalogTrackName
21453
- );
21454
- }
21511
+ });
21455
21512
  }
21456
21513
  }
21457
21514
  }
@@ -21827,7 +21884,7 @@ var MmtpRouter = class {
21827
21884
  }
21828
21885
  // ── Private: FEC recovery routing ───────────────────────────────
21829
21886
  /** Route WASM-recovered block through appropriate path based on container kind. */
21830
- #routeRecoveredBlock(blockId, recovered, ownerPacketId, catalogTrackName) {
21887
+ #routeRecoveredBlock(blockId, recovered, ownerPacketId, catalogTrackName, complete) {
21831
21888
  const packetSymbolSize = this.#symbolSizeForPacket(ownerPacketId);
21832
21889
  if (ownerPacketId === void 0) {
21833
21890
  throw new Error("[MmtpRouter] a recovered FEC block needs its owner packetId to read its fec.blockProfile");
@@ -21844,31 +21901,46 @@ var MmtpRouter = class {
21844
21901
  ownerPacketId,
21845
21902
  blockProfile
21846
21903
  });
21847
- this.#routeVerifiedFecSymbols(blockId, result, ownerPacketId, catalogTrackName);
21904
+ return this.#routeVerifiedFecSymbols(blockId, result, ownerPacketId, catalogTrackName, complete);
21848
21905
  }
21849
21906
  /**
21850
21907
  * A recovered source packet of a protected flow is admitted on its own
21851
21908
  * digest: its bytes with the SS_ID it was sent with (SS_Start + ESI)
21852
21909
  * appended are the datagram the record listed (7.2.7). One that no record
21853
21910
  * lists is dropped; holding it would let it out later as a multicast arrival.
21911
+ * Returns whether the decode recovered the block: every packet it yields
21912
+ * that this router has not parsed already is admitted. A block one of whose
21913
+ * recovered packets is refused is unrecovered (7.2.7): its caller leaves it
21914
+ * open, so it reaches its FEC deadline and fails by class. Always true
21915
+ * outside a protected flow.
21854
21916
  */
21855
- #routeVerifiedFecSymbols(blockId, result, ownerPacketId, catalogTrackName) {
21917
+ #routeVerifiedFecSymbols(blockId, result, ownerPacketId, catalogTrackName, complete) {
21856
21918
  const receiver = ownerPacketId == null ? void 0 : this.#provenanceFor(ownerPacketId, catalogTrackName);
21857
- if (receiver === "ambiguous") {
21858
- this.#ambiguousRecoveredDrops += result.symbols.length;
21859
- result = { ...result, symbols: [] };
21860
- } else if (receiver) {
21919
+ let recovered = true;
21920
+ if (receiver) {
21921
+ const parsed = this.#stream(ownerPacketId, catalogTrackName).duplicates.parsedSymbols;
21861
21922
  result = {
21862
21923
  ...result,
21863
21924
  symbols: result.symbols.filter((sym) => {
21864
- const wire = new Uint8Array(sym.rawSymbol.byteLength + 4);
21865
- wire.set(sym.rawSymbol);
21866
- new DataView(wire.buffer).setUint32(sym.rawSymbol.byteLength, this.#fecAnchorForRecovered(blockId, sym.index));
21867
- return receiver.admitRecovered(wire);
21925
+ const ssId = this.#fecAnchorForRecovered(blockId, sym.index);
21926
+ if (parsed.has(ssId)) return false;
21927
+ let admitted = false;
21928
+ if (receiver === "ambiguous") {
21929
+ this.#ambiguousRecoveredDrops++;
21930
+ } else {
21931
+ const wire = new Uint8Array(sym.rawSymbol.byteLength + 4);
21932
+ wire.set(sym.rawSymbol);
21933
+ new DataView(wire.buffer).setUint32(sym.rawSymbol.byteLength, ssId);
21934
+ admitted = receiver.admitRecovered(wire);
21935
+ }
21936
+ if (!admitted) recovered = false;
21937
+ return admitted;
21868
21938
  })
21869
21939
  };
21870
21940
  }
21941
+ if (recovered) complete?.();
21871
21942
  this.#routeProcessedFecSymbols(blockId, result, ownerPacketId, catalogTrackName);
21943
+ return recovered;
21872
21944
  }
21873
21945
  /**
21874
21946
  * The receiver a recovered packet of `packetId` answers to: the flow that
@@ -26928,17 +27000,7 @@ var MulticastManager = class {
26928
27000
  this.#fec.onBlockRecovered = (sbn, packetId, data, meta, recoveredTrackName) => {
26929
27001
  const recovered = new Uint8Array(data);
26930
27002
  const catalogTrackName = recoveredTrackName ?? this.#fec.catalogTrackNameForBlock(sbn, packetId);
26931
- if (catalogTrackName === void 0) {
26932
- this.#router.routeRecoveredSymbol(sbn, recovered, packetId, meta.esi);
26933
- } else {
26934
- this.#router.routeRecoveredSymbol(
26935
- sbn,
26936
- recovered,
26937
- packetId,
26938
- meta.esi,
26939
- catalogTrackName
26940
- );
26941
- }
27003
+ return catalogTrackName === void 0 ? this.#router.routeRecoveredSymbol(sbn, recovered, packetId, meta.esi) : this.#router.routeRecoveredSymbol(sbn, recovered, packetId, meta.esi, catalogTrackName);
26942
27004
  };
26943
27005
  this.#fec.onRepairResolved = (sbn, packetId, _outcome, catalogTrackName) => {
26944
27006
  this.clearRepairForBlock(sbn, packetId, catalogTrackName);
@@ -27916,11 +27978,11 @@ var MulticastManager = class {
27916
27978
  }
27917
27979
  /** Route a Worker-recovered FEC block through the MMTP parser + dedup pipeline. */
27918
27980
  routeRecoveredBlock(blockId, data, packetId, catalogTrackName) {
27919
- this.#router.routeRecoveredBlock(blockId, data, packetId, catalogTrackName);
27981
+ return this.#router.routeRecoveredBlock(blockId, data, packetId, catalogTrackName);
27920
27982
  }
27921
27983
  /** Route one Worker-recovered source symbol through the MMTP parser + dedup pipeline. */
27922
27984
  routeRecoveredSymbol(blockId, data, packetId, esi = 0, catalogTrackName) {
27923
- this.#router.routeRecoveredSymbol(blockId, data, packetId, esi, catalogTrackName);
27985
+ return this.#router.routeRecoveredSymbol(blockId, data, packetId, esi, catalogTrackName);
27924
27986
  }
27925
27987
  // ---------------------------------------------------------------------------
27926
27988
  // Phase 14: multi-track catalog ingest (D-04, D-06, D-07, D-17, D-26, D-28)