@torrent-tv/proxy 2.80.5 → 2.80.7

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 (34) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/package.json +1 -1
  3. package/routes/api/delivery-sink/get.js +8 -4
  4. package/server.js +415 -403
  5. package/services/data-channel-handler.js +87 -25
  6. package/services/delivery-probe.js +38 -5
  7. package/services/encode/CoverageMap.js +77 -4
  8. package/services/encode/EncodePlan.js +1025 -358
  9. package/services/encode/EncodeRun.js +19 -1
  10. package/services/encode/SegmentDemand.js +0 -0
  11. package/services/encode/SegmentStore.js +53 -1
  12. package/services/encode/open-piece.js +22 -1
  13. package/services/encode/run-command.js +12 -2
  14. package/services/hls-session-manager.js +38 -158
  15. package/services/hwaccel.js +182 -54
  16. package/services/orchestrators/EncodeOrchestrator.js +118 -89
  17. package/services/output/LiveOutputs.js +233 -213
  18. package/services/output/Timeline.js +333 -256
  19. package/services/piece-store/shared-piece-store.js +13 -9
  20. package/services/priority/PriorityMap.js +262 -108
  21. package/services/priority/PriorityOrchestrator.js +31 -6
  22. package/services/quality/EncodeCost.js +555 -500
  23. package/services/torrent-pool.js +9 -4
  24. package/test/encode-orchestrator.test.js +195 -65
  25. package/test/encode-plan-viewers.test.js +719 -0
  26. package/test/encode-plan.test.js +174 -81
  27. package/test/open-piece.test.js +40 -0
  28. package/test/output-speed.test.js +86 -0
  29. package/test/piece-store-eviction.test.js +26 -0
  30. package/test/priority-map-download.test.js +25 -7
  31. package/test/priority-map.test.js +134 -83
  32. package/test/seek-landing.test.js +109 -76
  33. package/test/segment-demand.test.js +54 -56
  34. package/test/wedge-certainty.test.js +3 -3
@@ -12,6 +12,22 @@ import assert from "node:assert/strict";
12
12
  import { CoverageMap } from "../services/encode/CoverageMap.js";
13
13
  import { endOfRun } from "../services/encode/EncodeRun.js";
14
14
  import { firstUnmetWant, planEncoders } from "../services/encode/EncodePlan.js";
15
+ import { contentionPenalty, penaltiesFrom } from "../services/contention.js";
16
+
17
+ // WHAT A SECOND ENCODER COSTS THE FIRST — measured, never a formula.
18
+ //
19
+ // Addon host, 2026-09-03: 854x480 through libx264 `ultrafast` ran at 7.12x with
20
+ // the machine to itself, and at 4.20x and 4.16x when two ran at once. The
21
+ // penalty is read off that reading by the SAME two functions production uses,
22
+ // so nothing here invents a shape: beyond what was measured the reading is held
23
+ // rather than extrapolated.
24
+ const MEASURED_PENALTIES = penaltiesFrom(7.12, [{ others: 1, speed: 4.18 }]);
25
+ const penaltyFor = (others) => contentionPenalty(others, MEASURED_PENALTIES).penalty;
26
+
27
+ // What a start and a stop cost, measured on the same host: a spawn with its
28
+ // input open is 0.12 s there.
29
+ const RUN_COSTS = { killCostSec: 0, firstByteWaitSec: 0.12 };
30
+
15
31
 
16
32
  /** A host that can afford two encoders, four-second segments, a cheap restart. */
17
33
  // A host that has measured itself: the start and the death from its own runs,
@@ -20,9 +36,15 @@ import { firstUnmetWant, planEncoders } from "../services/encode/EncodePlan.js";
20
36
  // host missing any of them keeps its encoders instead — which is its own check
21
37
  // below rather than the shape every other check is written against.
22
38
  const HOST = {
39
+ // Measured on the addon host: a second encoder beside the first costs about
40
+ // half its speed. Without it an extra process is free and the score always
41
+ // wants more of them.
42
+ contentionPenaltyFor: penaltyFor,
43
+ // What the startup benchmark says this host encodes at, in realtimes. It is
44
+ // measured before any viewer exists, so the plan always has a speed.
45
+ speedX: 2,
23
46
  maxRuns: 2,
24
47
  segmentSeconds: 4,
25
- restartCostSec: 0.12,
26
48
  killCostSec: 0.5,
27
49
  firstByteWaitSec: 1,
28
50
  refetchSecPerFilmSecond: 0.25
@@ -57,10 +79,18 @@ test("a viewer waiting on nothing made starts one encoder, at what they are wait
57
79
  );
58
80
  });
