@torrent-tv/proxy 2.87.4 → 2.87.5
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.
- package/.claude/commands/opsx/apply.md +155 -155
- package/.claude/commands/opsx/archive.md +160 -160
- package/.claude/commands/opsx/explore.md +174 -174
- package/.claude/commands/opsx/propose.md +109 -109
- package/.claude/commands/opsx/sync.md +143 -143
- package/.claude/skills/openspec-apply-change/SKILL.md +159 -159
- package/.claude/skills/openspec-archive-change/SKILL.md +117 -117
- package/.claude/skills/openspec-explore/SKILL.md +289 -289
- package/.claude/skills/openspec-propose/SKILL.md +113 -113
- package/.claude/skills/openspec-sync-specs/SKILL.md +147 -147
- package/.dockerignore +5 -5
- package/AGENTS.md +322 -322
- package/CHANGELOG.md +4 -0
- package/Dockerfile +30 -30
- package/LICENSE +16 -16
- package/README.md +369 -369
- package/assets/calibration/NOTICE.md +100 -100
- package/assets/diagnostics/sctpstate.gdb +31 -31
- package/bin/cli.js +684 -684
- package/bin/thread-pool.js +30 -30
- package/biome.json +411 -411
- package/docs/browser-proxy-contract.md +94 -94
- package/docs/container-architecture.md +218 -218
- package/docs/download-architecture.md +261 -261
- package/docs/encode-architecture.md +729 -729
- package/docs/encode-run-state.md +96 -96
- package/docs/logs.md +97 -97
- package/docs/socks5-egress-research.md +114 -114
- package/docs/storage-architecture.md +267 -267
- package/knip.json +14 -14
- package/openspec/changes/adaptive-bitrate/design.md +92 -92
- package/openspec/changes/adaptive-bitrate/proposal.md +56 -56
- package/openspec/changes/adaptive-bitrate/specs/adaptive-bitrate/spec.md +51 -51
- package/openspec/changes/adaptive-bitrate/tasks.md +40 -40
- package/openspec/changes/binary-distribution/.openspec.yaml +2 -2
- package/openspec/changes/binary-distribution/proposal.md +53 -53
- package/openspec/changes/chunked-request-bodies/design.md +81 -81
- package/openspec/changes/chunked-request-bodies/proposal.md +53 -53
- package/openspec/changes/chunked-request-bodies/specs/chunked-request-bodies/spec.md +30 -30
- package/openspec/changes/chunked-request-bodies/tasks.md +40 -40
- package/openspec/changes/cold-start/design.md +98 -98
- package/openspec/changes/cold-start/proposal.md +62 -62
- package/openspec/changes/cold-start/specs/cold-start/spec.md +47 -47
- package/openspec/changes/cold-start/tasks.md +48 -48
- package/openspec/changes/disk-cap/.openspec.yaml +2 -2
- package/openspec/changes/disk-cap/proposal.md +37 -37
- package/openspec/changes/disk-cap/specs/disk-cap/spec.md +25 -25
- package/openspec/changes/disk-cap/tasks.md +19 -19
- package/openspec/changes/proxy-observability/.openspec.yaml +2 -2
- package/openspec/changes/proxy-observability/design.md +38 -38
- package/openspec/changes/proxy-observability/proposal.md +49 -49
- package/openspec/changes/proxy-observability/specs/observability/spec.md +33 -33
- package/openspec/changes/proxy-observability/tasks.md +21 -21
- package/openspec/changes/seek-debounce/design.md +118 -118
- package/openspec/changes/seek-debounce/proposal.md +59 -59
- package/openspec/changes/seek-debounce/specs/seek-debounce/spec.md +41 -41
- package/openspec/changes/seek-debounce/tasks.md +33 -33
- package/openspec/changes/subtitle-language/.openspec.yaml +2 -2
- package/openspec/changes/subtitle-language/proposal.md +50 -50
- package/openspec/changes/subtitle-language/specs/subtitle-language/spec.md +34 -34
- package/openspec/changes/subtitle-language/tasks.md +26 -26
- package/openspec/changes/track-selection/.openspec.yaml +2 -2
- package/openspec/changes/track-selection/design.md +47 -47
- package/openspec/changes/track-selection/proposal.md +45 -45
- package/openspec/changes/track-selection/specs/track-selection/spec.md +41 -41
- package/openspec/changes/track-selection/tasks.md +20 -20
- package/openspec/changes/transcode-quality/.openspec.yaml +2 -2
- package/openspec/changes/transcode-quality/proposal.md +51 -51
- package/openspec/changes/transcode-quality/specs/transcode-quality/spec.md +139 -139
- package/openspec/changes/transcode-quality/tasks.md +63 -63
- package/openspec/config.yaml +38 -38
- package/package.json +1 -1
- package/public/.well-known/appspecific/com.chrome.devtools.json +1 -1
- package/research/delivery-wedge-stand-ladder-2026-09-13.md +329 -329
- package/research/double-spawn-2026-09-10.md +171 -171
- package/research/handover-2026-09-13.md +209 -209
- package/research/handover-reader-claims-removal-2026-09-12.md +166 -166
- package/research/piece-withdrawn-but-still-claimed-2026-09-12.md +175 -175
- package/research/preset-benchmark-size-scaling-2026-08-22.md +55 -55
- package/research/priority-map-is-the-truth-2026-09-12.md +225 -225
- package/routes/api/client-logs/post.js +86 -86
- package/routes/api/delivery-sink/get.js +72 -72
- package/routes/api/link-probe/get.js +51 -51
- package/routes/api/playback-plan/post.js +53 -53
- package/routes/api/sources/files/get.js +100 -100
- package/routes/api/sources/post.js +36 -36
- package/routes/api/sources/stats/get.js +132 -132
- package/routes/api/subtitles/get.js +230 -230
- package/routes/api/transcode-sessions/fragment-far/post.js +60 -60
- package/routes/api/transcode-sessions/net-report/post.js +129 -129
- package/routes/api/transcode-sessions/post.js +160 -160
- package/routes/api/transcode-sessions/progress/get.js +27 -27
- package/routes/api/transcode-sessions/release/post.js +41 -41
- package/routes/api/transcode-sessions/seek/post.js +63 -63
- package/routes/health/get.js +13 -13
- package/routes/healthz/get.js +13 -13
- package/routes/stream/get.js +361 -361
- package/routes/transcode/audio-file/get.js +64 -64
- package/routes/transcode/audio-warm/get.js +84 -84
- package/routes/transcode/session-file/get.js +52 -52
- package/routes/transcode/variant-file/get.js +95 -95
- package/routes/transcode/variant-warm/get.js +84 -84
- package/scripts/render-run-graph.js +173 -173
- package/services/encode/CoverageMap.js +428 -428
- package/services/encode/CushionReport.js +345 -345
- package/services/encode/EncodeAdmission.js +332 -332
- package/services/encode/EncodeOrchestrator.js +1217 -1217
- package/services/encode/EncodePlan.js +1366 -1366
- package/services/encode/EncodeRun.js +776 -776
- package/services/encode/EncodeRuns.js +1229 -1229
- package/services/encode/Encoder.js +142 -142
- package/services/encode/EncoderSelection.js +38 -38
- package/services/encode/LocalObservations.js +359 -359
- package/services/encode/NvencEncoder.js +50 -50
- package/services/encode/OutputOpening.js +755 -755
- package/services/encode/OutputTimes.js +579 -579
- package/services/encode/QsvEncoder.js +58 -58
- package/services/encode/Renditions.js +2162 -2162
- package/services/encode/RunProgress.js +84 -84
- package/services/encode/SegmentDemand.js +95 -95
- package/services/encode/SoftwareEncoder.js +121 -121
- package/services/encode/V4l2m2mEncoder.js +53 -53
- package/services/encode/VaapiEncoder.js +66 -66
- package/services/encode/args.js +416 -416
- package/services/encode/available-share.js +89 -89
- package/services/encode/calibration.js +361 -361
- package/services/encode/contention.js +128 -128
- package/services/encode/decode-cost-fit.js +363 -363
- package/services/encode/encode-exit.js +114 -114
- package/services/encode/encode-run-state.js +551 -551
- package/services/encode/encoder-readings.js +45 -45
- package/services/encode/fingerprint.js +322 -322
- package/services/encode/host-load.js +174 -174
- package/services/encode/hwaccel.js +1894 -1894
- package/services/encode/index.js +9 -9
- package/services/encode/learned-median.js +90 -90
- package/services/encode/output/EncodedOutput.js +57 -57
- package/services/encode/output/Output.js +131 -131
- package/services/encode/output/OutputCatalog.js +414 -414
- package/services/encode/output/OutputSpec.js +390 -390
- package/services/encode/output/Timeline.js +308 -308
- package/services/encode/output/cut-grid.js +107 -107
- package/services/encode/output/index.js +20 -20
- package/services/encode/output/ladder.js +32 -32
- package/services/encode/output/playlists.js +284 -284
- package/services/encode/output/rates.js +125 -125
- package/services/encode/output-key-format.js +21 -21
- package/services/encode/quality/EncodeCost.js +1083 -1083
- package/services/encode/quality/HostLoad.js +525 -525
- package/services/encode/quality/HostTimings.js +249 -249
- package/services/encode/quality/OutputQualityState.js +28 -28
- package/services/encode/quality/QualityController.js +935 -935
- package/services/encode/quality/QualityOffer.js +416 -416
- package/services/encode/quality/drain-threat.js +47 -47
- package/services/encode/quality/link-budget.js +254 -254
- package/services/encode/quality/output-format.js +266 -266
- package/services/encode/quality/serving-output.js +216 -216
- package/services/encode/quality/visible-rung.js +57 -57
- package/services/encode/run-budget.js +97 -97
- package/services/encode/run-command.js +747 -747
- package/services/encode/run-costs.js +155 -155
- package/services/encode/run-inputs.js +42 -42
- package/services/encode/segment-formats/fmp4.js +437 -437
- package/services/encode/segment-formats/index.js +77 -77
- package/services/encode/segment-formats/init-compat.js +324 -324
- package/services/encode/segment-formats/mp4-boxes.js +311 -311
- package/services/encode/segment-formats/mpegts.js +134 -134
- package/services/encode/start-stop-cost.js +178 -178
- package/services/encode/throughput.js +97 -97
- package/services/encode/torrent-cost.js +88 -88
- package/services/identity/installation-id.js +38 -38
- package/services/media/ContainerOrchestrator.js +149 -149
- package/services/media/KeyframeTables.js +316 -316
- package/services/media/SourceFile.js +298 -298
- package/services/media/SubtitleCues.js +520 -520
- package/services/media/SubtitleOrchestrator.js +167 -167
- package/services/media/audio-inventory.js +212 -212
- package/services/media/container/AviContainer.js +266 -266
- package/services/media/container/Container.js +603 -603
- package/services/media/container/ContainerFactory.js +56 -56
- package/services/media/container/KeyframeTable.js +272 -272
- package/services/media/container/MatroskaContainer.js +1494 -1494
- package/services/media/container/Mp4Container.js +1244 -1244
- package/services/media/container/SubtitleFileContainer.js +322 -322
- package/services/media/container/ebml-reader.js +159 -159
- package/services/media/ffmpeg-banner.js +260 -260
- package/services/media/keyframe-probe.js +157 -157
- package/services/media/media-info-probe.js +93 -93
- package/services/media/tracks/AudioTrack.js +131 -131
- package/services/media/tracks/ContainerTrack.js +75 -75
- package/services/media/tracks/ImageSubtitleTrack.js +19 -19
- package/services/media/tracks/SubtitleTrack.js +38 -38
- package/services/media/tracks/TextSubtitleTrack.js +287 -287
- package/services/media/tracks/VideoTrack.js +33 -33
- package/services/media/tracks/index.js +15 -15
- package/services/media/tracks/language-detect.js +174 -174
- package/services/server/OutputLifecycle.js +418 -418
- package/services/server/SegmentServing.js +1146 -1146
- package/services/server/ViewerRequests.js +522 -522
- package/services/server/WaitLedger.js +142 -142
- package/services/server/controllers/PlaybackController.js +36 -36
- package/services/server/controllers/SubtitleController.js +109 -109
- package/services/server/http-responses.js +60 -60
- package/services/server/transcode-session-files.js +325 -325
- package/services/server/wire-outputs.js +983 -983
- package/services/storage/Diagnostics.js +202 -202
- package/services/storage/MachineBudget.js +235 -235
- package/services/storage/allowance.js +107 -107
- package/services/storage/core-dumps.js +106 -106
- package/services/storage/files/CompletedFiles.js +391 -391
- package/services/storage/files/piece-from-whole-file.js +118 -118
- package/services/storage/free.js +71 -71
- package/services/storage/keep.js +48 -48
- package/services/storage/machine-memory.js +56 -56
- package/services/storage/memory-report.js +575 -575
- package/services/storage/piece-store/piece-disk-store.js +516 -516
- package/services/storage/piece-store/piece-lru.js +495 -495
- package/services/storage/piece-store/shared-piece-store.js +1880 -1880
- package/services/storage/returns.js +103 -103
- package/services/storage/segment-store/SegmentStore.js +1196 -1196
- package/services/storage/wire.js +175 -175
- package/services/torrent/Contents.js +324 -324
- package/services/torrent/demand/DemandRegister.js +202 -202
- package/services/torrent/demand/Urgency.js +137 -137
- package/services/torrent/demand/Window.js +118 -118
- package/services/torrent/demand/index.js +8 -8
- package/services/torrent/demand/pieces.js +140 -140
- package/services/torrent/download/SwarmSelection.js +386 -386
- package/services/torrent/download/registry.js +102 -102
- package/services/torrent/download/withdraw-claim.js +80 -80
- package/services/torrent/files.js +455 -455
- package/services/torrent/naming.js +619 -619
- package/services/torrent/piece-store-of.js +29 -29
- package/services/torrent/supply-margin.js +262 -262
- package/services/torrent/torrent-source-key.js +38 -38
- package/services/torrent/worker/background-fill.js +213 -213
- package/services/torrent/worker/channel.js +271 -271
- package/services/torrent/worker/fastest-wires.js +342 -342
- package/services/torrent/worker/handle-state.js +23 -23
- package/services/torrent/worker/held-bytes.js +155 -155
- package/services/torrent/worker/install-webrtc-shim.js +32 -32
- package/services/torrent/worker/piece-reader.js +1078 -1078
- package/services/torrent/worker/resume-warm.js +107 -107
- package/services/torrent/worker/webrtc-shim.js +219 -219
- package/services/transport/data-channel-handler.js +1356 -1356
- package/services/transport/delivery-probe.js +842 -842
- package/services/transport/health-collector.js +53 -53
- package/services/transport/nat-classifier.js +231 -231
- package/services/transport/packet-witness.js +808 -808
- package/services/transport/port-mapper.js +235 -235
- package/services/transport/registry-api.js +81 -81
- package/services/transport/tunnel-client.js +447 -447
- package/services/transport/usrsctp-state.js +175 -175
- package/services/transport/webrtc-manager.js +484 -484
- package/services/viewer/Assignments.js +340 -340
- package/services/viewer/PriorityMap.js +390 -390
- package/services/viewer/PriorityOrchestrator.js +325 -325
- package/services/viewer/Viewer.js +657 -657
- package/services/viewer/Viewers.js +368 -368
- package/services/viewer/active-output.js +37 -37
- package/services/viewer/audio-start.js +46 -46
- package/services/viewer/buffer-trend.js +81 -81
- package/services/viewer/choices.js +647 -647
- package/services/viewer/positions.js +126 -126
- package/services/viewer/report-intake.js +46 -46
- package/store/source-registry.js +71 -73
- package/test/asset-paths.test.js +28 -28
- package/test/audio-inventory.test.js +176 -176
- package/test/audio-track-bitrate.test.js +40 -40
- package/test/auto-quality-step.test.js +707 -707
- package/test/available-share.test.js +76 -76
- package/test/background-fill.test.js +190 -190
- package/test/client-log-file.test.js +110 -110
- package/test/completed-files.test.js +115 -115
- package/test/concurrent-cost.test.js +148 -148
- package/test/container-media-info.test.js +228 -228
- package/test/contention.test.js +76 -76
- package/test/core-dumps.test.js +57 -57
- package/test/coverage-follows-the-disk.test.js +228 -228
- package/test/coverage-map.test.js +195 -195
- package/test/cue-push-carries-what-the-walk-found.test.js +58 -58
- package/test/cut-times-timeline.test.js +65 -65
- package/test/cuts-follow-published-grid.test.js +109 -109
- package/test/data-channel-frame.test.js +63 -63
- package/test/dead-torrent-handle.test.js +52 -52
- package/test/declared-rates.test.js +116 -116
- package/test/declared-tracks.test.js +35 -35
- package/test/decode-cost-fit.test.js +202 -202
- package/test/decode-cost.test.js +462 -462
- package/test/delivery-probe.test.js +393 -393
- package/test/demand-register.test.js +206 -206
- package/test/dht-bootstrap.test.js +72 -72
- package/test/drain-threat.test.js +65 -65
- package/test/duplicate-add.test.js +48 -48
- package/test/encode-admission.test.js +146 -146
- package/test/encode-cost.test.js +283 -283
- package/test/encode-exit.test.js +111 -111
- package/test/encode-orchestrator.test.js +419 -419
- package/test/encode-plan-viewers.test.js +718 -718
- package/test/encode-plan.test.js +545 -545
- package/test/encode-run-state.test.js +314 -314
- package/test/encode-run.test.js +161 -161
- package/test/encode-slope.test.js +46 -46
- package/test/encoded-output.test.js +39 -39
- package/test/encoder-kinds.test.js +122 -122
- package/test/encoder-readings.test.js +46 -46
- package/test/every-claimant-is-told-its-share.test.js +152 -152
- package/test/failed-encoder-still-serves-what-exists.test.js +354 -354
- package/test/failed-start-breaker.test.js +202 -202
- package/test/fastest-wires.test.js +143 -143
- package/test/ffmpeg-stream-counts.test.js +78 -78
- package/test/file-edges.test.js +191 -191
- package/test/fmp4-self-contained.test.js +80 -80
- package/test/fmp4-timestamps.test.js +140 -140
- package/test/fmp4-track-completeness.test.js +41 -41
- package/test/format-keyed-outputs.test.js +78 -78
- package/test/health-metrics.test.js +38 -38
- package/test/held-request-width.test.js +157 -157
- package/test/helpers/encode-run.js +136 -136
- package/test/helpers/held-file.js +29 -29
- package/test/helpers/manager.js +57 -57
- package/test/helpers/output-spec.js +63 -63
- package/test/host-load.test.js +68 -68
- package/test/index-accuracy-is-about-the-file.test.js +128 -128
- package/test/init-compat.test.js +219 -219
- package/test/input-lost-quiets-the-plan.test.js +261 -261
- package/test/input-unavailable.test.js +48 -48
- package/test/installation-id.test.js +39 -39
- package/test/keeping-period.test.js +83 -83
- package/test/keyframe-index-accuracy.test.js +188 -188
- package/test/keyframe-table-budget.test.js +102 -102
- package/test/keyframes-belong-to-the-file.test.js +249 -249
- package/test/learned-median.test.js +37 -37
- package/test/letting-go-asks-who-is-watching.test.js +191 -191
- package/test/limit-row-by-area.test.js +60 -60
- package/test/link-budget.test.js +111 -111
- package/test/link-probe.test.js +56 -56
- package/test/local-encoding-data.test.js +273 -273
- package/test/logger-repeats.test.js +134 -134
- package/test/look-ahead-coverage.test.js +56 -56
- package/test/manager-surface.test.js +292 -292
- package/test/matroska-cues-track.test.js +192 -192
- package/test/memory-budget.test.js +390 -390
- package/test/move-cost.test.js +216 -216
- package/test/mp4-subtitles.test.js +173 -173
- package/test/one-authority.test.js +285 -285
- package/test/one-budget.test.js +149 -149
- package/test/one-output-one-session.test.js +204 -204
- package/test/one-resource-divided.test.js +185 -185
- package/test/one-serving-output-per-height.test.js +458 -458
- package/test/orchestrator-wired.test.js +206 -206
- package/test/output-catalog.test.js +39 -39
- package/test/output-shape.test.js +21 -21
- package/test/output-spec.test.js +251 -251
- package/test/output-speed.test.js +88 -88
- package/test/outputs-being-prepared.test.js +40 -40
- package/test/packet-witness-ring.test.js +236 -236
- package/test/packet-witness.test.js +148 -148
- package/test/piece-disk-store.test.js +381 -381
- package/test/piece-from-whole-file.test.js +129 -129
- package/test/piece-lru.test.js +319 -319
- package/test/piece-reader.test.js +162 -162
- package/test/piece-selection-offset.test.js +85 -85
- package/test/piece-store-eviction.test.js +560 -560
- package/test/piece-store-never-refuses.test.js +153 -153
- package/test/piece-store-reservations.test.js +318 -318
- package/test/piece-store-slow-disk.test.js +159 -159
- package/test/piece-tail.test.js +97 -97
- package/test/plan-host-timings.test.js +68 -68
- package/test/plan-is-steady.test.js +225 -225
- package/test/priority-map-download.test.js +174 -174
- package/test/priority-map-emptied.test.js +204 -204
- package/test/priority-map-per-output.test.js +215 -215
- package/test/priority-map.test.js +163 -163
- package/test/probe-wedge-certainty.test.js +60 -60
- package/test/produced-copy-choice.test.js +213 -213
- package/test/published-timeline.test.js +83 -83
- package/test/quality-offer.test.js +162 -162
- package/test/quality-variants.test.js +1229 -1229
- package/test/rate-control-in-the-output.test.js +180 -180
- package/test/read-survives-withdrawal.test.js +164 -164
- package/test/read-window.test.js +365 -365
- package/test/resume-warm.test.js +39 -39
- package/test/run-graph-drift.test.js +37 -37
- package/test/run-inputs.test.js +80 -80
- package/test/run-intervals.test.js +100 -100
- package/test/run-position-follows-published-grid.test.js +89 -89
- package/test/run-progress.test.js +65 -65
- package/test/same-height-switch.test.js +664 -664
- package/test/seek-frees-the-viewers-requests.test.js +217 -217
- package/test/seek-landing.test.js +109 -109
- package/test/seek-target-not-superseded.test.js +195 -195
- package/test/segment-closed-on-both-branches.test.js +96 -96
- package/test/segment-cut-times.test.js +63 -63
- package/test/segment-demand.test.js +80 -80
- package/test/segment-hold-supersede.test.js +91 -91
- package/test/segment-judged-by-its-own-header.test.js +221 -221
- package/test/segment-serve-wiring.test.js +475 -475
- package/test/segment-store-eviction.test.js +247 -247
- package/test/segment-store.test.js +323 -323
- package/test/segment-true-start.test.js +86 -86
- package/test/segments-are-shared.test.js +176 -176
- package/test/send-chunk-bytes.test.js +86 -86
- package/test/serving-existing-output.test.js +102 -102
- package/test/serving-output.test.js +102 -102
- package/test/shared-piece-store.test.js +310 -310
- package/test/sidecar-naming.test.js +142 -142
- package/test/source-file.test.js +133 -133
- package/test/source-files-route.test.js +103 -103
- package/test/source-registry.test.js +18 -0
- package/test/split-a-stretch.test.js +139 -139
- package/test/stale-request-after-seek.test.js +158 -158
- package/test/startup-readings.test.js +124 -124
- package/test/stream-route.test.js +182 -182
- package/test/subtitle-cue-framing.test.js +200 -200
- package/test/subtitle-cue-source.test.js +102 -102
- package/test/subtitle-cue-walk.test.js +370 -370
- package/test/subtitle-cursor.test.js +63 -63
- package/test/subtitle-defaults.test.js +118 -118
- package/test/subtitle-language.test.js +252 -252
- package/test/subtitle-track-numbering.test.js +386 -386
- package/test/supply-margin.test.js +177 -177
- package/test/swarm-follows-readers.test.js +174 -174
- package/test/swarm-reach.test.js +130 -130
- package/test/swarm-selection.test.js +220 -220
- package/test/tail-duplication.test.js +167 -167
- package/test/the-channel-has-both-ends.test.js +91 -91
- package/test/the-torrent-thread-serves-bytes.test.js +107 -107
- package/test/thread-pool.test.js +53 -53
- package/test/timeline.test.js +84 -84
- package/test/torrent-contents.test.js +235 -235
- package/test/torrent-cost.test.js +71 -71
- package/test/torrent-files.test.js +221 -221
- package/test/torrent-naming.test.js +255 -255
- package/test/tracks-begin-together.test.js +209 -209
- package/test/tunnel-renewal.test.js +167 -167
- package/test/two-viewers-one-picture.test.js +407 -407
- package/test/upload-hurry.test.js +186 -186
- package/test/usrsctp-state.test.js +131 -131
- package/test/video-facts.test.js +102 -102
- package/test/viewer-assignments.test.js +143 -143
- package/test/viewer-outputs.test.js +285 -285
- package/test/viewer-presence.test.js +116 -116
- package/test/viewer-requests-admission.test.js +89 -89
- package/test/viewer-states.test.js +189 -189
- package/test/viewer-subtitle-subscription.test.js +96 -96
- package/test/viewer.test.js +139 -139
- package/test/viewing-generations.test.js +390 -390
- package/test/visible-rung.test.js +37 -37
- package/test/wait-ledger.test.js +100 -100
- package/test/webrtc-shim.test.js +149 -149
- package/test/wedge-certainty.test.js +131 -131
- package/test/withdraw-piece-claim.test.js +212 -212
- package/test/worker-channel.test.js +158 -158
- package/utils/client-log-file.js +151 -151
- package/utils/logger.js +312 -312
- package/utils/parse-range.js +25 -25
- package/utils/perf.js +59 -59
package/test/encode-cost.test.js
CHANGED
|
@@ -1,283 +1,283 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file What encoding costs this machine, asked of the object that owns it.
|
|
3
|
-
*
|
|
4
|
-
* The arithmetic itself is exercised end to end by `auto-quality-step` and
|
|
5
|
-
* `quality-variants`, which go through the session manager. What is pinned here
|
|
6
|
-
* is the seam the move created: this object is given the host's readings as a
|
|
7
|
-
* QUESTION rather than a copy, and it holds what an encoder taught it.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import test from "node:test";
|
|
11
|
-
import assert from "node:assert/strict";
|
|
12
|
-
import { EncodeCost } from "../services/encode/quality/EncodeCost.js";
|
|
13
|
-
import { startRunOn } from "./helpers/encode-run.js";
|
|
14
|
-
import { outputSpec } from "./helpers/output-spec.js";
|
|
15
|
-
import { runStateOf } from "../services/encode/encode-run-state.js";
|
|
16
|
-
import { qualityStateOf } from "../services/encode/quality/OutputQualityState.js";
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* @param {object} [readings]
|
|
20
|
-
* @returns {{ cost: EncodeCost, asked: () => number, host: { share: number } }}
|
|
21
|
-
*/
|
|
22
|
-
function costOn(readings = {}) {
|
|
23
|
-
let asked = 0;
|
|
24
|
-
const host = { share: 1 };
|
|
25
|
-
const cost = new EncodeCost({
|
|
26
|
-
outputs: {
|
|
27
|
-
familyOf: (session) => readings.familyOf?.(session) ?? [],
|
|
28
|
-
variantHeightOf: () => 0
|
|
29
|
-
},
|
|
30
|
-
host: () => {
|
|
31
|
-
asked += 1;
|
|
32
|
-
return {
|
|
33
|
-
benchmark: readings.benchmark ?? null,
|
|
34
|
-
decodeModel: null,
|
|
35
|
-
contentionPenalties: null,
|
|
36
|
-
availability: { known: true, share: host.share }
|
|
37
|
-
};
|
|
38
|
-
},
|
|
39
|
-
runningEncoders: () => readings.runningEncoders?.() ?? 0,
|
|
40
|
-
encodersRunningNow: () => 0,
|
|
41
|
-
torrentCostSecFor: () => 0,
|
|
42
|
-
boundBy: async () => readings.boundBy ?? "cpu",
|
|
43
|
-
runsFor: (session) => [...(session.runs ?? [])],
|
|
44
|
-
stateFor: (session) => runStateOf(session.runs),
|
|
45
|
-
progressFor: (session) => session.progress ?? null
|
|
46
|
-
});
|
|
47
|
-
return { cost, asked: () => asked, host };
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* A session with one run going, of the kind this file measures.
|
|
52
|
-
*
|
|
53
|
-
* @param {{ audioOnly?: boolean, transcodeVideo?: boolean, audioSourceTrackIndex?: number, key?: string }} [what]
|
|
54
|
-
* @returns {object}
|
|
55
|
-
*/
|
|
56
|
-
function sessionProducing(what = {}) {
|
|
57
|
-
const session = {
|
|
58
|
-
id: "1111111122223333",
|
|
59
|
-
state: "ready",
|
|
60
|
-
spec: outputSpec({
|
|
61
|
-
audioOnly: what.audioOnly === true,
|
|
62
|
-
transcodeVideo: what.transcodeVideo === true,
|
|
63
|
-
audioSourceTrackIndex: what.audioSourceTrackIndex ?? 0
|
|
64
|
-
}),
|
|
65
|
-
file: { key: what.key ?? "torrent:abc:0", name: "film.mkv" },
|
|
66
|
-
output: { encodeWidth: 0, encodeHeight: 0, outputFps: 25, softwarePreset: null },
|
|
67
|
-
progress: { processedSeconds: 0 },
|
|
68
|
-
runs: new Set()
|
|
69
|
-
};
|
|
70
|
-
startRunOn(session, { from: 0, speedX: 1 });
|
|
71
|
-
return session;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Two readings of one run, far enough apart to be a speed.
|
|
76
|
-
*
|
|
77
|
-
* @param {EncodeCost} cost
|
|
78
|
-
* @param {object} session
|
|
79
|
-
* @param {number} speedX - Seconds of film produced per second of clock.
|
|
80
|
-
* @returns {Promise<void>}
|
|
81
|
-
*/
|
|
82
|
-
async function watchItRun(cost, session, speedX) {
|
|
83
|
-
session.progress.processedSeconds = 0;
|
|
84
|
-
await cost.learnFrom(session);
|
|
85
|
-
// The pair is a DELTA, and the first reading has nothing to be a delta from.
|
|
86
|
-
// Backdated rather than waited for: the window a speed may be read over is
|
|
87
|
-
// seconds, and a test that sleeps through it measures the clock.
|
|
88
|
-
qualityStateOf(session).learnSample.takenAt -= 10_000;
|
|
89
|
-
session.progress.processedSeconds = 10 * speedX;
|
|
90
|
-
await cost.learnFrom(session);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
test("the host is asked at the moment of the question, not when this was built", () => {
|
|
94
|
-
// The share of the machine that is free is re-read every few seconds. Copied
|
|
95
|
-
// into this object when it was made, every later rung would be priced against
|
|
96
|
-
// a machine that has gone.
|
|
97
|
-
const { cost, asked, host } = costOn();
|
|
98
|
-
assert.equal(asked(), 0, "nothing is read until something is asked");
|
|
99
|
-
|
|
100
|
-
cost.sustainableHeights({ heights: [1080], sourceWidth: 1920, sourceHeight: 1080, fps: 24, source: null, transcodeVideo: true, ownHeight: 0 });
|
|
101
|
-
const first = asked();
|
|
102
|
-
assert.ok(first > 0);
|
|
103
|
-
|
|
104
|
-
host.share = 0.1;
|
|
105
|
-
cost.sustainableHeights({ heights: [1080], sourceWidth: 1920, sourceHeight: 1080, fps: 24, source: null, transcodeVideo: true, ownHeight: 0 });
|
|
106
|
-
assert.ok(asked() > first, "and read again on the next question");
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
test("with no benchmark to judge by, every height offered is kept", () => {
|
|
110
|
-
// Nothing measured is not the same as nothing possible. Refusing here would
|
|
111
|
-
// hide the whole ladder on a host whose startup measurement failed.
|
|
112
|
-
const { cost } = costOn({ benchmark: null });
|
|
113
|
-
const kept = cost.sustainableHeights({
|
|
114
|
-
heights: [1080, 720, 480],
|
|
115
|
-
sourceWidth: 1920,
|
|
116
|
-
sourceHeight: 1080,
|
|
117
|
-
fps: 24,
|
|
118
|
-
source: null,
|
|
119
|
-
transcodeVideo: true,
|
|
120
|
-
ownHeight: 0
|
|
121
|
-
});
|
|
122
|
-
assert.deepEqual(kept, [1080, 720, 480]);
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
test("a rung measured below realtime is withdrawn, and a copied source height is not", () => {
|
|
126
|
-
const { cost } = costOn({ benchmark: null });
|
|
127
|
-
const kept = cost.sustainableHeights({
|
|
128
|
-
heights: [1080, 480],
|
|
129
|
-
sourceWidth: 1920,
|
|
130
|
-
sourceHeight: 1080,
|
|
131
|
-
fps: 24,
|
|
132
|
-
source: null,
|
|
133
|
-
// The base copies its picture, so the source height costs no encoder.
|
|
134
|
-
transcodeVideo: false,
|
|
135
|
-
ownHeight: 1080,
|
|
136
|
-
measuredHeights: new Map([[480, 0.4]])
|
|
137
|
-
});
|
|
138
|
-
assert.deepEqual(kept, [1080], "the copy stays; the rung seen failing does not");
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
test("what an encoder taught this host is learned here, and held here", () => {
|
|
142
|
-
// The three that learn were methods of the session manager, writing into maps
|
|
143
|
-
// this object owned — one keeper, a different author, and the maps public for
|
|
144
|
-
// it. Nothing outside can write a price now, and nothing outside needs to.
|
|
145
|
-
const { cost } = costOn();
|
|
146
|
-
|
|
147
|
-
assert.equal(typeof cost.learnFrom, "function");
|
|
148
|
-
assert.equal(cost.copyCost, undefined, "the store is not the surface");
|
|
149
|
-
assert.equal(cost.decodeCostFor("torrent:abc:0"), null, "and nothing is known until an encoder says so");
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
test("a copy is priced from what it was seen doing", async () => {
|
|
153
|
-
const { cost } = costOn();
|
|
154
|
-
const session = sessionProducing({ transcodeVideo: false });
|
|
155
|
-
|
|
156
|
-
await watchItRun(cost, session, 8);
|
|
157
|
-
|
|
158
|
-
assert.equal(cost.copyVersionFor("torrent:abc:0"), 1, "the price is published, so every offer recomputes");
|
|
159
|
-
assert.equal(
|
|
160
|
-
qualityStateOf(session).lastAloneSpeed.toFixed(2),
|
|
161
|
-
"8.00",
|
|
162
|
-
"and what it did with the machine to itself is on the session, which withdraws a step seen failing"
|
|
163
|
-
);
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
test("a reading taken while the swarm is short is not filed as the price of this host", async () => {
|
|
167
|
-
// A run starved of torrent data reports a speed that measures the swarm. Filed
|
|
168
|
-
// as a price it reads as more work per second than the machine has, and every
|
|
169
|
-
// quality step is then refused on the download's account.
|
|
170
|
-
const { cost } = costOn({ boundBy: "download" });
|
|
171
|
-
const session = sessionProducing({ transcodeVideo: false });
|
|
172
|
-
|
|
173
|
-
await watchItRun(cost, session, 0.3);
|
|
174
|
-
|
|
175
|
-
assert.equal(cost.copyVersionFor("torrent:abc:0"), 0, "nothing was learned about the machine");
|
|
176
|
-
assert.equal(qualityStateOf(session).lastAloneSpeed, undefined, "and nothing claims this rung was seen failing");
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
test("a picture's reading taken beside another encoder is not filed", async () => {
|
|
180
|
-
// It contains that other encoder's work, and the budget ADDS the same work
|
|
181
|
-
// again when it predicts — so the price is counted twice and grows with every
|
|
182
|
-
// reading. Measured in the field 2026-08-15: copying, whose truth is 7.9x, was
|
|
183
|
-
// learned as 2.03x, and the quality menu collapsed to one height.
|
|
184
|
-
const { cost } = costOn({ runningEncoders: () => 2 });
|
|
185
|
-
const session = sessionProducing({ transcodeVideo: false });
|
|
186
|
-
|
|
187
|
-
await watchItRun(cost, session, 8);
|
|
188
|
-
|
|
189
|
-
assert.equal(cost.copyVersionFor("torrent:abc:0"), 0);
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
test("one reading is not a speed", async () => {
|
|
193
|
-
const { cost } = costOn();
|
|
194
|
-
const session = sessionProducing({ transcodeVideo: false });
|
|
195
|
-
session.progress.processedSeconds = 40;
|
|
196
|
-
|
|
197
|
-
await cost.learnFrom(session);
|
|
198
|
-
|
|
199
|
-
assert.equal(cost.copyVersionFor("torrent:abc:0"), 0, "a delta needs two readings of one run");
|
|
200
|
-
assert.ok(qualityStateOf(session).learnSample, "and the first is kept to be the other half of the next");
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
test("a pair that straddles a restart measures the seek, not the host", async () => {
|
|
204
|
-
const { cost } = costOn();
|
|
205
|
-
const session = sessionProducing({ transcodeVideo: false });
|
|
206
|
-
await cost.learnFrom(session);
|
|
207
|
-
qualityStateOf(session).learnSample.takenAt -= 10_000;
|
|
208
|
-
// A new run, at another place in the film: twenty minutes of film against five
|
|
209
|
-
// seconds of clock is a seek, and filed as a price it admits every step there
|
|
210
|
-
// is.
|
|
211
|
-
session.runs = new Set();
|
|
212
|
-
startRunOn(session, { from: 300, speedX: 1 });
|
|
213
|
-
session.progress.processedSeconds = 1200;
|
|
214
|
-
|
|
215
|
-
await cost.learnFrom(session);
|
|
216
|
-
|
|
217
|
-
assert.equal(cost.copyVersionFor("torrent:abc:0"), 0);
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
test("two soundtracks of one file are priced apart", async () => {
|
|
221
|
-
// The key names the TRACK, not the file. A dub and the original run at
|
|
222
|
-
// different speeds — different codecs, different channel counts — and one key
|
|
223
|
-
// for both files each over the other's readings.
|
|
224
|
-
const { cost } = costOn();
|
|
225
|
-
const first = sessionProducing({ audioOnly: true });
|
|
226
|
-
const second = sessionProducing({ audioOnly: true, audioSourceTrackIndex: 1 });
|
|
227
|
-
|
|
228
|
-
await watchItRun(cost, first, 40);
|
|
229
|
-
|
|
230
|
-
assert.equal(cost.audioVersionFor(first), 1);
|
|
231
|
-
assert.equal(cost.audioVersionFor(second), 0, "the other track has been told nothing about itself");
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
test("a soundtrack's own share is what is left after everything else running is paid for", async () => {
|
|
235
|
-
// A rendition runs for exactly as long as the picture it accompanies, so
|
|
236
|
-
// "alone" is a state it is never in and the price stayed unmeasured for ever.
|
|
237
|
-
// Its share is recovered by subtracting what the machine is already known to
|
|
238
|
-
// be spending — which is only possible once that other work HAS a price.
|
|
239
|
-
const picture = sessionProducing({ transcodeVideo: false });
|
|
240
|
-
const sound = sessionProducing({ audioOnly: true });
|
|
241
|
-
let running = 1;
|
|
242
|
-
const { cost } = costOn({
|
|
243
|
-
runningEncoders: () => running,
|
|
244
|
-
familyOf: () => [picture, sound]
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
// The picture alone first, so copying has a price: 8x, i.e. 0.125 s/s.
|
|
248
|
-
await watchItRun(cost, picture, 8);
|
|
249
|
-
assert.equal(cost.copyVersionFor("torrent:abc:0"), 1);
|
|
250
|
-
|
|
251
|
-
// Now the pair. The reading off the soundtrack contains both, at 4x — 0.25 s/s
|
|
252
|
-
// together — so the sound's own share is 0.25 − 0.125 = 0.125 s/s.
|
|
253
|
-
running = 2;
|
|
254
|
-
await watchItRun(cost, sound, 4);
|
|
255
|
-
|
|
256
|
-
assert.equal(cost.audioVersionFor(sound), 1, "the soundtrack has a price at last");
|
|
257
|
-
// What was filed, read back the way the product reads it: the sum of what
|
|
258
|
-
// everything OTHER than the picture is costing. Charged the whole reading
|
|
259
|
-
// instead of its own share, a soundtrack is priced at twice the truth and
|
|
260
|
-
// refuses quality steps the host could hold.
|
|
261
|
-
running = 1;
|
|
262
|
-
assert.equal(
|
|
263
|
-
cost.pricedConcurrentCost(picture).toFixed(3),
|
|
264
|
-
"0.125",
|
|
265
|
-
"0.25 s/s together, less the 0.125 s/s the copy was already known to cost"
|
|
266
|
-
);
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
test("nothing is attributed to a soundtrack while something running has no price", async () => {
|
|
270
|
-
const picture = sessionProducing({ transcodeVideo: true });
|
|
271
|
-
const sound = sessionProducing({ audioOnly: true });
|
|
272
|
-
const { cost } = costOn({
|
|
273
|
-
runningEncoders: () => 2,
|
|
274
|
-
// A re-encoded picture with no benchmark to price it by and no speed of its
|
|
275
|
-
// own: unpriced work, which would otherwise land in the soundtrack's account
|
|
276
|
-
// and refuse quality steps on it.
|
|
277
|
-
familyOf: () => [picture, sound]
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
await watchItRun(cost, sound, 4);
|
|
281
|
-
|
|
282
|
-
assert.equal(cost.audioVersionFor(sound), 0);
|
|
283
|
-
});
|
|
1
|
+
/**
|
|
2
|
+
* @file What encoding costs this machine, asked of the object that owns it.
|
|
3
|
+
*
|
|
4
|
+
* The arithmetic itself is exercised end to end by `auto-quality-step` and
|
|
5
|
+
* `quality-variants`, which go through the session manager. What is pinned here
|
|
6
|
+
* is the seam the move created: this object is given the host's readings as a
|
|
7
|
+
* QUESTION rather than a copy, and it holds what an encoder taught it.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import test from "node:test";
|
|
11
|
+
import assert from "node:assert/strict";
|
|
12
|
+
import { EncodeCost } from "../services/encode/quality/EncodeCost.js";
|
|
13
|
+
import { startRunOn } from "./helpers/encode-run.js";
|
|
14
|
+
import { outputSpec } from "./helpers/output-spec.js";
|
|
15
|
+
import { runStateOf } from "../services/encode/encode-run-state.js";
|
|
16
|
+
import { qualityStateOf } from "../services/encode/quality/OutputQualityState.js";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {object} [readings]
|
|
20
|
+
* @returns {{ cost: EncodeCost, asked: () => number, host: { share: number } }}
|
|
21
|
+
*/
|
|
22
|
+
function costOn(readings = {}) {
|
|
23
|
+
let asked = 0;
|
|
24
|
+
const host = { share: 1 };
|
|
25
|
+
const cost = new EncodeCost({
|
|
26
|
+
outputs: {
|
|
27
|
+
familyOf: (session) => readings.familyOf?.(session) ?? [],
|
|
28
|
+
variantHeightOf: () => 0
|
|
29
|
+
},
|
|
30
|
+
host: () => {
|
|
31
|
+
asked += 1;
|
|
32
|
+
return {
|
|
33
|
+
benchmark: readings.benchmark ?? null,
|
|
34
|
+
decodeModel: null,
|
|
35
|
+
contentionPenalties: null,
|
|
36
|
+
availability: { known: true, share: host.share }
|
|
37
|
+
};
|
|
38
|
+
},
|
|
39
|
+
runningEncoders: () => readings.runningEncoders?.() ?? 0,
|
|
40
|
+
encodersRunningNow: () => 0,
|
|
41
|
+
torrentCostSecFor: () => 0,
|
|
42
|
+
boundBy: async () => readings.boundBy ?? "cpu",
|
|
43
|
+
runsFor: (session) => [...(session.runs ?? [])],
|
|
44
|
+
stateFor: (session) => runStateOf(session.runs),
|
|
45
|
+
progressFor: (session) => session.progress ?? null
|
|
46
|
+
});
|
|
47
|
+
return { cost, asked: () => asked, host };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* A session with one run going, of the kind this file measures.
|
|
52
|
+
*
|
|
53
|
+
* @param {{ audioOnly?: boolean, transcodeVideo?: boolean, audioSourceTrackIndex?: number, key?: string }} [what]
|
|
54
|
+
* @returns {object}
|
|
55
|
+
*/
|
|
56
|
+
function sessionProducing(what = {}) {
|
|
57
|
+
const session = {
|
|
58
|
+
id: "1111111122223333",
|
|
59
|
+
state: "ready",
|
|
60
|
+
spec: outputSpec({
|
|
61
|
+
audioOnly: what.audioOnly === true,
|
|
62
|
+
transcodeVideo: what.transcodeVideo === true,
|
|
63
|
+
audioSourceTrackIndex: what.audioSourceTrackIndex ?? 0
|
|
64
|
+
}),
|
|
65
|
+
file: { key: what.key ?? "torrent:abc:0", name: "film.mkv" },
|
|
66
|
+
output: { encodeWidth: 0, encodeHeight: 0, outputFps: 25, softwarePreset: null },
|
|
67
|
+
progress: { processedSeconds: 0 },
|
|
68
|
+
runs: new Set()
|
|
69
|
+
};
|
|
70
|
+
startRunOn(session, { from: 0, speedX: 1 });
|
|
71
|
+
return session;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Two readings of one run, far enough apart to be a speed.
|
|
76
|
+
*
|
|
77
|
+
* @param {EncodeCost} cost
|
|
78
|
+
* @param {object} session
|
|
79
|
+
* @param {number} speedX - Seconds of film produced per second of clock.
|
|
80
|
+
* @returns {Promise<void>}
|
|
81
|
+
*/
|
|
82
|
+
async function watchItRun(cost, session, speedX) {
|
|
83
|
+
session.progress.processedSeconds = 0;
|
|
84
|
+
await cost.learnFrom(session);
|
|
85
|
+
// The pair is a DELTA, and the first reading has nothing to be a delta from.
|
|
86
|
+
// Backdated rather than waited for: the window a speed may be read over is
|
|
87
|
+
// seconds, and a test that sleeps through it measures the clock.
|
|
88
|
+
qualityStateOf(session).learnSample.takenAt -= 10_000;
|
|
89
|
+
session.progress.processedSeconds = 10 * speedX;
|
|
90
|
+
await cost.learnFrom(session);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
test("the host is asked at the moment of the question, not when this was built", () => {
|
|
94
|
+
// The share of the machine that is free is re-read every few seconds. Copied
|
|
95
|
+
// into this object when it was made, every later rung would be priced against
|
|
96
|
+
// a machine that has gone.
|
|
97
|
+
const { cost, asked, host } = costOn();
|
|
98
|
+
assert.equal(asked(), 0, "nothing is read until something is asked");
|
|
99
|
+
|
|
100
|
+
cost.sustainableHeights({ heights: [1080], sourceWidth: 1920, sourceHeight: 1080, fps: 24, source: null, transcodeVideo: true, ownHeight: 0 });
|
|
101
|
+
const first = asked();
|
|
102
|
+
assert.ok(first > 0);
|
|
103
|
+
|
|
104
|
+
host.share = 0.1;
|
|
105
|
+
cost.sustainableHeights({ heights: [1080], sourceWidth: 1920, sourceHeight: 1080, fps: 24, source: null, transcodeVideo: true, ownHeight: 0 });
|
|
106
|
+
assert.ok(asked() > first, "and read again on the next question");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("with no benchmark to judge by, every height offered is kept", () => {
|
|
110
|
+
// Nothing measured is not the same as nothing possible. Refusing here would
|
|
111
|
+
// hide the whole ladder on a host whose startup measurement failed.
|
|
112
|
+
const { cost } = costOn({ benchmark: null });
|
|
113
|
+
const kept = cost.sustainableHeights({
|
|
114
|
+
heights: [1080, 720, 480],
|
|
115
|
+
sourceWidth: 1920,
|
|
116
|
+
sourceHeight: 1080,
|
|
117
|
+
fps: 24,
|
|
118
|
+
source: null,
|
|
119
|
+
transcodeVideo: true,
|
|
120
|
+
ownHeight: 0
|
|
121
|
+
});
|
|
122
|
+
assert.deepEqual(kept, [1080, 720, 480]);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("a rung measured below realtime is withdrawn, and a copied source height is not", () => {
|
|
126
|
+
const { cost } = costOn({ benchmark: null });
|
|
127
|
+
const kept = cost.sustainableHeights({
|
|
128
|
+
heights: [1080, 480],
|
|
129
|
+
sourceWidth: 1920,
|
|
130
|
+
sourceHeight: 1080,
|
|
131
|
+
fps: 24,
|
|
132
|
+
source: null,
|
|
133
|
+
// The base copies its picture, so the source height costs no encoder.
|
|
134
|
+
transcodeVideo: false,
|
|
135
|
+
ownHeight: 1080,
|
|
136
|
+
measuredHeights: new Map([[480, 0.4]])
|
|
137
|
+
});
|
|
138
|
+
assert.deepEqual(kept, [1080], "the copy stays; the rung seen failing does not");
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("what an encoder taught this host is learned here, and held here", () => {
|
|
142
|
+
// The three that learn were methods of the session manager, writing into maps
|
|
143
|
+
// this object owned — one keeper, a different author, and the maps public for
|
|
144
|
+
// it. Nothing outside can write a price now, and nothing outside needs to.
|
|
145
|
+
const { cost } = costOn();
|
|
146
|
+
|
|
147
|
+
assert.equal(typeof cost.learnFrom, "function");
|
|
148
|
+
assert.equal(cost.copyCost, undefined, "the store is not the surface");
|
|
149
|
+
assert.equal(cost.decodeCostFor("torrent:abc:0"), null, "and nothing is known until an encoder says so");
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("a copy is priced from what it was seen doing", async () => {
|
|
153
|
+
const { cost } = costOn();
|
|
154
|
+
const session = sessionProducing({ transcodeVideo: false });
|
|
155
|
+
|
|
156
|
+
await watchItRun(cost, session, 8);
|
|
157
|
+
|
|
158
|
+
assert.equal(cost.copyVersionFor("torrent:abc:0"), 1, "the price is published, so every offer recomputes");
|
|
159
|
+
assert.equal(
|
|
160
|
+
qualityStateOf(session).lastAloneSpeed.toFixed(2),
|
|
161
|
+
"8.00",
|
|
162
|
+
"and what it did with the machine to itself is on the session, which withdraws a step seen failing"
|
|
163
|
+
);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("a reading taken while the swarm is short is not filed as the price of this host", async () => {
|
|
167
|
+
// A run starved of torrent data reports a speed that measures the swarm. Filed
|
|
168
|
+
// as a price it reads as more work per second than the machine has, and every
|
|
169
|
+
// quality step is then refused on the download's account.
|
|
170
|
+
const { cost } = costOn({ boundBy: "download" });
|
|
171
|
+
const session = sessionProducing({ transcodeVideo: false });
|
|
172
|
+
|
|
173
|
+
await watchItRun(cost, session, 0.3);
|
|
174
|
+
|
|
175
|
+
assert.equal(cost.copyVersionFor("torrent:abc:0"), 0, "nothing was learned about the machine");
|
|
176
|
+
assert.equal(qualityStateOf(session).lastAloneSpeed, undefined, "and nothing claims this rung was seen failing");
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("a picture's reading taken beside another encoder is not filed", async () => {
|
|
180
|
+
// It contains that other encoder's work, and the budget ADDS the same work
|
|
181
|
+
// again when it predicts — so the price is counted twice and grows with every
|
|
182
|
+
// reading. Measured in the field 2026-08-15: copying, whose truth is 7.9x, was
|
|
183
|
+
// learned as 2.03x, and the quality menu collapsed to one height.
|
|
184
|
+
const { cost } = costOn({ runningEncoders: () => 2 });
|
|
185
|
+
const session = sessionProducing({ transcodeVideo: false });
|
|
186
|
+
|
|
187
|
+
await watchItRun(cost, session, 8);
|
|
188
|
+
|
|
189
|
+
assert.equal(cost.copyVersionFor("torrent:abc:0"), 0);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test("one reading is not a speed", async () => {
|
|
193
|
+
const { cost } = costOn();
|
|
194
|
+
const session = sessionProducing({ transcodeVideo: false });
|
|
195
|
+
session.progress.processedSeconds = 40;
|
|
196
|
+
|
|
197
|
+
await cost.learnFrom(session);
|
|
198
|
+
|
|
199
|
+
assert.equal(cost.copyVersionFor("torrent:abc:0"), 0, "a delta needs two readings of one run");
|
|
200
|
+
assert.ok(qualityStateOf(session).learnSample, "and the first is kept to be the other half of the next");
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test("a pair that straddles a restart measures the seek, not the host", async () => {
|
|
204
|
+
const { cost } = costOn();
|
|
205
|
+
const session = sessionProducing({ transcodeVideo: false });
|
|
206
|
+
await cost.learnFrom(session);
|
|
207
|
+
qualityStateOf(session).learnSample.takenAt -= 10_000;
|
|
208
|
+
// A new run, at another place in the film: twenty minutes of film against five
|
|
209
|
+
// seconds of clock is a seek, and filed as a price it admits every step there
|
|
210
|
+
// is.
|
|
211
|
+
session.runs = new Set();
|
|
212
|
+
startRunOn(session, { from: 300, speedX: 1 });
|
|
213
|
+
session.progress.processedSeconds = 1200;
|
|
214
|
+
|
|
215
|
+
await cost.learnFrom(session);
|
|
216
|
+
|
|
217
|
+
assert.equal(cost.copyVersionFor("torrent:abc:0"), 0);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
test("two soundtracks of one file are priced apart", async () => {
|
|
221
|
+
// The key names the TRACK, not the file. A dub and the original run at
|
|
222
|
+
// different speeds — different codecs, different channel counts — and one key
|
|
223
|
+
// for both files each over the other's readings.
|
|
224
|
+
const { cost } = costOn();
|
|
225
|
+
const first = sessionProducing({ audioOnly: true });
|
|
226
|
+
const second = sessionProducing({ audioOnly: true, audioSourceTrackIndex: 1 });
|
|
227
|
+
|
|
228
|
+
await watchItRun(cost, first, 40);
|
|
229
|
+
|
|
230
|
+
assert.equal(cost.audioVersionFor(first), 1);
|
|
231
|
+
assert.equal(cost.audioVersionFor(second), 0, "the other track has been told nothing about itself");
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test("a soundtrack's own share is what is left after everything else running is paid for", async () => {
|
|
235
|
+
// A rendition runs for exactly as long as the picture it accompanies, so
|
|
236
|
+
// "alone" is a state it is never in and the price stayed unmeasured for ever.
|
|
237
|
+
// Its share is recovered by subtracting what the machine is already known to
|
|
238
|
+
// be spending — which is only possible once that other work HAS a price.
|
|
239
|
+
const picture = sessionProducing({ transcodeVideo: false });
|
|
240
|
+
const sound = sessionProducing({ audioOnly: true });
|
|
241
|
+
let running = 1;
|
|
242
|
+
const { cost } = costOn({
|
|
243
|
+
runningEncoders: () => running,
|
|
244
|
+
familyOf: () => [picture, sound]
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// The picture alone first, so copying has a price: 8x, i.e. 0.125 s/s.
|
|
248
|
+
await watchItRun(cost, picture, 8);
|
|
249
|
+
assert.equal(cost.copyVersionFor("torrent:abc:0"), 1);
|
|
250
|
+
|
|
251
|
+
// Now the pair. The reading off the soundtrack contains both, at 4x — 0.25 s/s
|
|
252
|
+
// together — so the sound's own share is 0.25 − 0.125 = 0.125 s/s.
|
|
253
|
+
running = 2;
|
|
254
|
+
await watchItRun(cost, sound, 4);
|
|
255
|
+
|
|
256
|
+
assert.equal(cost.audioVersionFor(sound), 1, "the soundtrack has a price at last");
|
|
257
|
+
// What was filed, read back the way the product reads it: the sum of what
|
|
258
|
+
// everything OTHER than the picture is costing. Charged the whole reading
|
|
259
|
+
// instead of its own share, a soundtrack is priced at twice the truth and
|
|
260
|
+
// refuses quality steps the host could hold.
|
|
261
|
+
running = 1;
|
|
262
|
+
assert.equal(
|
|
263
|
+
cost.pricedConcurrentCost(picture).toFixed(3),
|
|
264
|
+
"0.125",
|
|
265
|
+
"0.25 s/s together, less the 0.125 s/s the copy was already known to cost"
|
|
266
|
+
);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
test("nothing is attributed to a soundtrack while something running has no price", async () => {
|
|
270
|
+
const picture = sessionProducing({ transcodeVideo: true });
|
|
271
|
+
const sound = sessionProducing({ audioOnly: true });
|
|
272
|
+
const { cost } = costOn({
|
|
273
|
+
runningEncoders: () => 2,
|
|
274
|
+
// A re-encoded picture with no benchmark to price it by and no speed of its
|
|
275
|
+
// own: unpriced work, which would otherwise land in the soundtrack's account
|
|
276
|
+
// and refuse quality steps on it.
|
|
277
|
+
familyOf: () => [picture, sound]
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
await watchItRun(cost, sound, 4);
|
|
281
|
+
|
|
282
|
+
assert.equal(cost.audioVersionFor(sound), 0);
|
|
283
|
+
});
|