@torrent-tv/proxy 2.89.7 → 2.89.8
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 +358 -338
- package/CHANGELOG.md +2340 -2335
- package/Dockerfile +30 -30
- package/LICENSE +16 -16
- package/README.md +369 -369
- package/assets/calibration/NOTICE.md +100 -100
- package/assets/calibration/audio/NOTICE.md +19 -19
- package/assets/calibration/audio/manifest.json +98 -98
- package/assets/diagnostics/sctpstate.gdb +31 -31
- package/bin/cli.js +684 -684
- package/bin/thread-pool.js +30 -30
- package/biome.json +412 -412
- package/docs/browser-proxy-contract.md +155 -155
- package/docs/container-architecture.md +273 -273
- package/docs/download-architecture.md +261 -261
- package/docs/encode-architecture.md +765 -765
- package/docs/encode-run-state.md +96 -96
- package/docs/logs.md +97 -97
- package/docs/playback-start-forecast.md +113 -113
- package/docs/socks5-egress-research.md +114 -114
- package/docs/storage-architecture.md +273 -273
- 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 +51 -50
- 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/audio-tracks/post.js +29 -29
- package/routes/api/playback-plan/post.js +53 -53
- package/routes/api/sources/files/get.js +106 -106
- package/routes/api/sources/post.js +36 -36
- package/routes/api/sources/stats/get.js +132 -132
- package/routes/api/sources/warm/post.js +217 -217
- 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 +138 -138
- package/routes/api/transcode-sessions/post.js +178 -178
- 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 +416 -416
- package/routes/transcode/audio-file/get.js +64 -64
- package/routes/transcode/audio-warm/get.js +95 -95
- 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/build-audio-samples.mjs +31 -31
- package/scripts/render-run-graph.js +173 -173
- package/server.js +748 -748
- package/services/encode/CoverageMap.js +428 -428
- package/services/encode/CushionReport.js +345 -345
- package/services/encode/EncodeAdmission.js +376 -376
- package/services/encode/EncodeOrchestrator.js +1192 -1192
- package/services/encode/EncodePlan.js +1368 -1368
- package/services/encode/EncodeRun.js +36 -12
- 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 +784 -784
- package/services/encode/OutputTimes.js +579 -579
- package/services/encode/QsvEncoder.js +58 -58
- package/services/encode/Renditions.js +2283 -2283
- package/services/encode/RunClock.js +87 -87
- package/services/encode/RunProgress.js +79 -79
- 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 +418 -418
- package/services/encode/audio-calibration.js +67 -67
- package/services/encode/audio-work-rate.js +15 -15
- 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 +116 -116
- package/services/encode/encode-run-state.js +551 -551
- package/services/encode/encoder-readings.js +43 -43
- 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 +24 -24
- 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 +1043 -1043
- package/services/encode/quality/HostLoad.js +525 -525
- package/services/encode/quality/HostTimings.js +249 -249
- package/services/encode/quality/OutputQualityState.js +24 -24
- package/services/encode/quality/QualityController.js +758 -758
- package/services/encode/quality/QualityOffer.js +416 -416
- package/services/encode/quality/drain-threat.js +47 -47
- package/services/encode/quality/link-budget.js +337 -337
- package/services/encode/quality/output-format.js +319 -319
- 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 +757 -757
- package/services/encode/run-costs.js +155 -155
- package/services/encode/run-inputs.js +53 -53
- package/services/encode/segment-formats/fmp4.js +476 -476
- package/services/encode/segment-formats/init-compat.js +324 -324
- 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 +202 -202
- package/services/media/KeyframeTables.js +356 -356
- package/services/media/SourceFile.js +298 -298
- package/services/media/SubtitleCues.js +592 -592
- package/services/media/SubtitleOrchestrator.js +167 -167
- package/services/media/audio-inventory.js +299 -299
- package/services/media/container/AviContainer.js +268 -268
- package/services/media/container/Container.js +632 -632
- package/services/media/container/ContainerFactory.js +38 -38
- package/services/media/container/KeyframeTable.js +272 -272
- package/services/media/container/MatroskaContainer.js +1031 -1031
- package/services/media/container/Mp4Container.js +1354 -1354
- package/services/media/container/SubtitleFileContainer.js +339 -339
- package/services/media/container/ebml-reader.js +159 -159
- package/services/media/container/ebml-stream.js +174 -174
- package/services/media/container/matroska-clusters.js +1165 -1165
- package/services/media/container/unavailable.js +77 -77
- 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/playback-planner.js +931 -931
- package/services/media/tracks/AudioTrack.js +143 -143
- package/services/media/tracks/ContainerTrack.js +84 -84
- package/services/media/tracks/ImageSubtitleTrack.js +19 -19
- package/services/media/tracks/SubtitleTrack.js +38 -38
- package/services/media/tracks/TextSubtitleTrack.js +295 -295
- package/services/media/tracks/VideoTrack.js +33 -33
- package/services/media/tracks/audio-bound.js +233 -233
- 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 +1152 -1152
- package/services/server/ViewerRequests.js +785 -785
- package/services/server/WaitLedger.js +142 -142
- package/services/server/controllers/PlaybackController.js +40 -40
- 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 +1029 -1029
- 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 +539 -539
- package/services/storage/piece-store/piece-lru.js +495 -495
- package/services/storage/piece-store/shared-piece-store.js +1937 -1937
- package/services/storage/returns.js +103 -103
- package/services/storage/segment-store/SegmentStore.js +1294 -1294
- package/services/storage/wire.js +175 -175
- package/services/torrent/Contents.js +382 -382
- 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/episode-naming.js +182 -182
- 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 +264 -264
- package/services/torrent/torrent-pool.js +2688 -2688
- package/services/torrent/worker/background-fill.js +213 -213
- package/services/torrent/worker/channel.js +271 -271
- package/services/torrent/worker/client.js +836 -836
- 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 +231 -231
- package/services/torrent/worker/install-webrtc-shim.js +32 -32
- package/services/torrent/worker/piece-reader.js +1078 -1078
- package/services/torrent/worker/pool-adapter.js +371 -371
- package/services/torrent/worker/protocol.js +184 -184
- package/services/torrent/worker/resume-warm.js +107 -107
- package/services/torrent/worker/webrtc-shim.js +219 -219
- package/services/torrent/worker/worker.js +1137 -1137
- package/services/transport/data-channel-handler.js +1373 -1373
- 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 +421 -421
- package/services/viewer/PriorityOrchestrator.js +325 -325
- package/services/viewer/Viewer.js +691 -691
- 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/media-time.js +355 -355
- package/services/viewer/playback-readiness.js +635 -635
- package/services/viewer/positions.js +126 -126
- package/services/viewer/report-intake.js +46 -46
- package/store/source-registry.js +71 -71
- package/test/asset-paths.test.js +28 -28
- package/test/audio-buffer-continuity.test.js +77 -77
- package/test/audio-calibration.test.js +23 -23
- package/test/audio-inventory.test.js +212 -212
- package/test/audio-track-bitrate.test.js +40 -40
- package/test/auto-quality-step.test.js +762 -762
- 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/coalesce.test.js +39 -39
- 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 +233 -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/data-channel-viewer-lifecycle.test.js +92 -92
- package/test/dead-torrent-handle.test.js +52 -52
- package/test/declared-rates.test.js +116 -116
- package/test/declared-tracks.test.js +43 -43
- package/test/decode-cost-fit.test.js +202 -202
- package/test/decode-cost.test.js +475 -475
- 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 +275 -275
- package/test/encode-exit.test.js +111 -111
- package/test/encode-orchestrator.test.js +426 -426
- package/test/encode-plan-viewers.test.js +725 -725
- package/test/encode-plan.test.js +545 -545
- package/test/encode-run-state.test.js +314 -314
- package/test/encode-run.test.js +20 -0
- 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 +68 -68
- package/test/episode-naming.test.js +131 -131
- 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/fixtures/audio-frame-boundary.json +1 -1
- 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 +36 -38
- package/test/held-read-ownership.test.js +77 -77
- package/test/held-request-width.test.js +157 -157
- package/test/helpers/encode-run.js +248 -240
- package/test/helpers/held-file.js +77 -77
- package/test/helpers/manager.js +62 -62
- package/test/helpers/matroska-file.js +296 -296
- 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 +123 -123
- 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 +291 -291
- package/test/matroska-cluster-discovery.test.js +323 -323
- package/test/matroska-cues-track.test.js +192 -192
- package/test/media-time.test.js +62 -62
- 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 +273 -273
- 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 +207 -207
- 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 +92 -87
- 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 +401 -401
- 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 +595 -595
- package/test/piece-store-never-refuses.test.js +153 -153
- package/test/piece-store-reservations.test.js +403 -403
- 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/playback-readiness.test.js +518 -518
- package/test/playback-start-regressions.test.js +101 -101
- 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 +160 -160
- package/test/quality-variants.test.js +1274 -1270
- package/test/rate-control-in-the-output.test.js +181 -180
- package/test/read-survives-withdrawal.test.js +164 -164
- package/test/read-window.test.js +365 -365
- package/test/reads-not-yet-arrived.test.js +218 -218
- 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 +64 -64
- package/test/same-height-switch.test.js +663 -663
- 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 +114 -114
- 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 +222 -222
- package/test/segment-serve-wiring.test.js +481 -481
- package/test/segment-store-eviction.test.js +247 -247
- package/test/segment-store.test.js +352 -352
- 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 +111 -111
- 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/soundtrack-rate-bound.test.js +289 -289
- package/test/source-file.test.js +133 -133
- package/test/source-files-route.test.js +113 -113
- package/test/source-registry.test.js +18 -18
- 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 +237 -237
- 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 +385 -385
- 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-metadata.test.js +15 -15
- package/test/subtitle-plan-waits.test.js +125 -125
- package/test/subtitle-track-numbering.test.js +391 -391
- package/test/supply-margin.test.js +190 -190
- 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 +508 -508
- 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 +92 -92
- package/test/viewer-starts-on-map.test.js +27 -27
- package/test/viewer-states.test.js +192 -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/whole-file-held-read.test.js +30 -30
- 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/coalesce.js +41 -41
- package/utils/logger.js +312 -312
- package/utils/parse-range.js +25 -25
- package/utils/perf.js +59 -59
- package/utils/torrent-source-key.js +38 -38
package/test/encode-cost.test.js
CHANGED
|
@@ -1,275 +1,275 @@
|
|
|
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 { measureSpeed, 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
|
-
runsFor: (session) => [...(session.runs ?? [])],
|
|
43
|
-
stateFor: (session) => runStateOf(session.runs)
|
|
44
|
-
});
|
|
45
|
-
return { cost, asked: () => asked, host };
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* A session with one run going, of the kind this file measures.
|
|
50
|
-
*
|
|
51
|
-
* @param {{ audioOnly?: boolean, transcodeVideo?: boolean, audioSourceTrackIndex?: number, key?: string }} [what]
|
|
52
|
-
* @returns {object}
|
|
53
|
-
*/
|
|
54
|
-
function sessionProducing(what = {}) {
|
|
55
|
-
const session = {
|
|
56
|
-
id: "1111111122223333",
|
|
57
|
-
state: "ready",
|
|
58
|
-
spec: outputSpec({
|
|
59
|
-
audioOnly: what.audioOnly === true,
|
|
60
|
-
transcodeVideo: what.transcodeVideo === true,
|
|
61
|
-
audioSourceTrackIndex: what.audioSourceTrackIndex ?? 0
|
|
62
|
-
}),
|
|
63
|
-
file: { key: what.key ?? "torrent:abc:0", name: "film.mkv" },
|
|
64
|
-
output: { encodeWidth: 0, encodeHeight: 0, outputFps: 25, softwarePreset: null },
|
|
65
|
-
clock: { at: 1000 },
|
|
66
|
-
runs: new Set()
|
|
67
|
-
};
|
|
68
|
-
startRunOn(session, { from: 0, clock: session.clock });
|
|
69
|
-
return session;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* The session's run measures its speed, and is learned from as the product
|
|
74
|
-
* learns: once the run has a reading.
|
|
75
|
-
*
|
|
76
|
-
* @param {EncodeCost} cost
|
|
77
|
-
* @param {object} session
|
|
78
|
-
* @param {number} speedX - Seconds of film produced per second of the run's own work.
|
|
79
|
-
* @param {number} [waitedMs] - How long the run's input waits for the swarm
|
|
80
|
-
* between two closed pieces.
|
|
81
|
-
* @returns {Promise<void>}
|
|
82
|
-
*/
|
|
83
|
-
async function watchItRun(cost, session, speedX, waitedMs = 0) {
|
|
84
|
-
const run = [...session.runs][0];
|
|
85
|
-
measureSpeed(run, speedX, session.clock, waitedMs);
|
|
86
|
-
await cost.learnFrom(session, run);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
test("the host is asked at the moment of the question, not when this was built", () => {
|
|
90
|
-
// The share of the machine that is free is re-read every few seconds. Copied
|
|
91
|
-
// into this object when it was made, every later rung would be priced against
|
|
92
|
-
// a machine that has gone.
|
|
93
|
-
const { cost, asked, host } = costOn();
|
|
94
|
-
assert.equal(asked(), 0, "nothing is read until something is asked");
|
|
95
|
-
|
|
96
|
-
cost.sustainableHeights({ heights: [1080], sourceWidth: 1920, sourceHeight: 1080, fps: 24, source: null, transcodeVideo: true, ownHeight: 0 });
|
|
97
|
-
const first = asked();
|
|
98
|
-
assert.ok(first > 0);
|
|
99
|
-
|
|
100
|
-
host.share = 0.1;
|
|
101
|
-
cost.sustainableHeights({ heights: [1080], sourceWidth: 1920, sourceHeight: 1080, fps: 24, source: null, transcodeVideo: true, ownHeight: 0 });
|
|
102
|
-
assert.ok(asked() > first, "and read again on the next question");
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
test("with no benchmark to judge by, every height offered is kept", () => {
|
|
106
|
-
// Nothing measured is not the same as nothing possible. Refusing here would
|
|
107
|
-
// hide the whole ladder on a host whose startup measurement failed.
|
|
108
|
-
const { cost } = costOn({ benchmark: null });
|
|
109
|
-
const kept = cost.sustainableHeights({
|
|
110
|
-
heights: [1080, 720, 480],
|
|
111
|
-
sourceWidth: 1920,
|
|
112
|
-
sourceHeight: 1080,
|
|
113
|
-
fps: 24,
|
|
114
|
-
source: null,
|
|
115
|
-
transcodeVideo: true,
|
|
116
|
-
ownHeight: 0
|
|
117
|
-
});
|
|
118
|
-
assert.deepEqual(kept, [1080, 720, 480]);
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
test("a rung measured below realtime is withdrawn, and a copied source height is not", () => {
|
|
122
|
-
const { cost } = costOn({ benchmark: null });
|
|
123
|
-
const kept = cost.sustainableHeights({
|
|
124
|
-
heights: [1080, 480],
|
|
125
|
-
sourceWidth: 1920,
|
|
126
|
-
sourceHeight: 1080,
|
|
127
|
-
fps: 24,
|
|
128
|
-
source: null,
|
|
129
|
-
// The base copies its picture, so the source height costs no encoder.
|
|
130
|
-
transcodeVideo: false,
|
|
131
|
-
ownHeight: 1080,
|
|
132
|
-
measuredHeights: new Map([[480, 0.4]])
|
|
133
|
-
});
|
|
134
|
-
assert.deepEqual(kept, [1080], "the copy stays; the rung seen failing does not");
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
test("what an encoder taught this host is learned here, and held here", () => {
|
|
138
|
-
// The three that learn were methods of the session manager, writing into maps
|
|
139
|
-
// this object owned — one keeper, a different author, and the maps public for
|
|
140
|
-
// it. Nothing outside can write a price now, and nothing outside needs to.
|
|
141
|
-
const { cost } = costOn();
|
|
142
|
-
|
|
143
|
-
assert.equal(typeof cost.learnFrom, "function");
|
|
144
|
-
assert.equal(cost.copyCost, undefined, "the store is not the surface");
|
|
145
|
-
assert.equal(cost.decodeCostFor("torrent:abc:0"), null, "and nothing is known until an encoder says so");
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
test("a copy is priced from what it was seen doing", async () => {
|
|
149
|
-
const { cost } = costOn();
|
|
150
|
-
const session = sessionProducing({ transcodeVideo: false });
|
|
151
|
-
|
|
152
|
-
await watchItRun(cost, session, 8);
|
|
153
|
-
|
|
154
|
-
assert.equal(cost.copyVersionFor("torrent:abc:0"), 1, "the price is published, so every offer recomputes");
|
|
155
|
-
assert.equal(
|
|
156
|
-
qualityStateOf(session).lastAloneSpeed.toFixed(2),
|
|
157
|
-
"8.00",
|
|
158
|
-
"and what it did with the machine to itself is on the session, which withdraws a step seen failing"
|
|
159
|
-
);
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
test("the time a run's input waited for the swarm is not in the price of this host", async () => {
|
|
163
|
-
// Field 2026-10-01: a copy waited 32.93 s and 43.91 s for two pieces and read
|
|
164
|
-
// 0.21x over the clock. Over its own working time it is the machine's speed.
|
|
165
|
-
const { cost } = costOn();
|
|
166
|
-
const session = sessionProducing({ transcodeVideo: false });
|
|
167
|
-
|
|
168
|
-
await watchItRun(cost, session, 8, 9_000);
|
|
169
|
-
|
|
170
|
-
assert.equal(qualityStateOf(session).lastAloneSpeed, 8, "the speed is the run's own, not the swarm's");
|
|
171
|
-
assert.equal(cost.copyVersionFor("torrent:abc:0"), 1, "and it is filed as what copying costs here");
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
test("a picture's reading taken beside another encoder is not filed", async () => {
|
|
175
|
-
// It contains that other encoder's work, and the budget ADDS the same work
|
|
176
|
-
// again when it predicts — so the price is counted twice and grows with every
|
|
177
|
-
// reading. Measured in the field 2026-08-15: copying, whose truth is 7.9x, was
|
|
178
|
-
// learned as 2.03x, and the quality menu collapsed to one height.
|
|
179
|
-
const { cost } = costOn({ runningEncoders: () => 2 });
|
|
180
|
-
const session = sessionProducing({ transcodeVideo: false });
|
|
181
|
-
|
|
182
|
-
await watchItRun(cost, session, 8);
|
|
183
|
-
|
|
184
|
-
assert.equal(cost.copyVersionFor("torrent:abc:0"), 0);
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
test("a run that has measured nothing teaches nothing", async () => {
|
|
188
|
-
const { cost } = costOn();
|
|
189
|
-
const session = sessionProducing({ transcodeVideo: false });
|
|
190
|
-
const run = [...session.runs][0];
|
|
191
|
-
|
|
192
|
-
await cost.learnFrom(session, run);
|
|
193
|
-
|
|
194
|
-
assert.equal(run.speedReading, null, "a speed needs two closed pieces of one run");
|
|
195
|
-
assert.equal(cost.copyVersionFor("torrent:abc:0"), 0);
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
test("a restarted run starts its own reckoning, so a seek is never read as speed", async () => {
|
|
199
|
-
// Twenty minutes of film against five seconds of work is a seek, and filed as
|
|
200
|
-
// a price it admits every step there is. The reading belongs to the run that
|
|
201
|
-
// made it, so a new run at another place has none until its own pieces close.
|
|
202
|
-
const { cost } = costOn();
|
|
203
|
-
const session = sessionProducing({ transcodeVideo: false });
|
|
204
|
-
session.runs = new Set();
|
|
205
|
-
const moved = startRunOn(session, { from: 300, clock: session.clock });
|
|
206
|
-
|
|
207
|
-
await cost.learnFrom(session, moved);
|
|
208
|
-
|
|
209
|
-
assert.equal(cost.copyVersionFor("torrent:abc:0"), 0);
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
test("two soundtracks of one file are priced apart", async () => {
|
|
213
|
-
// The key names the TRACK, not the file. A dub and the original run at
|
|
214
|
-
// different speeds — different codecs, different channel counts — and one key
|
|
215
|
-
// for both files each over the other's readings.
|
|
216
|
-
const { cost } = costOn();
|
|
217
|
-
const first = sessionProducing({ audioOnly: true });
|
|
218
|
-
const second = sessionProducing({ audioOnly: true, audioSourceTrackIndex: 1 });
|
|
219
|
-
|
|
220
|
-
await watchItRun(cost, first, 40);
|
|
221
|
-
|
|
222
|
-
assert.equal(cost.audioVersionFor(first), 1);
|
|
223
|
-
assert.equal(cost.audioVersionFor(second), 0, "the other track has been told nothing about itself");
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
test("a soundtrack's own share is what is left after everything else running is paid for", async () => {
|
|
227
|
-
// A rendition runs for exactly as long as the picture it accompanies, so
|
|
228
|
-
// "alone" is a state it is never in and the price stayed unmeasured for ever.
|
|
229
|
-
// Its share is recovered by subtracting what the machine is already known to
|
|
230
|
-
// be spending — which is only possible once that other work HAS a price.
|
|
231
|
-
const picture = sessionProducing({ transcodeVideo: false });
|
|
232
|
-
const sound = sessionProducing({ audioOnly: true });
|
|
233
|
-
let running = 1;
|
|
234
|
-
const { cost } = costOn({
|
|
235
|
-
runningEncoders: () => running,
|
|
236
|
-
familyOf: () => [picture, sound]
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
// The picture alone first, so copying has a price: 8x, i.e. 0.125 s/s.
|
|
240
|
-
await watchItRun(cost, picture, 8);
|
|
241
|
-
assert.equal(cost.copyVersionFor("torrent:abc:0"), 1);
|
|
242
|
-
|
|
243
|
-
// Now the pair. The reading off the soundtrack contains both, at 4x — 0.25 s/s
|
|
244
|
-
// together — so the sound's own share is 0.25 − 0.125 = 0.125 s/s.
|
|
245
|
-
running = 2;
|
|
246
|
-
await watchItRun(cost, sound, 4);
|
|
247
|
-
|
|
248
|
-
assert.equal(cost.audioVersionFor(sound), 1, "the soundtrack has a price at last");
|
|
249
|
-
// What was filed, read back the way the product reads it: the sum of what
|
|
250
|
-
// everything OTHER than the picture is costing. Charged the whole reading
|
|
251
|
-
// instead of its own share, a soundtrack is priced at twice the truth and
|
|
252
|
-
// refuses quality steps the host could hold.
|
|
253
|
-
running = 1;
|
|
254
|
-
assert.equal(
|
|
255
|
-
cost.pricedConcurrentCost(picture).toFixed(3),
|
|
256
|
-
"0.125",
|
|
257
|
-
"0.25 s/s together, less the 0.125 s/s the copy was already known to cost"
|
|
258
|
-
);
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
test("nothing is attributed to a soundtrack while something running has no price", async () => {
|
|
262
|
-
const picture = sessionProducing({ transcodeVideo: true });
|
|
263
|
-
const sound = sessionProducing({ audioOnly: true });
|
|
264
|
-
const { cost } = costOn({
|
|
265
|
-
runningEncoders: () => 2,
|
|
266
|
-
// A re-encoded picture with no benchmark to price it by and no speed of its
|
|
267
|
-
// own: unpriced work, which would otherwise land in the soundtrack's account
|
|
268
|
-
// and refuse quality steps on it.
|
|
269
|
-
familyOf: () => [picture, sound]
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
await watchItRun(cost, sound, 4);
|
|
273
|
-
|
|
274
|
-
assert.equal(cost.audioVersionFor(sound), 0);
|
|
275
|
-
});
|
|
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 { measureSpeed, 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
|
+
runsFor: (session) => [...(session.runs ?? [])],
|
|
43
|
+
stateFor: (session) => runStateOf(session.runs)
|
|
44
|
+
});
|
|
45
|
+
return { cost, asked: () => asked, host };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* A session with one run going, of the kind this file measures.
|
|
50
|
+
*
|
|
51
|
+
* @param {{ audioOnly?: boolean, transcodeVideo?: boolean, audioSourceTrackIndex?: number, key?: string }} [what]
|
|
52
|
+
* @returns {object}
|
|
53
|
+
*/
|
|
54
|
+
function sessionProducing(what = {}) {
|
|
55
|
+
const session = {
|
|
56
|
+
id: "1111111122223333",
|
|
57
|
+
state: "ready",
|
|
58
|
+
spec: outputSpec({
|
|
59
|
+
audioOnly: what.audioOnly === true,
|
|
60
|
+
transcodeVideo: what.transcodeVideo === true,
|
|
61
|
+
audioSourceTrackIndex: what.audioSourceTrackIndex ?? 0
|
|
62
|
+
}),
|
|
63
|
+
file: { key: what.key ?? "torrent:abc:0", name: "film.mkv" },
|
|
64
|
+
output: { encodeWidth: 0, encodeHeight: 0, outputFps: 25, softwarePreset: null },
|
|
65
|
+
clock: { at: 1000 },
|
|
66
|
+
runs: new Set()
|
|
67
|
+
};
|
|
68
|
+
startRunOn(session, { from: 0, clock: session.clock });
|
|
69
|
+
return session;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The session's run measures its speed, and is learned from as the product
|
|
74
|
+
* learns: once the run has a reading.
|
|
75
|
+
*
|
|
76
|
+
* @param {EncodeCost} cost
|
|
77
|
+
* @param {object} session
|
|
78
|
+
* @param {number} speedX - Seconds of film produced per second of the run's own work.
|
|
79
|
+
* @param {number} [waitedMs] - How long the run's input waits for the swarm
|
|
80
|
+
* between two closed pieces.
|
|
81
|
+
* @returns {Promise<void>}
|
|
82
|
+
*/
|
|
83
|
+
async function watchItRun(cost, session, speedX, waitedMs = 0) {
|
|
84
|
+
const run = [...session.runs][0];
|
|
85
|
+
measureSpeed(run, speedX, session.clock, waitedMs);
|
|
86
|
+
await cost.learnFrom(session, run);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
test("the host is asked at the moment of the question, not when this was built", () => {
|
|
90
|
+
// The share of the machine that is free is re-read every few seconds. Copied
|
|
91
|
+
// into this object when it was made, every later rung would be priced against
|
|
92
|
+
// a machine that has gone.
|
|
93
|
+
const { cost, asked, host } = costOn();
|
|
94
|
+
assert.equal(asked(), 0, "nothing is read until something is asked");
|
|
95
|
+
|
|
96
|
+
cost.sustainableHeights({ heights: [1080], sourceWidth: 1920, sourceHeight: 1080, fps: 24, source: null, transcodeVideo: true, ownHeight: 0 });
|
|
97
|
+
const first = asked();
|
|
98
|
+
assert.ok(first > 0);
|
|
99
|
+
|
|
100
|
+
host.share = 0.1;
|
|
101
|
+
cost.sustainableHeights({ heights: [1080], sourceWidth: 1920, sourceHeight: 1080, fps: 24, source: null, transcodeVideo: true, ownHeight: 0 });
|
|
102
|
+
assert.ok(asked() > first, "and read again on the next question");
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("with no benchmark to judge by, every height offered is kept", () => {
|
|
106
|
+
// Nothing measured is not the same as nothing possible. Refusing here would
|
|
107
|
+
// hide the whole ladder on a host whose startup measurement failed.
|
|
108
|
+
const { cost } = costOn({ benchmark: null });
|
|
109
|
+
const kept = cost.sustainableHeights({
|
|
110
|
+
heights: [1080, 720, 480],
|
|
111
|
+
sourceWidth: 1920,
|
|
112
|
+
sourceHeight: 1080,
|
|
113
|
+
fps: 24,
|
|
114
|
+
source: null,
|
|
115
|
+
transcodeVideo: true,
|
|
116
|
+
ownHeight: 0
|
|
117
|
+
});
|
|
118
|
+
assert.deepEqual(kept, [1080, 720, 480]);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("a rung measured below realtime is withdrawn, and a copied source height is not", () => {
|
|
122
|
+
const { cost } = costOn({ benchmark: null });
|
|
123
|
+
const kept = cost.sustainableHeights({
|
|
124
|
+
heights: [1080, 480],
|
|
125
|
+
sourceWidth: 1920,
|
|
126
|
+
sourceHeight: 1080,
|
|
127
|
+
fps: 24,
|
|
128
|
+
source: null,
|
|
129
|
+
// The base copies its picture, so the source height costs no encoder.
|
|
130
|
+
transcodeVideo: false,
|
|
131
|
+
ownHeight: 1080,
|
|
132
|
+
measuredHeights: new Map([[480, 0.4]])
|
|
133
|
+
});
|
|
134
|
+
assert.deepEqual(kept, [1080], "the copy stays; the rung seen failing does not");
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("what an encoder taught this host is learned here, and held here", () => {
|
|
138
|
+
// The three that learn were methods of the session manager, writing into maps
|
|
139
|
+
// this object owned — one keeper, a different author, and the maps public for
|
|
140
|
+
// it. Nothing outside can write a price now, and nothing outside needs to.
|
|
141
|
+
const { cost } = costOn();
|
|
142
|
+
|
|
143
|
+
assert.equal(typeof cost.learnFrom, "function");
|
|
144
|
+
assert.equal(cost.copyCost, undefined, "the store is not the surface");
|
|
145
|
+
assert.equal(cost.decodeCostFor("torrent:abc:0"), null, "and nothing is known until an encoder says so");
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("a copy is priced from what it was seen doing", async () => {
|
|
149
|
+
const { cost } = costOn();
|
|
150
|
+
const session = sessionProducing({ transcodeVideo: false });
|
|
151
|
+
|
|
152
|
+
await watchItRun(cost, session, 8);
|
|
153
|
+
|
|
154
|
+
assert.equal(cost.copyVersionFor("torrent:abc:0"), 1, "the price is published, so every offer recomputes");
|
|
155
|
+
assert.equal(
|
|
156
|
+
qualityStateOf(session).lastAloneSpeed.toFixed(2),
|
|
157
|
+
"8.00",
|
|
158
|
+
"and what it did with the machine to itself is on the session, which withdraws a step seen failing"
|
|
159
|
+
);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test("the time a run's input waited for the swarm is not in the price of this host", async () => {
|
|
163
|
+
// Field 2026-10-01: a copy waited 32.93 s and 43.91 s for two pieces and read
|
|
164
|
+
// 0.21x over the clock. Over its own working time it is the machine's speed.
|
|
165
|
+
const { cost } = costOn();
|
|
166
|
+
const session = sessionProducing({ transcodeVideo: false });
|
|
167
|
+
|
|
168
|
+
await watchItRun(cost, session, 8, 9_000);
|
|
169
|
+
|
|
170
|
+
assert.equal(qualityStateOf(session).lastAloneSpeed, 8, "the speed is the run's own, not the swarm's");
|
|
171
|
+
assert.equal(cost.copyVersionFor("torrent:abc:0"), 1, "and it is filed as what copying costs here");
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test("a picture's reading taken beside another encoder is not filed", async () => {
|
|
175
|
+
// It contains that other encoder's work, and the budget ADDS the same work
|
|
176
|
+
// again when it predicts — so the price is counted twice and grows with every
|
|
177
|
+
// reading. Measured in the field 2026-08-15: copying, whose truth is 7.9x, was
|
|
178
|
+
// learned as 2.03x, and the quality menu collapsed to one height.
|
|
179
|
+
const { cost } = costOn({ runningEncoders: () => 2 });
|
|
180
|
+
const session = sessionProducing({ transcodeVideo: false });
|
|
181
|
+
|
|
182
|
+
await watchItRun(cost, session, 8);
|
|
183
|
+
|
|
184
|
+
assert.equal(cost.copyVersionFor("torrent:abc:0"), 0);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test("a run that has measured nothing teaches nothing", async () => {
|
|
188
|
+
const { cost } = costOn();
|
|
189
|
+
const session = sessionProducing({ transcodeVideo: false });
|
|
190
|
+
const run = [...session.runs][0];
|
|
191
|
+
|
|
192
|
+
await cost.learnFrom(session, run);
|
|
193
|
+
|
|
194
|
+
assert.equal(run.speedReading, null, "a speed needs two closed pieces of one run");
|
|
195
|
+
assert.equal(cost.copyVersionFor("torrent:abc:0"), 0);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test("a restarted run starts its own reckoning, so a seek is never read as speed", async () => {
|
|
199
|
+
// Twenty minutes of film against five seconds of work is a seek, and filed as
|
|
200
|
+
// a price it admits every step there is. The reading belongs to the run that
|
|
201
|
+
// made it, so a new run at another place has none until its own pieces close.
|
|
202
|
+
const { cost } = costOn();
|
|
203
|
+
const session = sessionProducing({ transcodeVideo: false });
|
|
204
|
+
session.runs = new Set();
|
|
205
|
+
const moved = startRunOn(session, { from: 300, clock: session.clock });
|
|
206
|
+
|
|
207
|
+
await cost.learnFrom(session, moved);
|
|
208
|
+
|
|
209
|
+
assert.equal(cost.copyVersionFor("torrent:abc:0"), 0);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test("two soundtracks of one file are priced apart", async () => {
|
|
213
|
+
// The key names the TRACK, not the file. A dub and the original run at
|
|
214
|
+
// different speeds — different codecs, different channel counts — and one key
|
|
215
|
+
// for both files each over the other's readings.
|
|
216
|
+
const { cost } = costOn();
|
|
217
|
+
const first = sessionProducing({ audioOnly: true });
|
|
218
|
+
const second = sessionProducing({ audioOnly: true, audioSourceTrackIndex: 1 });
|
|
219
|
+
|
|
220
|
+
await watchItRun(cost, first, 40);
|
|
221
|
+
|
|
222
|
+
assert.equal(cost.audioVersionFor(first), 1);
|
|
223
|
+
assert.equal(cost.audioVersionFor(second), 0, "the other track has been told nothing about itself");
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test("a soundtrack's own share is what is left after everything else running is paid for", async () => {
|
|
227
|
+
// A rendition runs for exactly as long as the picture it accompanies, so
|
|
228
|
+
// "alone" is a state it is never in and the price stayed unmeasured for ever.
|
|
229
|
+
// Its share is recovered by subtracting what the machine is already known to
|
|
230
|
+
// be spending — which is only possible once that other work HAS a price.
|
|
231
|
+
const picture = sessionProducing({ transcodeVideo: false });
|
|
232
|
+
const sound = sessionProducing({ audioOnly: true });
|
|
233
|
+
let running = 1;
|
|
234
|
+
const { cost } = costOn({
|
|
235
|
+
runningEncoders: () => running,
|
|
236
|
+
familyOf: () => [picture, sound]
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// The picture alone first, so copying has a price: 8x, i.e. 0.125 s/s.
|
|
240
|
+
await watchItRun(cost, picture, 8);
|
|
241
|
+
assert.equal(cost.copyVersionFor("torrent:abc:0"), 1);
|
|
242
|
+
|
|
243
|
+
// Now the pair. The reading off the soundtrack contains both, at 4x — 0.25 s/s
|
|
244
|
+
// together — so the sound's own share is 0.25 − 0.125 = 0.125 s/s.
|
|
245
|
+
running = 2;
|
|
246
|
+
await watchItRun(cost, sound, 4);
|
|
247
|
+
|
|
248
|
+
assert.equal(cost.audioVersionFor(sound), 1, "the soundtrack has a price at last");
|
|
249
|
+
// What was filed, read back the way the product reads it: the sum of what
|
|
250
|
+
// everything OTHER than the picture is costing. Charged the whole reading
|
|
251
|
+
// instead of its own share, a soundtrack is priced at twice the truth and
|
|
252
|
+
// refuses quality steps the host could hold.
|
|
253
|
+
running = 1;
|
|
254
|
+
assert.equal(
|
|
255
|
+
cost.pricedConcurrentCost(picture).toFixed(3),
|
|
256
|
+
"0.125",
|
|
257
|
+
"0.25 s/s together, less the 0.125 s/s the copy was already known to cost"
|
|
258
|
+
);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
test("nothing is attributed to a soundtrack while something running has no price", async () => {
|
|
262
|
+
const picture = sessionProducing({ transcodeVideo: true });
|
|
263
|
+
const sound = sessionProducing({ audioOnly: true });
|
|
264
|
+
const { cost } = costOn({
|
|
265
|
+
runningEncoders: () => 2,
|
|
266
|
+
// A re-encoded picture with no benchmark to price it by and no speed of its
|
|
267
|
+
// own: unpriced work, which would otherwise land in the soundtrack's account
|
|
268
|
+
// and refuse quality steps on it.
|
|
269
|
+
familyOf: () => [picture, sound]
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
await watchItRun(cost, sound, 4);
|
|
273
|
+
|
|
274
|
+
assert.equal(cost.audioVersionFor(sound), 0);
|
|
275
|
+
});
|