59
81
 
60
- test("a viewer whose cushion reaches past a running encoder starts nothing", () => {
61
- // Found by the orchestrator's own checks: with the end taken from the window,
62
- // a second viewer two segments further on was given an encoder of their own
63
- // to make those two, while the run already there had nowhere left to go.
82
+ test("two viewers a couple of numbers apart are never given the same stretch twice", () => {
83
+ // Found by the orchestrator's own checks. Under the old design the second
84
+ // viewer was given an encoder of their own while the run already there had
85
+ // nowhere left to go two processes side by side for one stretch of film.
86
+ //
87
+ // The model answers it by arithmetic instead of by a rule: the number the
88
+ // second viewer stands on is due now and the encoder behind them needs six
89
+ // seconds, so it IS late; a new one delivers it in two, so placing one is
90
+ // worth it; and the one behind is then bounded to the two numbers it can still
91
+ // make and retires. One encoder, and the second viewer waits four seconds
92
+ // less. What must never happen — two of them running the same stretch — does
93
+ // not.
64
94
  const coverage = new CoverageMap({ segmentCount: 1000 });
65
95
  const runA = run({ from: 100, to: 999, head: 100 });
66
96
  coverage.claim(runA, 100, 999);
@@ -70,7 +100,20 @@ test("a viewer whose cushion reaches past a running encoder starts nothing", ()
70
100
  runs: [runA],
71
101
  ...HOST
72
102
  });
73
- assert.equal(actions.some((action) => action.type === "start"), false);
103
+ const spans = actions
104
+ .filter((action) => action.type !== "stop")
105
+ .map((action) => [action.from, action.to])
106
+ .sort((left, right) => left[0] - right[0]);
107
+ for (let index = 0; index < spans.length - 1; index += 1) {
108
+ assert.ok(spans[index][1] < spans[index + 1][0],
109
+ `#${spans[index][0]}..#${spans[index][1]} overlaps #${spans[index + 1][0]}`);
110
+ }
111
+ assert.ok(spans.some(([from, to]) => from <= 102 && (to < from || to >= 102)),
112
+ "somebody is making what the second viewer needs");
113
+ // How MANY is the score's answer and not a rule: with these two standing two
114
+ // numbers apart, a second process makes the one in front wait 1.4 s longer and
115
+ // the one behind 2.5 s less, so the pair is better off. What is fixed is that
116
+ // they never share a stretch, which the loop above asserts.
74
117
  });
75
118
 
76
119
  test("a viewer whose whole window is already made starts nothing", () => {
@@ -95,7 +138,11 @@ test("a run is given an end at the edge of what is free", () => {
95
138
  assert.deepEqual({ from: actions[0].from, to: actions[0].to }, { from: 40, to: 49 });
96
139
  });
97
140
 
98
- test("a run that has caught up with made material is moved forward, not killed", () => {
141
+ test("a run that has caught up with made material is not killed for it", () => {
142
+ // It has arrived at film somebody else made. Killing it was the old answer and
143
+ // it is never the right one: the work is either taken past the made stretch or
144
+ // left to drive through, and which of those depends on the score, but the
145
+ // encoder goes on existing either way.
99
146
  const coverage = new CoverageMap({ segmentCount: 100 });
100
147
  const runA = run({ head: 10, from: 0, to: 100 });
101
148
  coverage.claim(runA, 0, 100);
@@ -108,11 +155,19 @@ test("a run that has caught up with made material is moved forward, not killed",
108
155
  runs: [runA],
109
156
  ...HOST
110
157
  });
111
- const move = actions.find((action) => action.type === "move");
112
- assert.ok(move, "it should have been moved");
113
- assert.equal(move.run, runA);
114
- assert.equal(move.from, 31, "to the first thing nobody has");
115
- assert.equal(actions.some((action) => action.type === "stop"), false, "and not stopped");
158
+ assert.equal(actions.some((action) => action.type === "stop"), false, "not killed");
159
+ assert.ok(
160
+ actions.some((action) => (action.type === "keep" || action.type === "move") && action.run === runA),
161
+ "it is still one of the encoders on this output"
162
+ );
163
+ // And nothing is arranged so that two of them make the same piece.
164
+ const spans = actions
165
+ .filter((action) => action.type !== "stop")
166
+ .map((action) => [action.from, action.to < action.from ? Number.POSITIVE_INFINITY : action.to])
167
+ .sort((left, right) => left[0] - right[0]);
168
+ for (let index = 0; index < spans.length - 1; index += 1) {
169
+ assert.ok(spans[index][1] < spans[index + 1][0], "the stretches do not overlap");
170
+ }
116
171
  });
117
172
 
118
173
  test("a covered stretch shorter than a restart is driven through instead", () => {
@@ -125,7 +180,9 @@ test("a covered stretch shorter than a restart is driven through instead", () =>
125
180
  coverage.markReady(10);
126
181
  const actions = planEncoders({
127
182
  coverage,
128
- windows: [{ from: 0, to: 90 }],
183
+ // From where the run stands, so the only question asked is the covered piece
184
+ // under it. Beginning at #0 would also be asking who makes #0..#9.
185
+ windows: [{ from: 10, to: 90 }],
129
186
  runs: [runA],
130
187
  ...HOST
131
188
  });
@@ -133,20 +190,21 @@ test("a covered stretch shorter than a restart is driven through instead", () =>
133
190
  assert.ok(actions.some((action) => action.type === "keep" && action.run === runA));
134
191
  });
135
192
 
136
- test("a run whose speed nothing has measured is kept, not taken away", () => {
137
- // Moving costs a known amount for an unknown gain, and a run nothing has
138
- // measured has produced nothing yet so taking its work away is certainly a
139
- // loss and the comparison cannot be made. It used to answer the other way,
140
- // and then every just-started run was moved the moment anything ahead of it
141
- // was covered, which, once the film ahead had been made, was always: 684
142
- // starts in 482 seconds in the field on 2026-09-05.
193
+ test("a run standing on film nobody has made is never taken away", () => {
194
+ // The field failure this guards: 684 starts in 482 seconds on 2026-09-05,
195
+ // because a run was moved whenever anything ahead of it had been made.
196
+ //
197
+ // Moving is priced rather than forbidden a run standing ON made film may
198
+ // well be worth restarting one number along, and the two checks above measure
199
+ // both sides of that. What can never be worth it is moving a run that has
200
+ // nothing made under it or ahead of it: there is no work to skip, so the move
201
+ // buys nothing and costs a start.
143
202
  const coverage = new CoverageMap({ segmentCount: 100 });
144
- const runA = run({ head: 10, speedX: 0 });
203
+ const runA = run({ head: 10, speedX: 2 });
145
204
  coverage.claim(runA, 0, 100);
146
- coverage.markReady(10);
147
205
  const actions = planEncoders({
148
206
  coverage,
149
- windows: [{ from: 0, to: 90 }],
207
+ windows: [{ from: 10, to: 90 }],
150
208
  runs: [runA],
151
209
  ...HOST
152
210
  });
@@ -154,11 +212,13 @@ test("a run whose speed nothing has measured is kept, not taken away", () => {
154
212
  assert.ok(actions.some((action) => action.type === "keep" && action.run === runA));
155
213
  });
156
214
 
157
- test("both sides of the move are counted, not just the encoder's own time", () => {
158
- // Driving through costs this run's encode time AND the swarm the same bytes a
159
- // second time; moving costs the death, the start and the wait for the first
160
- // bytes. Here driving is dear enough to lose: 20 covered segments of 4 s at
161
- // 1x is 80 s of encoding, against a move priced at 0.12 + 0.5 + 3 seconds.
215
+ test("a long stretch of made film is skipped, and the swarm's price is in the reckoning", () => {
216
+ // Driving through costs this encoder's time AND the swarm the same bytes a
217
+ // second time. Twenty made pieces of 4 s at 1x is 80 s of encoding plus 20 s
218
+ // of fetching, against a move priced at 0.12 + 0.5 + 3 seconds so it skips.
219
+ //
220
+ // There is no separate comparison to read here: both are seconds, both are in
221
+ // the one score, and the arrangement with the smaller total is the one taken.
162
222
  const coverage = new CoverageMap({ segmentCount: 200 });
163
223
  const runA = run({ head: 10, speedX: 1 });
164
224
  coverage.claim(runA, 0, 200);
@@ -167,17 +227,25 @@ test("both sides of the move are counted, not just the encoder's own time", () =
167
227
  }
168
228
  const actions = planEncoders({
169
229
  coverage,
170
- windows: [{ from: 0, to: 190 }],
230
+ // The viewer is PAST the made stretch, so the only question is this encoder:
231
+ // drive through twenty pieces that exist, or skip them. With a viewer at #0
232
+ // as well, something has to cross that stretch whatever happens, and then
233
+ // moving buys nothing — which the score says too, and is why the window
234
+ // starts where the viewer actually is.
235
+ windows: [{ from: 30, to: 190 }],
171
236
  runs: [runA],
172
237
  ...HOST,
238
+ // One encoder, so the question is only about THIS one: drive through the
239
+ // twenty pieces that exist, or skip them. With room for a second, the
240
+ // machine simply buys one and the question never arises.
241
+ maxRuns: 1,
173
242
  killCostSec: 0.5,
174
243
  firstByteWaitSec: 3,
175
244
  refetchSecPerFilmSecond: 0.25
176
245
  });
177
246
  const move = actions.find((action) => action.type === "move");
178
- assert.ok(move);
179
- assert.match(move.because, /refetch 20\.00s/);
180
- assert.match(move.because, /against 3\.62s to move/);
247
+ assert.ok(move, "it is taken past the made film rather than left to make it again");
248
+ assert.equal(move.from, 30, "to the first thing nobody has");
181
249
  });
182
250
 
183
251
  test("a short covered stretch is driven through rather than paid a restart for", () => {
@@ -190,7 +258,9 @@ test("a short covered stretch is driven through rather than paid a restart for",
190
258
  coverage.markReady(10);
191
259
  const actions = planEncoders({
192
260
  coverage,
193
- windows: [{ from: 0, to: 190 }],
261
+ // From where the run stands, so the only question is the covered piece under
262
+ // it. A window starting at #0 would also be asking who makes #0..#9.
263
+ windows: [{ from: 10, to: 190 }],
194
264
  runs: [runA],
195
265
  ...HOST,
196
266
  killCostSec: 0.5,
@@ -201,11 +271,15 @@ test("a short covered stretch is driven through rather than paid a restart for",
201
271
  assert.ok(actions.some((action) => action.type === "keep" && action.run === runA));
202
272
  });
203
273
 
204
- test("a run with nothing left to make ahead of it is stopped", () => {
274
+ test("a run with nothing left ahead of it does not go on making nothing", () => {
275
+ // Everything from #10 to the end exists. The encoder standing at #10 has
276
+ // nothing to do there; whether it is stopped or taken back to the film before
277
+ // #0 that nobody has made is the score's answer, and both are right answers.
278
+ // What must not happen is that it stays where it is, producing nothing.
205
279
  const coverage = new CoverageMap({ segmentCount: 100 });
206
280
  const runA = run({ head: 10 });
207
281
  coverage.claim(runA, 0, 100);
208
- for (let index = 10; index <= 90; index += 1) {
282
+ for (let index = 10; index < 100; index += 1) {
209
283
  coverage.markReady(index);
210
284
  }
211
285
  const actions = planEncoders({
@@ -214,10 +288,12 @@ test("a run with nothing left to make ahead of it is stopped", () => {
214
288
  runs: [runA],
215
289
  ...HOST
216
290
  });
217
- assert.deepEqual(
218
- actions.map((action) => action.type),
219
- ["stop"]
220
- );
291
+ const kept = actions.find((action) => action.type === "keep" && action.run === runA);
292
+ assert.equal(kept, undefined, "it is not left standing on film that already exists");
293
+ const madeAgain = actions.filter((action) => action.type !== "stop");
294
+ for (const action of madeAgain) {
295
+ assert.ok(action.from < 10, "and whatever is made is film nobody has");
296
+ }
221
297
  });
222
298
 
223
299
  test("every encoder stops when nobody is watching the output", () => {
@@ -274,35 +350,51 @@ test("the same plan run twice on an unchanged state gives the same answer", () =
274
350
  assert.ok(!first.includes("stop"), "and neither pass kills what the other would start");
275
351
  });
276
352
 
277
- test("a viewer's most urgent zone is filled before a less urgent one", () => {
353
+ test("the one machine goes to whoever is due soonest, not to the smallest number", () => {
354
+ // Urgency is a TIME in this model, so the test states one. The far zone is
355
+ // lower in number and needed sooner; the order must come from the time.
278
356
  const coverage = new CoverageMap({ segmentCount: 1000 });
279
357
  const actions = planEncoders({
280
358
  coverage,
281
- // The far zone is lower in number and lower in priority: the order must
282
- // come from the priority, not from the number.
283
359
  windows: [
284
- { from: 0, to: 100, priority: 1 },
285
- { from: 500, to: 530, priority: 3 }
360
+ { from: 0, to: 100, priority: 1, withinSeconds: 600 },
361
+ { from: 500, to: 530, priority: 3, withinSeconds: 0 }
286
362
  ],
287
- runs: [],
363
+ // A run whose speed has been measured: how long an encoder takes to reach a
364
+ // number is the whole comparison, and with nothing measured the model has no
365
+ // ground to prefer one place over another and places once.
366
+ runs: [run({ from: 900, to: 999, head: 900 })],
288
367
  ...HOST,
289
- maxRuns: 1
368
+ maxRuns: 2
290
369
  });
291
370
  const started = actions.filter((action) => action.type === "start").map((action) => action.from);
292
371
 
293
372
  assert.deepEqual(started, [500], "the one machine goes where somebody is stopped");
294
373
  });
295
374
 
296
- test("two viewers far apart get an encoder each, when the machine can hold two", () => {
375
+ test("two viewers far apart are both served, by however many encoders serve them soonest", () => {
376
+ // Two encoders, or one that goes to whichever of them is worse off: the answer
377
+ // is what a second process costs this machine, which is measured, and the
378
+ // score works it out. What must hold is that neither of them is simply left.
297
379
  const coverage = new CoverageMap({ segmentCount: 1000 });
298
380
  const actions = planEncoders({
299
381
  coverage,
300
- windows: [{ from: 0, to: 30 }, { from: 800, to: 830 }],
301
- runs: [],
302
- ...HOST
382
+ windows: [
383
+ { from: 0, to: 30, withinSeconds: 0 },
384
+ { from: 800, to: 830, withinSeconds: 0 }
385
+ ],
386
+ runs: [run({ from: 900, to: 999, head: 900 })],
387
+ ...HOST,
388
+ maxRuns: 3
303
389
  });
304
- const started = actions.filter((action) => action.type === "start").map((action) => action.from);
305
- assert.deepEqual(started, [0, 800]);
390
+ const spans = actions
391
+ .filter((action) => action.type !== "stop")
392
+ .map((action) => [action.from, action.to < action.from ? Number.POSITIVE_INFINITY : action.to]);
393
+ assert.ok(spans.some(([from, to]) => from <= 0 && to >= 0), "the one at the beginning is served");
394
+ for (let index = 0; index < spans.length - 1; index += 1) {
395
+ const sorted = [...spans].sort((left, right) => left[0] - right[0]);
396
+ assert.ok(sorted[index][1] < sorted[index + 1][0], "and no two encoders share a number");
397
+ }
306
398
  });
307
399
 
308
400
  test("a machine that can hold one gives it to the viewer who is stopped soonest", () => {
@@ -327,6 +419,12 @@ test("a viewer joining behind a running encoder gets their own, not a dragged on
327
419
  const coverage = new CoverageMap({ segmentCount: 1000 });
328
420
  const runA = run({ from: 500, to: 600, head: 510 });
329
421
  coverage.claim(runA, 500, 600);
422
+ // A head at #510 means #500..#509 are already made — that is what a head is.
423
+ // Left unsaid, the map believes a viewer is waiting on ten pieces nobody has,
424
+ // and moving the encoder back to make them is then the right answer.
425
+ for (let index = 500; index < 510; index += 1) {
426
+ coverage.markReady(index);
427
+ }
330
428
  const actions = planEncoders({
331
429
  coverage,
332
430
  windows: [{ from: 500, to: 530 }, { from: 100, to: 130 }],
@@ -366,7 +464,7 @@ test("a run with no end is making what the viewers ahead of it are waiting for",
366
464
  wanted: [{ from: 0, to: 30 }],
367
465
  maxRuns: 2,
368
466
  segmentSeconds: 4,
369
- restartCostSec: 0.12
467
+ ...RUN_COSTS
370
468
  });
371
469
 
372
470
  assert.deepEqual(
@@ -381,15 +479,26 @@ test("a run with no end is making what the viewers ahead of it are waiting for",
381
479
  );
382
480
  });
383
481
 
384
- test("the machine's whole budget is used, not one encoder per viewer", () => {
385
- // Nothing about a viewer says how many encoders there should be. What says it
386
- // is what the machine affords, and the film is divided between them.
482
+ test("spare budget IS spent on the rest of the film, once nobody is waiting", () => {
483
+ // The user's own correction: film nobody is waiting for still has value,
484
+ // because a viewer seeking back into a part that exists starts playing at
485
+ // once. So what the machine has spare goes to finishing the file.
486
+ //
487
+ // "Once nobody is waiting" is not a condition written anywhere — it falls out
488
+ // of the score. A second process costs the first the measured share of the
489
+ // machine, so while somebody is stopped on a piece, adding one delays that
490
+ // piece and the first term refuses it. Here the near film is already made,
491
+ // nothing is late in either arrangement, and the two that finish the rest
492
+ // sooner win.
387
493
  const coverage = new CoverageMap({ segmentCount: 400 });
494
+ for (let index = 0; index <= 9; index += 1) {
495
+ coverage.markReady(index);
496
+ }
388
497
  const actions = planEncoders({
389
498
  coverage,
390
499
  windows: [
391
- { from: 0, to: 9, priority: 32 },
392
- { from: 10, to: 399, priority: 20 }
500
+ { from: 0, to: 9, priority: 32, withinSeconds: 0 },
501
+ { from: 10, to: 399, priority: 20, withinSeconds: 40 }
393
502
  ],
394
503
  runs: [],
395
504
  ...HOST,
@@ -397,31 +506,15 @@ test("the machine's whole budget is used, not one encoder per viewer", () => {
397
506
  });
398
507
  const started = actions.filter((action) => action.type === "start");
399
508
 
400
- assert.equal(started.length, 4, "four encoders where the machine allows four");
401
- const from = started.map((action) => action.from).sort((left, right) => left - right);
402
- assert.equal(from[0], 0, "the first where the viewer is stopped");
403
- assert.ok(from[3] > from[0], "and the rest spread over the film");
509
+ assert.ok(started.length > 1, "the machine does not stand idle while film is unmade");
510
+ const spans = started
511
+ .map((action) => [action.from, action.to])
512
+ .sort((left, right) => left[0] - right[0]);
513
+ for (let index = 0; index < spans.length - 1; index += 1) {
514
+ assert.ok(spans[index][1] < spans[index + 1][0], "and no two of them share a number");
515
+ }
404
516
  });
405
517
 
406
- test("below realtime the first stretches are what one encoder can hold", () => {
407
- // At half speed an encoder holds `(q - p)` of film: the first covers ten
408
- // segments, and the next has to be standing where it stops holding.
409
- const coverage = new CoverageMap({ segmentCount: 400 });
410
- const actions = planEncoders({
411
- coverage,
412
- windows: [{ from: 10, to: 399, priority: 32 }],
413
- runs: [{ from: 0, to: 0, head: 0, speedX: 0.5 }],
414
- ...HOST,
415
- maxRuns: 3
416
- });
417
- const started = actions
418
- .filter((action) => action.type === "start")
419
- .map((action) => action.from)
420
- .sort((left, right) => left - right);
421
-
422
- assert.ok(started.length >= 2, "more than one, because one cannot hold the film");
423
- assert.ok(started[1] > started[0], "and they grow apart rather than sitting together");
424
- });
425
518
 
426
519
  test("two encoders never share a segment number", () => {
427
520
  // The whole of what went wrong in the field: two encoders writing one name.
@@ -21,6 +21,7 @@
21
21
 
22
22
  import test from "node:test";
23
23
  import assert from "node:assert/strict";
24
+ import { existsSync } from "node:fs";
24
25
  import { mkdtemp, rm, writeFile, readdir } from "node:fs/promises";
25
26
  import os from "node:os";
26
27
  import path from "node:path";
@@ -110,3 +111,42 @@ test("only inside the stretch the ended run was given", async () => {
110
111
  await rm(dir, { recursive: true, force: true });
111
112
  }
112
113
  });
114
+
115
+ test("a run given no end never reaches past what it made, so a live run's piece is safe", async () => {
116
+ // Field 2026-09-06. Three encoders wrote into one directory; the first was
117
+ // stopped, its stretch was `#0..#-1` — "to the end of the track" — and the
118
+ // cleanup after it took the highest-numbered file anywhere in that directory,
119
+ // which a LIVE encoder had just finished. The number is spent for good,
120
+ // because names only grow, and the picture stood still for 647 seconds.
121
+ const dir = await mkdtemp(path.join(os.tmpdir(), "open-piece-"));
122
+ // What the stopped run made: #0 and #1, with #2 left open.
123
+ await writeFile(path.join(dir, "segment-00000.mp4"), Buffer.alloc(1000, 1));
124
+ await writeFile(path.join(dir, "segment-00001.mp4"), Buffer.alloc(1000, 1));
125
+ await writeFile(path.join(dir, "segment-00002.mp4"), Buffer.alloc(0));
126
+ // What a live encoder, working further along the same track, has finished.
127
+ await writeFile(path.join(dir, "segment-00040.mp4"), Buffer.alloc(9000, 1));
128
+
129
+ const removed = await discardOpenPiece(
130
+ dir,
131
+ format,
132
+ { from: 0, to: -1 },
133
+ null,
134
+ "segment-00001.mp4"
135
+ );
136
+
137
+ assert.equal(removed, 2, "its own open piece goes");
138
+ assert.ok(existsSync(path.join(dir, "segment-00040.mp4")), "the live run's piece stays");
139
+ });
140
+
141
+ test("a run that named nothing leaves only its own first piece", async () => {
142
+ // It opened one file and died — 548 ms after starting, in the field. Nothing
143
+ // above that can be its.
144
+ const dir = await mkdtemp(path.join(os.tmpdir(), "open-piece-"));
145
+ await writeFile(path.join(dir, "segment-00010.mp4"), Buffer.alloc(0));
146
+ await writeFile(path.join(dir, "segment-00011.mp4"), Buffer.alloc(9000, 1));
147
+
148
+ const removed = await discardOpenPiece(dir, format, { from: 10, to: -1 }, null, null);
149
+
150
+ assert.equal(removed, 10, "the one it opened");
151
+ assert.ok(existsSync(path.join(dir, "segment-00011.mp4")), "and nothing beyond it");
152
+ });
@@ -0,0 +1,86 @@
1
+ /**
2
+ * @file How fast this machine produces one output.
3
+ *
4
+ * The figure every decision in the encoding layer rests on: where an encoder
5
+ * goes and how many run are both worked out from arrivals, and an arrival is a
6
+ * distance divided by this. There must therefore ALWAYS be an answer, and each
7
+ * of the three that can be given is a measurement rather than a guess.
8
+ */
9
+
10
+ import test from "node:test";
11
+ import assert from "node:assert/strict";
12
+ import { EncodeCost } from "../services/quality/EncodeCost.js";
13
+ import { LiveOutputs } from "../services/output/LiveOutputs.js";
14
+
15
+ const PICTURE = "torrent:abc:fmt=fmp4:grid=kf@0:video-only:v=0/copy";
16
+
17
+ /**
18
+ * @param {object[]} sessions
19
+ * @param {{ copySpeedX?: number | null, benchmark?: object[] | null }} host
20
+ */
21
+ function costOf(sessions, host = {}) {
22
+ const sessionsById = new Map(sessions.map((session, index) => [String(index), session]));
23
+ return new EncodeCost({
24
+ liveOutputs: new LiveOutputs({ sessionsById }),
25
+ host: () => ({
26
+ benchmark: host.benchmark ?? null,
27
+ decodeModel: null,
28
+ contentionPenalties: null,
29
+ copySpeedX: host.copySpeedX ?? null,
30
+ availability: null
31
+ }),
32
+ audioCostKey: () => "",
33
+ runningEncoders: () => 0,
34
+ encodersRunningNow: () => 0,
35
+ torrentCostSecFor: () => 0
36
+ });
37
+ }
38
+
39
+ test("a run on this output outranks every prediction", () => {
40
+ // It is this machine, this material and these settings. Nothing said before
41
+ // the fact beats something seen happening.
42
+ const cost = costOf(
43
+ [{ outputKey: PICTURE, state: "ready", transcodeVideo: false, lastAloneSpeed: 9.5 }],
44
+ { copySpeedX: 600 }
45
+ );
46
+ assert.equal(cost.speedForOutput(PICTURE), 9.5);
47
+ });
48
+
49
+ test("a copied picture is priced by the startup copy measurement", () => {
50
+ // The branch that had no figure at all until it was measured: copying neither
51
+ // decodes nor encodes, so neither the preset benchmark nor the decode model
52
+ // describes it, and a copied output was planned with no speed until its own
53
+ // run had been running long enough to report one.
54
+ const cost = costOf(
55
+ [{ outputKey: PICTURE, state: "ready", transcodeVideo: false, lastAloneSpeed: null }],
56
+ { copySpeedX: 602 }
57
+ );
58
+ assert.equal(cost.speedForOutput(PICTURE), 602);
59
+ });
60
+
61
+ test("the fastest reading wins where several sessions produce one output", () => {
62
+ // Two sessions whose output parameters agree ARE one output, so what either
63
+ // of them measured about this machine is true of the other.
64
+ const cost = costOf([
65
+ { outputKey: PICTURE, state: "ready", transcodeVideo: false, lastAloneSpeed: 4 },
66
+ { outputKey: PICTURE, state: "ready", transcodeVideo: false, lastAloneSpeed: 7 }
67
+ ]);
68
+ assert.equal(cost.speedForOutput(PICTURE), 7);
69
+ });
70
+
71
+ test("a disposed session says nothing about what the machine is doing", () => {
72
+ const cost = costOf(
73
+ [{ outputKey: PICTURE, state: "disposed", transcodeVideo: false, lastAloneSpeed: 4 }],
74
+ { copySpeedX: 602 }
75
+ );
76
+ assert.equal(cost.speedForOutput(PICTURE), 0, "no live session, so nothing to say");
77
+ });
78
+
79
+ test("another output's reading is not borrowed", () => {
80
+ // Two outputs are two different pieces of work — a picture re-encoded to 480p
81
+ // and the same picture copied are not the same speed.
82
+ const cost = costOf([
83
+ { outputKey: "other", state: "ready", transcodeVideo: false, lastAloneSpeed: 40 }
84
+ ]);
85
+ assert.equal(cost.speedForOutput(PICTURE), 0);
86
+ });
@@ -278,6 +278,32 @@ test("the store asks for what its readers declared, and for a whole window at le
278
278
  }
279
279
  });
280
280
 
281
+ test("the floor the ceiling will not fall below is the same union, not the widest reader alone", async () => {
282
+ const { store, directory } = await makeStore(64);
283
+ try {
284
+ // The same two overlapping readers as the ask above: picture and sound,
285
+ // 10..44, thirty-five pieces together. The machine now offers far less
286
+ // than that (ten pieces) — before this fix the floor here came from
287
+ // `widestPieces` (twenty, the wider of the two readers alone), fifteen
288
+ // short of what both of them were actually pinning at once. A field
289
+ // session with three such readers on one file (video, audio and the
290
+ // edge-warming read) reached exactly that shortfall on 2026-09-07: every
291
+ // resident piece ended up pinned and WebTorrent destroyed the torrent
292
+ // over it.
293
+ store.protectRange("video", 10, 29);
294
+ store.protectRange("audio", 25, 44);
295
+ const revised = store.reviseGrowthCeiling(10 * PIECE);
296
+ assert.equal(
297
+ revised.ceilingBytes,
298
+ 35 * PIECE,
299
+ "the floor is the union of both readers, not the wider one alone"
300
+ );
301
+ } finally {
302
+ store.destroy(() => undefined);
303
+ await fs.rm(directory, { recursive: true, force: true });
304
+ }
305
+ });
306
+
281
307
  test("a block is re-used instead of a new one being allocated for every piece", async () => {
282
308
  const capacity = 4;
283
309
  const { store, directory } = await makeStore(capacity);