diff --git a/CHANGELOG.md b/CHANGELOG.md index 121d0b87e..9b6633031 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13021,6 +13021,10 @@ Three follow-up items filed: (1) document per-session thread-safety contract in (3) propagate `GetTensorElementType` failure instead of silent UNDEFINED default. +- **docs(state): Audit open bugs in `docs/state.md` and reconcile with master (#1238)**: + Moved four confirmed-resolved bug tracking rows (`T-UPSTREAM-818-POOLING-ENUM-NO-PERCENTILES-2026-09-03`, `T-AI-PTQ-STATIC-QUANT-FORMAT-UNPINNED-2026-09-03`, `T-SVTAV1-HDR-ADAPTER-2026-05-20`, and `T-VMAFTUNE-PROFILE-REPORT-AUDIT-2026-05-20`) from Open bugs to Recently closed with PR citations; restored accidentally dropped rows for `T-METAL-MOTION-V2-MIRROR-OFF-BY-ONE-2026-09-03` and `T-UPSTREAM-1564-ADM-CM-GPU-BORDER-AND-ROUNDING-2026-09-03`; deduplicated tombstone comments in Open bugs. + + **Close the three `docs/ai/` gaps of epic #1242** — factual state, not promises - [`docs/ai/sidecar-online-training.md`](../../docs/ai/sidecar-online-training.md): diff --git a/changelog.d/changed/docs-1238-state-md-stale-rows.md b/changelog.d/changed/docs-1238-state-md-stale-rows.md new file mode 100644 index 000000000..95b639919 --- /dev/null +++ b/changelog.d/changed/docs-1238-state-md-stale-rows.md @@ -0,0 +1,2 @@ +- **docs(state): Audit open bugs in `docs/state.md` and reconcile with master (#1238)**: + Moved four confirmed-resolved bug tracking rows (`T-UPSTREAM-818-POOLING-ENUM-NO-PERCENTILES-2026-09-03`, `T-AI-PTQ-STATIC-QUANT-FORMAT-UNPINNED-2026-09-03`, `T-SVTAV1-HDR-ADAPTER-2026-05-20`, and `T-VMAFTUNE-PROFILE-REPORT-AUDIT-2026-05-20`) from Open bugs to Recently closed with PR citations; restored accidentally dropped rows for `T-METAL-MOTION-V2-MIRROR-OFF-BY-ONE-2026-09-03` and `T-UPSTREAM-1564-ADM-CM-GPU-BORDER-AND-ROUNDING-2026-09-03`; deduplicated tombstone comments in Open bugs. diff --git a/docs/state.md b/docs/state.md index 4422f25cb..6eec925f5 100644 --- a/docs/state.md +++ b/docs/state.md @@ -1,6 +1,6 @@ _Updated: 2026-09-08 (T-DISTS-PLACEHOLDER-CHECKPOINT-2026-09-08 and T-PREDICTOR-SOFTWARE-AMF-STUB-MODELS-2026-09-08 opened; T6-2a-followup' updated — issue #1270 blockers triaged: DISTS model is a smoke placeholder and lacks learned feature stack, point-of-use warning added to feature_dists.c and docs/metrics/dists.md callout added, tracked as T-DISTS-PLACEHOLDER-CHECKPOINT-2026-09-08; mobilesal.md placeholder claim is stale as production saliency uses saliency_student_v2 (ADR-0444) / saliency_student_v1 (ADR-0286), doc and feature_mobilesal.c warning corrected; predictor models for software and AMF encoders are synthetic stubs (ADR-0325), point-of-use warnings added in Python Predictor / vmaf-tune and Go NewWithModel, docs/ai/predictor.md callout added, tracked as T-PREDICTOR-SOFTWARE-AMF-STUB-MODELS-2026-09-08.)_ -_Updated: 2026-09-08 (T-CODE-SCANNING-1243-FIX-2026-09-08 closed — resolved actionable open GitHub code-scanning alerts for issue #1243: fixed loop variable modification in cli_parse.cpp, commented-out-code false positive in test_model_feature_overload_ownership.c, and unused duplicate constant block in server.py; audited all 22 open alerts against origin/master; zero alerts dismissed by agent. Row added to Recently closed.)_ +_Updated: 2026-09-08 (docs/state.md bookkeeping sweep (#1238) — T-UPSTREAM-818-POOLING-ENUM-NO-PERCENTILES-2026-09-03 (PR #1340), T-AI-PTQ-STATIC-QUANT-FORMAT-UNPINNED-2026-09-03 (PR #1306), T-SVTAV1-HDR-ADAPTER-2026-05-20 (PR #1296) and T-VMAFTUNE-PROFILE-REPORT-AUDIT-2026-05-20 (PR #1296) moved from Open bugs to Recently closed; T-METAL-MOTION-V2-MIRROR-OFF-BY-ONE-2026-09-03 and T-UPSTREAM-1564-ADM-CM-GPU-BORDER-AND-ROUNDING-2026-09-03 rows restored in Recently closed; duplicate tombstone comments cleaned up. T-CODE-SCANNING-1243-FIX-2026-09-08 closed separately on master.)_ _Updated: 2026-09-05 (T-SYCL-NO-CI-KERNEL-EXECUTION-2026-09-04 updated — self-hosted runner online since 2026-09-05 09:29Z (`cachyos-arc-a380-ephemeral`, supervisor active); closes when the check is required.)_ _Updated: 2026-09-05 (T-SYCL-NO-CI-KERNEL-EXECUTION-2026-09-04 opened — no CI lane has ever executed a SYCL kernel: `SYCL float_ssim Parity (Arc DG2-G10)` is gated on `vars.GPU_COVERAGE_ENABLED` (unset) and a `gpu-full` runner that was never registered, which is how #865 merged. Fix in flight on branch `ci/sycl-arc-self-hosted-runner` (ADR-1177): containerised ephemeral self-hosted runner exposing only the Arc A380 render node, required check `SYCL Parity (Arc A380)` switched by `vars.SYCL_ARC_RUNNER_ENABLED`, loud probe failure when the lane is enabled and the runner is unregistered/offline. Row stays Open until the runner is registered, the variable is set, and the check has passed on master. Row added to Open bugs.)_ _Updated: 2026-09-05 (T-SYCL-MOTION2-CHECKERBOARD-DRIFT-2026-09-05 closed — on 1080p checkerboard pairs (`checkerboard_1920_1080_10_3_0_0.yuv` vs `..._1_0.yuv` and `..._10_0.yuv`), SYCL integer motion2 produced `12.554712` pooled mean vs `12.000000` on CPU reference. Root cause: `core/src/feature/sycl/integer_motion_sycl.cpp:841,848,896` computed `motion2_clipped = MIN(motion2 * s->motion_fps_weight, s->motion_max_val)` but appended raw unclipped `motion2` to the feature collector (and unclipped `score` in debug mode, and unclipped `prev_motion_score` in flush). When `motion_max_val=18.0` engaged on the ~18.805/18.858 raw scores, CPU clamped to 18.0 while SYCL emitted unclipped scores. Fixed by appending `motion2_clipped` and `last_motion2`. Bit-exact parity verified with 0 ULP diff on both checkerboard pairs; golden and parity test suites pass. Row added to Recently closed.)_ @@ -357,8 +357,8 @@ landed fix yet._ | **T-GPU-RUNNER-LABEL-MISMATCH-2026-09-05** | `.github/workflows/tests-and-quality-gates.yml` schedules `Coverage GPU (advisory)` and `SYCL float_ssim Parity` on `runs-on: [self-hosted, linux, gpu-full]`, but the only registered self-hosted runner on `VMAFx/vmafx` carries the labels `self-hosted, Linux, X64, sycl-arc` — no `gpu-full`. The repository variable `GPU_COVERAGE_ENABLED` those jobs also require is unset (`gh api .../actions/variables/GPU_COVERAGE_ENABLED` -> 404), so the jobs are doubly unschedulable and no GPU work has ever run on the runner. Separately, no CI job anywhere covers tiny-AI cross-device parity, so the CPU-vs-CUDA bounds quoted in `docs/ai/inference.md` (1e-4 fp32, 1e-2 fp16) are workstation measurements, not gated numbers. Documented as the current factual state in `docs/ai/inference.md` by PR #1320; the CI fix itself is not in that PR. | `gh api /repos/VMAFx/vmafx/actions/runners --jq '.runners[] \| {name, labels: [.labels[].name]}'` prints only `cachyos-arc-a380-ephemeral` with `sycl-arc`; `grep -n 'gpu-full' .github/workflows/tests-and-quality-gates.yml` shows the two jobs that can never match it. | Maintainer — either relabel the runner `gpu-full` (and set `GPU_COVERAGE_ENABLED=true`) or retarget the two jobs at `sycl-arc` and narrow their scope to what an Arc A380 can actually run. | Decision + a follow-up PR; tiny-AI cross-device parity needs its own job either way. | | **T-AI-PTQ-STATIC-QUANT-FORMAT-UNPINNED-2026-09-03** | `ai/scripts/ptq_static.py` calls `onnxruntime.quantization.quantize_static` without passing `quant_format`, so the wire format of every static-PTQ artefact it produces is whatever the installed ONNX Runtime defaults to. The fork's own policy requires QDQ: `core/src/dnn/op_allowlist.c` allowlists `QuantizeLinear` / `DequantizeLinear` (QDQ) and `DynamicQuantizeLinear` / `MatMulInteger` / `ConvInteger` (QOperator-dynamic) but **no** `QLinear*` op, so a QOperator-static graph (`QLinearConv`, `QLinearMatMul`, `QGemm`, …) is rejected by `vmaf_dnn_scan_onnx` with `-EPERM`. The sibling implementation `ai/src/vmaf_train/quantize.py` pins `quant_format=QuantFormat.QDQ` explicitly and documents exactly this reason in its module docstring; `ptq_static.py` does not, and inherits the guarantee from a library default instead. **Not currently broken**: verified against the pinned floor `onnxruntime>=1.29.0` (`ai/pyproject.toml`) — upstream `quantize.py` v1.29.0 line 513 declares `quant_format=QuantFormat.QDQ` as the default. The exposure is that a future ORT default change, or a user on an older ORT, would silently produce an unloadable int8 file. Failure is silent by design: since [ADR-1032](adr/1032-vmaf-init-double-init-guard-vmaf-close-pointer-contract.md) Fix 3 the loader logs the rejection at `VMAF_LOG_LEVEL_DEBUG` and falls back to the fp32 baseline, so the model loads and scores normally, at fp32 weights, with no warning at default log level. One-line fix (pass `quant_format=QuantFormat.QDQ`) belongs in an `ai/` PR, not a docs PR. | `grep -n 'quant_format' ai/scripts/ptq_static.py` — no match; compare `ai/src/vmaf_train/quantize.py:135`. To observe the fallback: build a static-PTQ int8 file with `quant_format=QuantFormat.QOperator`, place it next to a `quant_mode: "static"` registry entry, and load with `--log-level debug`; expect `dnn: int8 sidecar unavailable (…, rc=-1)` and fp32 scores. | unassigned | `ai/` follow-up PR | + - @@ -366,10 +366,8 @@ landed fix yet._ -| **T-SVTAV1-HDR-ADAPTER-2026-05-20** | `vmaf-tune` has `libsvtav1` and `libaom-av1` adapters, but no independently selectable SVT-AV1-HDR runtime for [`juliobbv-p/svt-av1-hdr`](https://github.com/juliobbv-p/svt-av1-hdr/). The repo is a BSD-3-Clause-Clear fork of `psy-ex/svt-av1-psy` with HDR-focused SVT-AV1 changes and community FFmpeg builds; the adapter work must determine whether FFmpeg exposes it through the same `libsvtav1` wrapper or a distinct encoder name. | `python - <<'PY'\nfrom vmaftune.codec_adapters import known_codecs\nprint('svtav1-hdr' in known_codecs(), 'libsvtav1' in known_codecs())\nPY` → `False True`; `gh repo view juliobbv-p/svt-av1-hdr --json isFork,parent,licenseInfo,updatedAt` identifies the fork/licence/source. | Owner-driven; likely needs per-codec `ffmpeg_bin` / runtime-variant support plus a pinned SVT-AV1-HDR build before a separate adapter token is honest. | Closes when `vmaf-tune compare` can select mainline SVT-AV1 and SVT-AV1-HDR independently in one sweep and documents the HDR fork's tune/CRF/preset/runtime knobs. | -| **T-VMAFTUNE-PROFILE-REPORT-AUDIT-2026-05-20** | Profile-card reports are now emitted directly by `vmaf-tune compare --format html|both`, making chart quality part of the compare workflow. The renderer needs a deep audit for graph density, axis scaling, labels, failed-row affordances, bitrate units, mobile layout, deterministic colors, Markdown/HTML parity, and artifact packaging. | Run a multi-codec sweep with `vmaf-tune compare --format both --output report.html` and inspect the rate-quality, ladder, and per-shot charts for readability on desktop and mobile. Current tests assert SVG presence and some samples, not report usability or graph semantics. | Next backlog branch; start in `tools/vmaf-tune/src/vmaftune/report.py` and `tools/vmaf-tune/tests/test_report.py`. | Closes when the audit lands with concrete graph/layout improvements plus regression tests over representative compare v1/v2, ladder, and per-shot reports. | - - + + @@ -381,19 +379,15 @@ landed fix yet._ - - + | **T-UPSTREAM-1568-WINDOWS-NARROW-PATH-API-2026-09-03** | Netflix/vmaf#1568, verified. `core/src/libvmaf.c:3259-3287` `output_file_open()` uses the narrow `_open()` on `_WIN32`, which decodes the path with the process ANSI code page, so a non-ASCII `--output` / ffmpeg `log_path` fails or lands in the wrong place. It is the sole opener for the public API (`vmaf_write_output` / `vmaf_write_output_with_format`), and the in-tree ffmpeg patches pass the raw `AV_OPT_TYPE_STRING` straight through. No mitigation exists anywhere: `grep -rn "_wfopen\|MultiByteToWideChar\|_wopen\|CommandLineToArgvW\|wmain" core/ ffmpeg-patches/` returns nothing, no `.manifest` sets `activeCodePage`, and the only `setlocale` use forces `LC_ALL=C` for numeric formatting. The fork has the same class of defect at 12 further sites upstream does not have: `core/tools/vmaf.cpp:146`/`:1202`/`:1318`/`:1325`, `core/src/read_json_model.cpp:612`/`:742`, `core/tools/vmaf_per_shot.c:672`/`:761`, `core/tools/vmaf_roi.c:250`/`:403`, `core/tools/vmaf_bench.c:139-140`, `core/tools/vmaf_vpl.c:187`, `core/src/dnn/model_loader.c:383`/`:819`/`:984`, `core/src/interop/pelorus_qp_report_csv.c:298`. | Not reproducible on Linux — every call is byte-transparent there. The evidence is the grep-provable API misuse plus the absence of any ACP mitigation; end-to-end confirmation needs a Windows CI leg with a non-ASCII log path. | Owner-driven; needs a new `core/src/compat/path_utf8.{h,c}` surface and a documented encoding contract on the public API, so it needs its own ADR. | Closes when an internal `vmaf_fopen_utf8` / `vmaf_open_utf8` shim (wide on `_WIN32`, a literal no-op on POSIX) is wired through `output_file_open()` and the 12 fork-added sites, `docs/api/` states that path arguments are UTF-8 on Windows, and a Windows-gated unit test round-trips a non-ASCII filename. | - -| **T-UPSTREAM-818-POOLING-ENUM-NO-PERCENTILES-2026-09-03** | Netflix/vmaf#818, verified. `core/include/libvmaf/libvmaf.h:127-133` still exposes only `{UNKNOWN, MIN, MAX, MEAN, HARMONIC_MEAN, NB}`, so a C-API or Go-binding caller cannot ask for perc5/perc10/perc20/median five years after the report. Two of the report's implied claims are **refuted** here and should not be re-investigated: (a) there is no silent fallback to mean — `pool_reduce()` (`core/src/libvmaf.c:3061-3090`) ends in `default: return -EINVAL;` and the three public entry points reject `VMAF_POOL_METHOD_UNKNOWN` up front; upstream does the same. (b) The Python `perc5/perc10/perc20/median` options never reach the C pooling code — `Result._try_get_aggregate_score` applies `ListStats.perc10` to the per-frame list in NumPy, and `python/test/quality_runner_test.py:662-681` is a passing golden assertion proving it computes a true percentile. Every in-tree C consumer hardcodes MEAN and the CLI has no `--pool` flag, so the gap is currently unreachable from the shipped binary; the three ffmpeg filter patches do route a user-selectable option through `pool_method_map`. | `grep -n VMAF_POOL_METHOD core/include/libvmaf/*.h` shows no `PERC` / `MEDIAN` enumerator. `grep -rn '\"pool\"\|--pool' core/tools/ docs/usage/` is empty. | Owner-driven; append-only enum growth plus a percentile buffer, and CLAUDE.md §12 r14 on `ffmpeg-patches/0005`, `0006` and `0013`. | Closes when the four enumerators are appended after `HARMONIC_MEAN`, `pool_reduce` gains a sorted-vector path reusing `core/src/predict.c:580`'s `percentile()` helper (matching `np.percentile`'s linear interpolation so C and the Python harness agree), the weighted-pooling interaction is decided in an ADR, the three ffmpeg patches are updated, and a test asserts the C perc10 matches `quality_runner_test.py:679`'s 72.71845922683059. | - - + @@ -412,6 +406,11 @@ landed fix yet._ | **T-RELEASE-RC-PEP440-VERSION-MISMATCH-2026-09-07** | The `1.0.0-rc.1` release PR (#1213) was red on **eleven build lanes simultaneously** — `Ubuntu gcc` / `clang` / `gcc static` / `ARM clang`, both `+DNN` variants, `macOS clang`, `macOS Metal`, and the three FFmpeg lanes — each failing on one assertion, `test/setup_metadata_test.py::test_setup_metadata_version_matches_package_marker`: `assert '1.0.0rc1' == '1.0.0-rc.1'`. Every lane runs the Python suite, so a single Python-level defect presented as a total build failure and read like a broken release. **Nothing was wrong with the release.** `vmaf.__version__` carries the SemVer string release-please writes, and for a release candidate [ADR-1201](adr/1201-release-candidates-before-1-0-0.md) makes that a hyphenated prerelease, `1.0.0-rc.1`. setuptools canonicalises whatever it is handed to PEP 440 before publishing it (`Distribution._normalize_version`), so the same release reaches `setup.py --version` as `1.0.0rc1`. Two spellings of one version. The test compared them as **raw strings**, which held only while every release was a plain `MAJOR.MINOR.PATCH` — where normalisation is a no-op — so the defect was latent from the day the test was written and the first RC was the first version that could expose it. **FIXED**: the comparison is made on parsed `packaging.version.Version` objects, and a new `test_package_marker_is_a_valid_version` rejects an unsubstituted `x-release-please` or `${...}` marker outright. The file is now **stricter** than before, not looser: a marker comment leaking into the shipped version fails either as an inequality or as `InvalidVersion`. `packaging` needed no new requirements entry — pytest declares `packaging>=22` as a hard non-extra dependency, verified with `importlib.metadata.requires('pytest')`. Verified by simulating the release on the branch (`__version__ = "1.0.0-rc.1"` in `compat/python-vmaf/__init__.py`): plain `3.2.1` passes old and new; `1.0.0-rc.1` fails the old test with the exact CI message and passes the new one; `x-release-please-version` fails both new tests, confirming the regression the file exists to catch is still caught. | [ADR-1201](adr/1201-release-candidates-before-1-0-0.md), [ADR-0165](adr/0165-state-md-bug-tracking.md) | PR #1401 | 2026-09-07 | closed | | **T-METAL-INTEGER-ADM-P-NORM-IGNORED-2026-09-07** | `integer_adm_metal.mm` declared `adm_p_norm` (alias `apn`, default 3.0, range 1.0-20.0) with `VMAF_OPT_FLAG_FEATURE_PARAM`, so the engine accepted it, range-checked it and folded it into the [ADR-1183](adr/1183-model-options-gate-gpu-twin-selection.md) derived feature name — and then `conclude_adm_cm()` computed the numerator p-norm with a hardcoded `1.0f / 3.0f`. A run with `apn=2` therefore published a p = 3 number under the key `integer_adm2_apn_2`, which is worse than an unimplemented option because the output schema asserts the setting was applied. **Fourth instance of the class** documented in [research digest 2037](research/2037-advertised-but-unimplemented-gpu-options.md) (after `motion_fps_weight`, the float-VIF constants and the float-ADM trio) and the first on an *integer* twin. Found by a tree-wide audit of `.offset = offsetof(...)` option fields that are never read back, which flagged 11 candidates across 280 sources; ten were correctly justified (the HIP and SYCL integer-ADM twins do not emit `adm3`, the only score `adm_dlm_weight` / `adm_min_val` affect, and say so inline) and this was the one real hit. Metal was the sole `integer_adm` twin affected — CUDA, SYCL and HIP all pass `adm_p_norm` into their reduction; the giveaway was the member-access count per twin (CPU 4, CUDA 2, SYCL 1, HIP 1, **Metal 0**). **FIXED**: `conclude_adm_cm()` takes `p_norm` and both call sites pass `s->adm_p_norm`. Only the numerator is parameterised, mirroring the CPU, whose `adm_den_scale_finalise` denominator is a fixed cube root in every backend — verified by classifying each hardcoded `1/3` site as numerator or denominator across all five implementations. The default path is bit-identical (`1.0f / (float)3.0` and `1.0f / 3.0f` are the same float), so no shipped score moves. `test_metal_integer_adm_parity` gains `test_integer_adm_p_norm_reaches_kernel`; the pre-existing default-only test could not see the bug, because at p = 3 the hardcoded constant is the correct answer. **Not verified on device** — this workstation has no Apple silicon, so the parity variant is proven only by the macOS Metal CI lanes. | [ADR-1183](adr/1183-model-options-gate-gpu-twin-selection.md), [ADR-0214](adr/0214-gpu-parity-ci-gate.md) | PR #1404 | 2026-09-07 | closed | | **T-CODE-SCANNING-1243-FIX-2026-09-08** | Resolved actionable open GitHub code-scanning alerts for epic #1243 and audited the full 22-alert inventory against `origin/master`: (1) Fixed `cpp/loop-variable-changed` in `core/tools/cli_parse.cpp` (Alerts 1030 and 1031) by converting `for` loops in `cli_split()` and `cli_unescape()` with loop-body counter modifications to idiomatic `while` loops; (2) Fixed `cpp/commented-out-code` in `core/test/test_model_feature_overload_ownership.c` (Alert 951) by replacing a trailing semicolon in a comment with a comma; (3) Fixed `py/unused-global-variable` in `mcp-server/vmaf-mcp/src/vmaf_mcp/server.py` (Alerts 970 and 971) by removing an obsolete duplicated constants block (`_VALID_AOM_CTCS`, `_VALID_NFLX_CTCS`) and consolidating `_VALID_OUTPUT_FMTS` into the canonical constants section; (4) Verified and catalogued dispositions for all remaining open alerts: Alert 1005 (`convolve.c`) is load-bearing under ADR-0138; Alert 1036 is a transient Meson compiler probe; Alerts 1002/1003 are variadic template pack false positives; Alerts 946, 947-949 carry existing inline `# nosemgrep` and `usedforsecurity=False`; Alerts 168, 927 are exact float sentinel/default comparisons; Alerts 908, 943, 955 are white-box test text-includes; Alerts 917/918 are function-local MCP imports; Alerts 1 and 3 are OpenSSF Scorecard repository-level process items. Zero alerts dismissed by agent (maintainer decision per policy). Golden assertions untouched; `meson test -C core/build --suite=fast -j4` 119/119 OK, MCP pytest 375/42/0 OK. | [research digest 2040](research/2040-code-scanning-cleanup-1243.md) | fix/1243-security-cleanup | 2026-09-08 | closed | +| **T-UPSTREAM-818-POOLING-ENUM-NO-PERCENTILES-2026-09-03** | Netflix/vmaf#818: `enum VmafPoolingMethod` exposed only `{UNKNOWN, MIN, MAX, MEAN, HARMONIC_MEAN}`, so a C-API, Rust-binding or FFmpeg caller could not request `median` / `perc5` / `perc10` / `perc20` — the summaries the Python harness has always produced in NumPy via `ListStats` — five years after the report. A verified feature gap, not a latent defect: the report's "silently falls back to mean" claim stays **refuted** (`pool_reduce()` ended in `default: return -EINVAL;` and a pre-fix probe returned `rc=-22` for discriminants 5–8). Fixed by appending `VMAF_POOL_METHOD_MEDIAN` / `_PERC5` / `_PERC10` / `_PERC20` after `HARMONIC_MEAN` (append-only; `VMAF_POOL_METHOD_NB` 5 → 9) plus `VMAF_HAVE_PERCENTILE_POOLING`; the frame walk moved into `pool_accumulate()`, which retains the per-frame scores in a geometrically-grown buffer only for the order-statistic methods, and `pool_reduce_percentile()` sorts and interpolates via the new header-only `core/src/percentile.h` (shared verbatim with `predict.c`'s golden-asserted bootstrap `ci_p95`). The rule is `numpy.percentile(method="linear")`, so C and the harness agree. Accumulator arithmetic untouched (ADR-1118 isolation); percentiles ignore perceptual weighting, as MIN/MAX already do; `output.cpp` iterates an explicit `pool_report_order[]` so `pooled_metrics` still emits exactly `min`/`max`/`mean`/`harmonic_mean`. Rust `PoolingMethod::{Median,Perc5,Perc10,Perc20}`; `ffmpeg-patches/0018` maps the option strings (and `max`, which FFmpeg never mapped) — 18/18 patches replay onto pristine n9.0.1. Verification: `core/test/test_pool_percentile.c` 6/6 (C `perc10` of the golden pair matches `numpy.percentile` to < 1e-12 and `quality_runner_test.py:679`'s 72.71845922683059 within its own `places=2` tolerance); `meson test --suite=fast` 115/115; Netflix golden gate 271 passed / 12 skipped. Follow-up left open on purpose: the `vmaf` CLI still has no `--pool` flag (`docs/reference/faq.md` wrongly claimed one and was corrected). | [ADR-1188](adr/1188-percentile-pooling-methods.md), [Research-1188](research/1188-percentile-pooling-methods.md), [ADR-1118](adr/1118-perceptual-sidedata-weighting.md) | PR #1340 (commit `b43fc4414`) | 2026-09-06 | closed | +| **T-SVTAV1-HDR-ADAPTER-2026-05-20** | `vmaf-tune` lacked documented tuning knobs and runtime adapter specifications for the HDR-focused SVT-AV1 fork [`juliobbv-p/svt-av1-hdr`](https://github.com/juliobbv-p/svt-av1-hdr/). Documented the SVT-AV1-HDR runtime variant `libsvtav1@svt-av1-hdr` (at `0033340`, 2026-09-01) and its complete `-svtav1-params` knob table (valid ranges and defaults reconstructed from upstream `Docs/Parameters.md`) across `docs/usage/vmaf-tune.md` and `docs/usage/vmaf-tune-codec-adapters.md` per ADR-0644 and ADR-0294. Specified the three injection points for `-svtav1-params` and the inherited CRF/preset window; `'svtav1-hdr' in known_codecs()` remains `False` by design. | [ADR-0644](adr/0644-vmaftune-codec-adapter-abstraction.md), [ADR-0294](adr/0294-vmaf-tune-crf-windowing.md) | PR #1296 (commit `e335857f4`) | 2026-09-04 | closed | +| **T-VMAFTUNE-PROFILE-REPORT-AUDIT-2026-05-20** | Profile-card report generator (`tools/vmaf-tune/src/vmaftune/report.py` and `cli.py`) underwent a deep audit for visual density, layout, accessibility, and reproducibility. Resolved findings #2–#10: unified bitrate axis labels and tick formatting (`kbps` / `Mbps`), em-dash rendering for failed rows (0/NaN) in HTML/Markdown, distinct palette slots (15–17) for VideoToolbox encoders to prevent collision, deduplicated Pareto annotations with bitrate context, `--json-sidecar` CLI flag and `ReportData.from_dict` round-trip support, picked-CRF scatter plot labels with deduplicated legend entries, byte-identical SVG/HTML output via timestamp stripping and `svg.hashsalt`, and failed-target markers on sweep charts. Added 303 lines of regression tests in `tools/vmaf-tune/tests/test_report.py`. | none (bug fix) | PR #1296 (commit `e335857f4`) | 2026-09-04 | closed | +| **T-METAL-MOTION-V2-MIRROR-OFF-BY-ONE-2026-09-03** | Fixed by PR #1223 (commit `71da046db`, ADR-1166); header comment corrected + closeout ADR-1176 in branch `fix/metal-motion-v2-mirror-closeout`. `core/src/feature/metal/integer_motion_v2.metal:74-80` `mv2_mirror` updated to iterated reflect-101 `idx = (idx < 0) ? -idx : 2 * (sup - 1) - idx`, matching CPU `integer_motion_v2.c:157`, CUDA `mv2_mirror`, SYCL `dev_mirror_mv2` and HIP `mv2_mirror`. Header comment contradiction corrected; closeout ADR-1176 landed; `core/test/test_metal_motion_v2_parity.c` updated to set `mu_skipped = 1` (exit 77) on `-ENODEV` and emit stdout on device. No Metal snapshot exists in `testdata/` (`git ls-tree origin/master testdata | grep -i metal` = 0), so no snapshot regeneration needed. | [ADR-1176](adr/1176-metal-motion-v2-mirror-closeout.md), [ADR-1166](adr/1166-upstream-issue-harvest.md) | PR #1294 (commit `8d103e3cc`) | 2026-09-04 | closed | +| **T-UPSTREAM-1564-ADM-CM-GPU-BORDER-AND-ROUNDING-2026-09-03** | Fixed two confirmed GPU numerical defects in VMAFx integer ADM contrast masking kernels (`adm_cm.cu` and `adm_cm.hip`) identified during harvest of Netflix/vmaf#1564. (1) Border row selection at `i == 0 && top <= 0` (scale 3 height <= 14 px): replaced running pointers with absolute indexing `{row_top, row_bot, col_l, col_r}` and evaluated `csf_a` at row 0 center instead of row 2. (2) Distributed rounding shift across warps/threads: launched kernels with `gridDim.x = 1` and accumulated across columns in 64-bit precision, applying `(row_total + add_shift_inner_accum) >> shift_inner_accum` once per row instead of per-warp (CUDA) or per-thread (HIP). Added regression parity tests `test_cuda_adm_small_border` and `test_cuda_adm_wide_rounding` (and HIP twins). Residual (3) x86 `half_w_modN` tail bound fixed on AVX2 and AVX-512 (`adm_avx2.c`, `adm_avx512.c`) to always leave the last column to scalar mirror loop (`half_w - 1 - ((half_w - 2) % N)`); verified with `core/test/test_adm_dwt2_x86.c`. Netflix golden assertions untouched (271 passed). | [ADR-1167](adr/1167-adm-cm-row-level-rounding.md) (supersedes [ADR-0539](adr/0539-hip-adm-kernels-real.md)) | PR #1224 (commit `6c843bb84`) | 2026-09-03 | closed | | **T-GPU-MOTION3-FPS-WEIGHT-SQUARED-2026-09-07** | The CUDA, SYCL and HIP `motion` twins applied `motion_fps_weight` **twice**, so `VMAF_integer_feature_motion3_score` carried the weight squared whenever the option was set away from its `1.0` default. The CPU reference applies it in exactly one place — `extract()` in `core/src/feature/integer_motion.c`, which stores the *weighted* SAD as `motion_sad_score`; `flush()` then blends that already-weighted value into `motion2` / `motion3` without touching the weight again. All three twins' host-side `motion3_postprocess_*()` opened with `score2 * motion_fps_weight` while every caller already passed a fps-weighted, `motion_max_val`-clipped value. `motion2_score` was unaffected. Green in every gate because `1.0² = 1.0` and every motion3 parity test instantiated its extractors with `NULL` options — the option-value analogue of the fixture-shape blind spot in ADR-1204 / ADR-1206. **FIXED**: the second multiplication is removed from all three helpers, and each of `test_{cuda,sycl,hip}_motion3_parity` gained a `test_motion3_fps_weight_applied_once` variant pinning `motion_fps_weight = 0.6` and reading the ADR-1183-derived `integer_motion3_mfw_0.6` key. Measured drift before the fix, identical on all three backends (256x144 8-bpc): `cpu = 14.48987751`, `gpu = 8.69392654` (`= cpu × 0.6`), delta `5.80e+00` against the `1e-4` ADR-0214 gate. Verified on an RTX 4090, an Arc A380 and gfx1030: each variant fails with the second multiplication restored and passes with it removed. `float_motion` (weight applied once at the emission site) and the `motion_v2` twins (separate, documented seed-frame divergence) were audited and left unchanged. | [research digest 2033](research/2033-identity-default-option-blind-spot.md), [ADR-1216](adr/1216-gpu-motion3-fps-weight-applied-once.md) | PR #1375 | 2026-09-07 | closed | | **T-GPU-FLOAT-VIF-OPTIONS-IGNORED-2026-09-07** | The CUDA, SYCL and HIP `float_vif` compute kernels hardcoded `vif_sigma_nsq = 2.0f` and `vif_enhn_gain_limit = 100.0f` as kernel-local constants. Both are `VMAF_OPT_FLAG_FEATURE_PARAM` options that all three twins declare with the CPU's names, aliases, defaults and ranges; the host never forwarded them and `init()` validates only `vif_kernelscale`, so a non-default value was accepted, range-checked, folded into the ADR-1183-derived feature name, and then discarded. **Reachable from a shipped model**: `model/vmaf_float_v0.6.1neg.json` sets `vif_enhn_gain_limit = 1.0` on all four VIF-scale features — the setting that makes it the NEG model — so every GPU run of the NEG model published ordinary enhancement-gain-enabled scores under the NEG feature keys, with nothing in the output schema revealing it. **FIXED**: all three kernels take `vif_sigma_nsq`, `vif_egl` and the host-derived `sigma_max_inv` as arguments, computed exactly as `vif_tools.c::vif_statistic_s` computes them, so the default path is unchanged. Measured drift at `egl=1.0 snsq=1.5`, scale 0, against the `1e-4` ADR-0214 gate (per-backend fixtures, so comparable to the gate but not to each other): CUDA `cpu=0.22693315 gpu=0.24385797 delta=1.69e-02`, SYCL `cpu=0.81855110 gpu=0.81771439 delta=8.37e-04`, HIP `cpu=0.56285676 gpu=0.55776394 delta=5.09e-03`. No test caught it: the CUDA and SYCL float-VIF parity tests ran with `NULL` options (where the hardcoded values are correct), and `float_vif_hip` had no parity test at all — `test_hip_vif_parity.c` targets the *integer* `vif_hip` twin. Each test now carries a `test_float_vif_options_reach_kernel` variant reading the derived `vif_scale0_egl_1_snsq_1.5` key, and `core/test/test_hip_float_vif_parity.c` is new. The Metal twin was audited and already threads both options through. **Any NEG-model run made on a GPU backend before this fix must be re-scored.** | [research digest 2034](research/2034-kernel-local-constants-shadowing-options.md), [ADR-1217](adr/1217-gpu-float-vif-options-reach-kernel.md) | PR #1376 | 2026-09-07 | closed | | **T-GPU-SPEED-SINGULAR-SOLUTION-2026-09-07** | SpEED's 25x25 covariance is regular only if **every** eigenvalue is >= 1e-6; the CPU zeroes the solution on a singular plane, reports the singularity separately, and `speed_extract_score()` returns **0** when exactly one of ref/dis was singular. Two GPU divergences. (a) All six twins (`speed_chroma` + `speed_temporal` on CUDA/SYCL/HIP) zeroed the **host** staging buffer and uploaded nothing, so the score kernel read the **device** solution left from the previous frame — or, on the first frame, raw allocator memory (`sycl::malloc_device` is explicitly uninitialised). The host memset was dead code: `h_indterm` is re-downloaded from `d_indterm` every run. (b) [ADR-1202](adr/1202-cuda-speed-chroma-4k-launch-bounds.md) (PR #1360) fixed singularity reporting for the **chroma** twins only; the three **temporal** twins returned success on a singular matrix, so the one-sided-zero rule could not exist and they returned the kernel's inflated score. **(b) is a first-order scoring bug**, reachable from any static passage: measured on a 960x960 fixture with a frozen reference and a moving distorted side, `cpu = 0.00000000` vs `gpu = 230.71379089`, identical on RTX 4090 / Arc A380 / gfx1030 (host-side scalar control flow, shared in shape). **(a) has no demonstrable score impact** — with both sides singular the CPU's own zeroed solution drives every variance to 0 and the score to exactly 0 regardless — but it is an uninitialised device read. **FIXED**: `d_sol` is zeroed on the device in all six twins, and the three temporal twins gained `singular_out` plus the one-sided-zero rule. No existing test could see either: the SpEED parity fixtures are 768x432, whose chroma planes give 4x2 = 8 blocks for a 25x25 covariance — rank-deficient by construction, so `is_matrix_regular()` is false on every frame and the regular path is never exercised. New `test_{cuda,sycl,hip}_speed_singular_parity` use 960x960 (36 chroma blocks, 144 luma) and drive three fixtures: temporal one-sided, temporal both-sides, chroma both-sides. **Re-measure any GPU `speed_temporal` score taken over content with static passages.** | [research digest 2035](research/2035-singular-covariance-fixture-design.md), [ADR-1218](adr/1218-gpu-speed-singular-device-solution.md), [ADR-1202](adr/1202-cuda-speed-chroma-4k-launch-bounds.md) | PR #1377 | 2026-09-07 | closed |