@torrent-tv/proxy 2.80.13 → 2.80.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -102,6 +102,11 @@
102
102
  * measured on this host from its own runs. Zero until something has measured
103
103
  * it, which makes moving one look cheaper than it is and is said here so the
104
104
  * bias is known.
105
+ * @param {number} [params.moveCostSec] - What moving a running encoder costs on
106
+ * this host, measured. `Infinity` until something has been measured, because a
107
+ * move is irreversible and leaving the encoder alone is always available.
108
+ * @param {number} [params.now] - The clock, injected. This layer is arithmetic
109
+ * and reads no clock of its own; how old a run is is one of its inputs.
105
110
  * @param {number} [params.firstByteWaitSec] - How long a fresh encoder takes to
106
111
  * produce anything: process start, opening the input, and the first piece.
107
112
  * Measured the same way. It replaced a constant of 0.12 s taken from one
@@ -123,6 +128,8 @@ export function planEncoders({
123
128
  segmentSeconds,
124
129
  killCostSec = 0,
125
130
  firstByteWaitSec = 0,
131
+ moveCostSec = Number.POSITIVE_INFINITY,
132
+ now = Date.now(),
126
133
  refetchSecPerFilmSecond = 0,
127
134
  contentionPenaltyFor = () => 1,
128
135
  speedX = 0
@@ -164,7 +171,38 @@ export function planEncoders({
164
171
  // its death, the start of another, and the wait for the first bytes there.
165
172
  // Taking an encoder somewhere else is stopping this one and waiting for the
166
173
  // next to produce. Both halves are measured on this host.
167
- const moveSec = killCostSec + firstByteWaitSec;
174
+ // WHAT A MOVE COSTS. Killing an encoder and waiting for a fresh one's first
175
+ // piece is the price; until something has produced anything on this host that
176
+ // price is unknown, and a move is then refused rather than priced at zero.
177
+ // Placing one where there is none is the other question and takes the unknown
178
+ // the other way — see `run-costs.js`.
179
+ const moveSec = Number.isFinite(moveCostSec) ? moveCostSec : killCostSec + firstByteWaitSec;
180
+
181
+ // WHAT A RUN STILL HAS TO GO BEFORE IT PRODUCES ANYTHING — the measured time
182
+ // to a first piece, less the time it has already been alive.
183
+ //
184
+ // This is the memory the score was missing. It is computed afresh whenever
185
+ // anything changes, and every arrangement used to be priced as though it were
186
+ // the last decision anybody would take: a run that started 10 ms ago was
187
+ // assumed to produce instantly, so killing it and starting another looked like
188
+ // a straight gain. A move is justified by a benefit that arrives when the
189
+ // moved run produces something; taken again before it has, the benefit is
190
+ // never collected and the cost is paid twice, three times, forty times.
191
+ //
192
+ // A run 0.8 s old has 0.14 s left to go against 0.94 s to move it, so it is
193
+ // left alone; one working for half a minute has nothing left, and a move
194
+ // happens exactly when the film it would reach sooner is worth the restart. No
195
+ // state to keep and nothing to choose: one measured figure minus elapsed time.
196
+ const remainingWarmOf = (run) => {
197
+ if (Number(run.head) !== Number(run.from)) {
198
+ return 0; // it has produced something, so its warm-up is spent
199
+ }
200
+ const startedAt = Number(run.startedAt);
201
+ if (!Number.isFinite(startedAt) || startedAt <= 0) {
202
+ return firstByteWaitSec; // not started yet, so all of it is ahead
203
+ }
204
+ return Math.max(0, firstByteWaitSec - (now - startedAt) / 1000);
205
+ };
168
206
 
169
207
  // ------------------------------------------------------------------ WHERE
170
208
  //
@@ -228,6 +266,28 @@ export function planEncoders({
228
266
  if (arrangement.used.has(run)) {
229
267
  continue;
230
268
  }
269
+ // A LIVE RUN IS NEVER TAKEN FOR RESIDUAL WORK.
270
+ //
271
+ // A zone that states no deadline has nobody waiting in it: it is the
272
+ // film behind the viewers, kept in case somebody seeks back, and it is
273
+ // done with capacity that is left over. A run already standing in front
274
+ // of a viewer is not left over — taking it there means killing it, and
275
+ // the viewer it was serving waits out a cold start for film nobody had
276
+ // asked for.
277
+ //
278
+ // Field 2026-09-08: on a host affording three runs, one viewer got
279
+ // three. Two of them came from this residual capacity, and the third was
280
+ // the run serving the viewer, taken to #30 in the middle of the film
281
+ // they had already watched. Reaching that film took the third term from
282
+ // "never" — the film's own length, 2024 s — to a real figure, which
283
+ // outvotes any price a move can carry. It must not be able to outvote
284
+ // it, and the reason is not arithmetic: nobody is waiting there.
285
+ //
286
+ // Read off the map, which is the one thing that states it. Nothing here
287
+ // knows that a viewer exists.
288
+ if (!Number.isFinite(untilNeeded(positions[index]))) {
289
+ continue;
290
+ }
231
291
  next.push({
232
292
  fill: [...arrangement.fill, run],
233
293
  used: new Set([...arrangement.used, run]),
@@ -254,7 +314,7 @@ export function planEncoders({
254
314
  const head = Number(filler.head);
255
315
  bodies.push({
256
316
  at: positions[index],
257
- delaySec: head === positions[index] ? 0 : moveSec
317
+ delaySec: head === positions[index] ? remainingWarmOf(filler) : moveSec
258
318
  });
259
319
  }
260
320
  // Bodies nobody was given a position for go on working where they stand,
@@ -273,10 +333,20 @@ export function planEncoders({
273
333
  const cutInFront = arrangement.fill.some((filler, index) =>
274
334
  filler !== null && positions[index] > head
275
335
  && (endless || positions[index] <= Number(run.to)));
276
- bodies.push({ at: head, delaySec: cutInFront ? moveSec : 0 });
336
+ bodies.push({ at: head, delaySec: cutInFront ? moveSec : remainingWarmOf(run) });
277
337
  }
278
338
  const scored = latenessOf(bodies, coverage, wanted, untilNeeded, rate / contentionPenaltyFor(Math.max(0, bodies.length - 1)), refetchPerSegment, segmentSeconds);
279
- if (bestScore === null || cheaperThan(scored, bestScore)) {
339
+ // WHETHER THIS ARRANGEMENT KILLS A RUNNING ENCODER, which is what has to pay
340
+ // for itself. An arrangement that leaves every live run where it stands owes
341
+ // nothing; one that takes a run somewhere else has to be better by what the
342
+ // taking costs.
343
+ const disturbs = live.some((run) => {
344
+ const at = arrangement.used.has(run)
345
+ ? positions[arrangement.fill.indexOf(run)]
346
+ : Number(run.head);
347
+ return at !== Number(run.head);
348
+ });
349
+ if (bestScore === null || cheaperThan(scored, bestScore, disturbs ? moveSec : 0)) {
280
350
  bestScore = scored;
281
351
  best = arrangement;
282
352
  }
@@ -309,7 +379,7 @@ export function planEncoders({
309
379
  }
310
380
  const at = override.has(run) ? override.get(run) : (placement.get(run) ?? Number(run.head));
311
381
  const head = Number(run.head);
312
- bodies.push({ at, delaySec: at === head ? 0 : moveSec });
382
+ bodies.push({ at, delaySec: at === head ? remainingWarmOf(run) : moveSec });
313
383
  }
314
384
  for (let index = 0; index < positions.length; index += 1) {
315
385
  if ((best ? best.fill[index] : null) === "new") {
@@ -333,7 +403,7 @@ export function planEncoders({
333
403
  }
334
404
  const asIs = scoreOf(new Map());
335
405
  const moved = scoreOf(new Map([[run, gap]]));
336
- if (cheaperThan(moved, asIs)) {
406
+ if (cheaperThan(moved, asIs, moveSec)) {
337
407
  placement.set(run, gap);
338
408
  }
339
409
  }
@@ -566,19 +636,33 @@ function latenessOf(bodies, coverage, wanted, untilNeeded, rate, refetchSecPerSe
566
636
  // had nothing to compare, and the encoder was free to wander to the start of
567
637
  // the file. Which side a stretch is on and how soon it is wanted are two
568
638
  // different facts, and the map states both.
569
- const isBehind = (at) => {
570
- let behind = false;
639
+ // WHAT RANK THE MAP GIVES THIS NUMBER — the highest, where zones overlap,
640
+ // because a number two viewers want is wanted as much as the more urgent of
641
+ // them wants it.
642
+ //
643
+ // This replaced a boolean, "is it behind everybody", and the boolean was the
644
+ // whole of what the objective knew about the map's own order. The map states
645
+ // ten ranks on a film — p100 at the number a viewer is stopped on, doubling
646
+ // zones down to p91 for the far tail, p1 for what lies behind them — and all
647
+ // of that was collapsed into two buckets and then converted to seconds, where
648
+ // "never" for the film behind is the film's own length. On a 48-minute file
649
+ // that is 2024 s, which outvotes everything: field 2026-09-08, one viewer got
650
+ // three encoders, two of them on film behind them, and the run serving them
651
+ // was killed to make room for one.
652
+ const rankAt = (at) => {
653
+ let rank = 0;
571
654
  for (const span of wanted) {
572
- if (at < span.from || at > span.to) {
573
- continue;
574
- }
575
- if (span.behind !== true) {
576
- return false;
655
+ if (at >= span.from && at <= span.to) {
656
+ rank = Math.max(rank, Number(span.priority) || 0);
577
657
  }
578
- behind = true;
579
658
  }
580
- return behind;
659
+ return rank;
581
660
  };
661
+ // The ranks the map actually states, most urgent first. The comparison is over
662
+ // these and nothing else, so a rank can never be outvoted by a lower one
663
+ // however many seconds are at stake there.
664
+ const ranks = [...new Set(wanted.map((span) => Number(span.priority) || 0))]
665
+ .sort((left, right) => right - left);
582
666
 
583
667
  // EVERY COUNT IS OVER THE FILM, NOT OVER THE ENCODERS. When a piece is made
584
668
  // depends on which encoder reaches it soonest, and the encoder that reaches
@@ -604,10 +688,11 @@ function latenessOf(bodies, coverage, wanted, untilNeeded, rate, refetchSecPerSe
604
688
  // clock forward makes that trade impossible: abandoning the near film delays
605
689
  // the far film by at least as much.
606
690
  let stalled = 0;
607
- let tardiness = 0;
608
- let aheadDone = 0;
609
- let behindDone = 0;
610
691
  let wastedSwarm = 0;
692
+ /** Seconds anybody waits past a deadline, per rank. @type {Map<number, number>} */
693
+ const lateAt = new Map(ranks.map((rank) => [rank, 0]));
694
+ /** When the last number of a rank is made, per rank. @type {Map<number, number>} */
695
+ const doneAt = new Map(ranks.map((rank) => [rank, 0]));
611
696
  for (let index = first; index <= last; index += 1) {
612
697
  // Which encoder gets to this piece first, and when. One standing on it is
613
698
  // already there; one behind it must work its way up, re-making anything
@@ -643,30 +728,39 @@ function latenessOf(bodies, coverage, wanted, untilNeeded, rate, refetchSecPerSe
643
728
  // total on a host whose startup measured nothing at all, where every arrival
644
729
  // is beyond reckoning and every arrangement is therefore equally hopeless.
645
730
  const when = byWhom === null ? never : Math.min(soonest, never);
646
- if (isBehind(index)) {
647
- behindDone = Math.max(behindDone, when);
648
- } else {
649
- aheadDone = Math.max(aheadDone, when);
650
- }
731
+ const rank = rankAt(index);
732
+ doneAt.set(rank, Math.max(doneAt.get(rank) ?? 0, when));
651
733
  const deadline = untilNeeded(index);
652
734
  if (Number.isFinite(deadline)) {
653
735
  const due = deadline + stalled;
654
736
  const waited = Math.max(0, when - due);
655
- tardiness += waited;
737
+ lateAt.set(rank, (lateAt.get(rank) ?? 0) + waited);
656
738
  stalled += waited;
657
739
  }
658
740
  }
659
741
 
660
742
  return {
661
- // 1. SECONDS ANYBODY SPENDS LOOKING AT A SPINNER. Nothing outranks it.
662
- stall: tardiness,
663
- // 2. WHEN THE FILM IN FRONT OF THEM IS DONE — the last piece of it to be
664
- // made, whichever encoder makes it. Film nobody reaches counts as never,
665
- // which is what stops the front being abandoned.
666
- ahead: aheadDone,
667
- // 3. WHEN THE WHOLE FILE IS DONE the film behind included, and the swarm's
668
- // price for anything fetched twice, which delays everything.
669
- whole: Math.max(aheadDone, behindDone) + wastedSwarm
743
+ // THE MAP'S OWN ORDER, AS A VECTOR. One pair per rank the map states, most
744
+ // urgent rank first: how long anybody waits at that rank, then when the last
745
+ // number of it is made.
746
+ //
747
+ // Compared position by position, so a rank is never outvoted by a lower one
748
+ // — which is the whole of what was asked for: nobody stares at a spinner;
749
+ // then the film in front of the viewers is encoded as fast as it can be, band
750
+ // by band as the map ranks them; then, with whatever is left over and only
751
+ // then, the film behind them, in case somebody seeks back.
752
+ //
753
+ // No weights, and none possible: a weight would let seconds at one rank buy
754
+ // seconds at another, and it would be a number nobody measured. The map is
755
+ // the source of truth about what matters, and it already says so.
756
+ byRank: ranks.flatMap((rank) => [lateAt.get(rank) ?? 0, doneAt.get(rank) ?? 0]),
757
+ // HOW MANY ENCODERS IT TAKES. Ranked below every rank of the map and above
758
+ // the swarm's bill, so it cannot buy one where the map is indifferent — and
759
+ // the map IS indifferent about spare capacity, which is what bought an
760
+ // encoder for film nobody waits for.
761
+ bodies: bodies.length,
762
+ // WHAT THE SWARM PAYS for anything fetched twice, which delays everything.
763
+ wasted: wastedSwarm
670
764
  };
671
765
  }
672
766
 
@@ -686,14 +780,44 @@ function latenessOf(bodies, coverage, wanted, untilNeeded, rate, refetchSecPerSe
686
780
  * @param {{ stall: number, ahead: number, whole: number }} right
687
781
  * @returns {boolean}
688
782
  */
689
- function cheaperThan(left, right) {
690
- if (left.stall !== right.stall) {
691
- return left.stall < right.stall;
783
+ function cheaperThan(left, right, byAtLeast = 0) {
784
+ // POSITION BY POSITION, in the map's own order of ranks. A difference at a
785
+ // higher rank settles it, and nothing at a lower one can reopen it.
786
+ //
787
+ // `byAtLeast` is what an ACT has to pay for itself. Where the left side is
788
+ // only reachable by killing a running encoder, a gain smaller than what that
789
+ // killing costs is not a gain: the arithmetic says the film arrives sooner,
790
+ // and the machine says a process died for it.
791
+ //
792
+ // Field 2026-09-08, and it is worth the exact numbers because they are so
793
+ // close. A run standing at #58 with the viewer's zone at #59..#60: driving
794
+ // there means making TWO pieces, 1.89 s at 4.45x on a 4.2 s grid, while
795
+ // moving means a cold start and ONE piece, 0.94 + 0.94 = 1.88 s. The move is
796
+ // faster — by ten milliseconds. Every one of 39 moves in that session was
797
+ // individually correct by this arithmetic, 24 of them between three adjacent
798
+ // numbers, and the viewer's picture stood still for 116.7 s.
799
+ //
800
+ // The margin is measured, never chosen: it is the cost of the act itself, and
801
+ // an act that does not repay its own cost is not worth taking. It is also
802
+ // wider than the spread of the figures the comparison is made of, which are
803
+ // medians of recent runs — so a difference smaller than it is not a difference
804
+ // this model can see.
805
+ const size = Math.max(left.byRank.length, right.byRank.length);
806
+ for (let index = 0; index < size; index += 1) {
807
+ const here = left.byRank[index] ?? 0;
808
+ const there = right.byRank[index] ?? 0;
809
+ if (here !== there) {
810
+ return here + byAtLeast < there;
811
+ }
692
812
  }
693
- if (left.ahead !== right.ahead) {
694
- return left.ahead < right.ahead;
813
+ // Where every rank is served identically, fewer encoders. This is what stops
814
+ // spare capacity buying one: the map is indifferent, so the machine decides,
815
+ // and a process, a reader of the piece store and the swarm's bandwidth are all
816
+ // paid by the viewers the ranks above are about.
817
+ if (left.bodies !== right.bodies) {
818
+ return left.bodies < right.bodies;
695
819
  }
696
- return left.whole < right.whole;
820
+ return left.wasted < right.wasted;
697
821
  }
698
822
 
699
823
  /**
@@ -742,7 +866,24 @@ function deadlineReaderFor(windows, segmentSeconds) {
742
866
  // same as a stated one — read as due all at once instead, a window as wide
743
867
  // as a viewer's cushion demanded its far end instantly and bought an
744
868
  // encoder to stand beside one already working.
745
- const within = stated === undefined ? 0 : Number(stated);
869
+ // `null` IS A STATEMENT AND IT SAYS NOBODY IS COMING. `undefined` is the
870
+ // absence of one, and a caller that knows only a position is somebody
871
+ // waiting at it.
872
+ //
873
+ // Read through `Number()`, `null` becomes 0 — due NOW — so the film BEHIND
874
+ // the viewers, which the map marks with exactly that, was the most urgent
875
+ // material in the file. Everything followed from it: it bought encoders,
876
+ // it took the run standing in front of the viewer because that run was the
877
+ // nearest body to it, and it did so again on every pass. Field 2026-09-08:
878
+ // 39 moves in one session, 24 between three adjacent numbers, one viewer
879
+ // on three encoders, and the picture stood still for 116.7 s in three
880
+ // interruptions, the worst of them 91.8 s.
881
+ //
882
+ // The map has always said it plainly — `{"from":0,"to":57,"priority":1,
883
+ // "withinSeconds":null,"behind":true}` is in the log of every session — and
884
+ // this line turned it into its opposite. Fourth time in this repository
885
+ // that the input to a calculation was not what the calculation assumed.
886
+ const within = stated === undefined ? 0 : (stated === null ? Number.NaN : Number(stated));
746
887
  if (!Number.isFinite(within)) {
747
888
  // Stated as no time at all: nobody is coming here.
748
889
  continue;