PR Gates / CI image pin matches docker/ci (pull_request) Successful in 11s
PR Gates / Docs update reminder (pull_request) Successful in 16s
PR Gates / decisions lifecycle (pull_request) Failing after 23s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 1m17s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 1m29s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 8m5s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 16m5s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Successful in 17m6s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
168 records -> docs/decisions/records/<area>/<topic>.md (163 active, 23 dirs) and docs/decisions/archive/<area>/<topic>.md (5 archived). The filename IS the key, so one-active-record-per-key becomes a filesystem property rather than a validator check, and supersession becomes a `git mv`. WHY: the monolith was a concurrency problem before an aesthetic one. A 3,900-line append target made parallel sessions collide -- PR #605 and PR #614 both hit append-vs-append conflicts during routine rebases, and hand-resolving those inside the corpus is exactly the operation the rationale-rewrite guard exists to police. HOW IT IS VERIFIED: a ~170-file diff cannot be meaningfully read, so correctness does not rest on reading it. The parser was taught BOTH formats first, so the body-diff guard parses the old form at the merge-base and the new form at head -- the migration validates itself, no bypass. The proof is a field-level equivalence harness: 168 records before and after, zero lost, zero gained, zero field mismatches, zero rationale bodies differing. Reviewers should scrutinise the harness; it is the actual evidence. What measuring caught that reading would not have: - ~500 lines sit OUTSIDE any record -- decisions.md's lifecycle schema and each topic file's preamble, mostly the only copy. Source files are kept and stripped, never deleted. They also cannot be filed per-area: topic files hold several areas and 4 of 23 areas span several files. - Archive discovery was a non-recursive glob; after the split it found ZERO archived records, surfacing as four bogus "supersedes points to unknown key" errors rather than an obvious failure. - ~32 live docs point into the corpus BY DATE, which the split dangles. Each stripped file now ends with a generated "Records formerly in this file" index, which also rescues the identical breadcrumbs in old issue comments. - decisions.md's "In this file:" list was 97 same-file anchor bullets that the split makes WRONG, not merely stale. Dropped; the generated index replaces them with links that resolve. The equivalence harness now runs against a checked-in FIXTURE, not the live corpus. The earlier version migrated the real tree, which made it a one-shot: the moment the migration landed there was nothing left to move and the tests failed for reasons unrelated to the code. A fixture keeps them testing the SCRIPT rather than the repo's current state. Keys preserved verbatim, warts included: `sched` (12) and `scheduling` (1) remain two directories for one concept. Renaming a key is not a move -- it changes identity, breaks the equivalence proof, and invalidates MemPalace's per-key drawers. Taxonomy normalisation is separate work. refs #610
4.3 KiB
4.3 KiB
key, title, status, since, supersedes, superseded-by, rule, signals, mechanics
| key | title | status | since | supersedes | superseded-by | rule | signals | mechanics |
|---|---|---|---|---|---|---|---|---|
| ffmpeg.qsv-decode-encode-split | 2026-07-20 (#498) — QSV decode is split from QSV encode via a single `QsvPreferNativeDecoder` bool | active | 2026-07-20 | none | none | QSV decode is decoupled from QSV encode via a single `FFmpegProfile.QsvPreferNativeDecoder` bool (default ON, Linux-only), so a QSV encode profile can decode with the more tolerant native VA-API decoder instead of the QSV decoder, mirroring Jellyfin's hybrid decode/encode toggle instead of a general decode-family enum. | QSV, native VA-API decode, Dolby Vision passthrough, hybrid decode/encode, HDR tonemap fallback · paths: `FFmpegProfile.QsvPreferNativeDecoder`, `QsvPipelineBuilder.SetTonemap`, `QsvPipelineBuilder` · issues: #498, #505 | `docs/superpowers/specs/2026-07-20-qsv-native-decode-design.md`; migration `HasDefaultValue(true)` on the nullable `bool?` column; `QsvPipelineBuilder` decoder-mode branch |
FFmpegProfile.HardwareAcceleration picked one pipeline builder for both decode and encode, so an
Intel QSV profile decoded with the QSV decoder — which is materially less tolerant of imperfect H.264 than
FFmpeg's native VA-API decoder and cannot carry Dolby Vision metadata. Jellyfin, on identical hardware,
avoids this by decoding with VA-API and encoding with QSV; that combination was not expressible in
ErsatzTV. Full design: docs/superpowers/specs/2026-07-20-qsv-native-decode-design.md.
- Chosen: a single, QSV-scoped
FFmpegProfile.QsvPreferNativeDecoderboolean, mirroring Jellyfin's "Prefer OS native DXVA or VA-API hardware decoders" checkbox exactly — Jellyfin itself deliberately collapses this to one default-on toggle rather than a decode-family picker, and copying the reference tool's granularity avoids over-building past a problem it has already solved. - Default ON. Native VA-API decode is strictly more tolerant of imperfect streams than the QSV decoder
wrapper, and Dolby Vision passthrough requires it — so the working configuration is the default for a
reliability fix. Existing QSV profiles adopt the hybrid on upgrade via the migration's
HasDefaultValue(true)(SQLiteINTEGER/MySQLtinyint, nullable withDEFAULT 1— the column isbool?so an explicitfalsepersists, whileNULL/absent reads as ON via!= false; a non-nullablebool+ store default would make EF silently drop a create-with-false), no opt-in required. - Rejected: a general
DecodeHardwareAccelerationenum column decoupling decode and encode hardware families entirely. More configurable than Jellyfin's own control, but nothing in the codebase or the reported failure needs that generality yet —FFmpegStatealready separatesDecoderHardwareAccelerationMode/EncoderHardwareAccelerationModeinternally (used today for the software-decode-plus-hardware-encode error-loop fallback), so the seam to grow into a real enum exists if a second asymmetric-decode case ever shows up on another hardware family. Deferred rather than built now, on YAGNI grounds. - Contained to the QSV builder. The flag does not touch
PipelineBuilderFactory's single-builder dispatch per profile, and does not repurpose the existing decode/encode-mode fields into a general cross-family selector — that repurposing is exactly the rejected enum option, just introduced through the back door. - Accepted trade-off — HDR tonemapping runs in software on the native path. With native decode ON the
decoder mode is
Vaapi, soQsvPipelineBuilder.SetTonemap(which only selectsTonemapQsvFilterforDecoderHardwareAccelerationMode == Qsv) falls to the softwarezscale/tonemapchain for HDR content. Output is correct but costs CPU on the realtime path. Accepted for now because software tonemap is correct-but-slower while an unvalidated GPU-tonemap graph could be worse (needs the Intel host to verify), and the escape hatch covers it: HDR-on-QSV users who don't need the tolerant decoder set the flag OFF to keep GPU tonemap. Optimizing the native path totonemap_qsvis tracked in #505. - Native decode is Linux-only. Guarded with
!OperatingSystem.IsWindows()in the QSV builder — FFmpeg has novaapihwaccel on Windows (and Windows QSV capabilities are over-reported), so on Windows a QSV profile keeps QSV decode regardless of the flag.