Skip to content

feat(core): add percentile temporal pooling to the public C API - #1340

Merged
lusoris merged 7 commits into
masterfrom
fix/t-upstream-818-pooling-enum-no-percentil
Sep 7, 2026
Merged

feat(core): add percentile temporal pooling to the public C API#1340
lusoris merged 7 commits into
masterfrom
fix/t-upstream-818-pooling-enum-no-percentil

Conversation

@lusoris

@lusoris lusoris commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Summary

enum VmafPoolingMethod exposed only {UNKNOWN, MIN, MAX, MEAN, HARMONIC_MEAN}, so a
C-API, Rust-binding or FFmpeg caller could not ask for the median / perc5 / perc10 /
perc20 summaries the Python harness has always offered — any discriminant past
HARMONIC_MEAN was rejected with -EINVAL. This appends the four order-statistic
enumerators (ABI-safe: existing values are unchanged), computes them by sorting the pooled
per-frame scores and interpolating linearly between ranks — the same
numpy.percentile(method="linear") rule the harness uses — and leaves the golden
accumulator path byte-identical. Closes the verified ledger row
T-UPSTREAM-818-POOLING-ENUM-NO-PERCENTILES-2026-09-03 (upstream Netflix/vmaf#818).

Type

  • feat — new feature

Checklist

  • Commits follow Conventional Commits (the commit-msg hook enforces this).
  • make format && make lint is green locally.
  • Unit tests pass: meson test -C build.
  • If I touched any SIMD/GPU code path, I ran /cross-backend-diff and the worst ULP is ≤ 2.
  • If I touched a feature extractor with SIMD/GPU twins, I either updated every twin or listed the gap under "Known follow-ups" below.
  • If I added a new .c / .cpp / .cu / .h / .hpp, it has the appropriate license header (see CONTRIBUTING.md).
  • If this is a breaking change, the commit message uses ! or BREAKING CHANGE: and the migration path is documented below.
  • If this PR adds an ADR, the ADR row lives in docs/adr/_index_fragments/<NNNN-slug>.md and the slug is appended to docs/adr/_index_fragments/_order.txt.

No SIMD/GPU code path is touched — pooling is backend-independent host code that reads the
feature collector, so there is no twin to update and no cross-backend ULP table to paste.
This is not a breaking change: the enum grows append-only and VMAF_POOL_METHOD_NB (already
documented as an unstable count sentinel) moves 5 → 9.

Bug-status hygiene (ADR-0165)

  • docs/state.md updated in this PR: the
    T-UPSTREAM-818-POOLING-ENUM-NO-PERCENTILES-2026-09-03 row moves from Open bugs to
    Recently closed.

Netflix golden-data gate (ADR-0024)

  • I did not modify any assertAlmostEqual(...) score in the Netflix golden Python tests.

The full gate was run locally on the CPU backend: 271 passed, 12 skipped across
quality_runner_test.py, feature_extractor_test.py, vmafexec_test.py,
vmafexec_feature_extractor_test.py and result_test.py. The accumulator methods keep
their exact upstream float expressions (ADR-1118 golden-gate isolation), and
vmaf_percentile() stays static inline so predict.c's golden-asserted bootstrap
ci_p95 arithmetic is still compiled in its own translation unit.

Cross-backend numerical results

n/a — host-side pooling only; no SIMD or GPU kernel is touched.

Performance (if perf or feat)

MIN / MAX / MEAN / HARMONIC_MEAN are unchanged: same expressions, O(1) space, zero
allocation. Only the new order-statistic methods allocate — 8 × n_frames bytes, grown
geometrically, plus one qsort.

Deep-dive deliverables (ADR-0108)

  • Research digestdocs/research/1188-percentile-pooling-methods.md.
  • Decision matrixdocs/adr/1188-percentile-pooling-methods.md ## Alternatives considered (five options).
  • AGENTS.md invariant notecore/src/AGENTS.md, "Pooling: accumulators stay O(1) and byte-identical; percentiles buffer (ADR-1188)".
  • Reproducer / smoke-test command — pasted below under "Reproducer".
  • CHANGELOG fragmentchangelog.d/added/percentile-pooling-methods.md.
  • Rebase notedocs/rebase-notes.md, entry fix/t-upstream-818-pooling-enum-no-percentil.

Reproducer

# Before: every percentile discriminant is rejected. After: they pool.
meson setup build-cpu core -Denable_cuda=false -Denable_sycl=false
ninja -C build-cpu
./build-cpu/test/test_pool_percentile          # 6 tests run, 6 passed
meson test -C build-cpu --suite=fast           # 115/115 OK

# The C perc10 of the golden pair's frames vs the Python harness's golden constant:
CUDA_VISIBLE_DEVICES="" VMAF_FORCE_BACKEND=cpu PYTHONPATH=$PWD/python \
  python3 -m pytest python/test/quality_runner_test.py -k pool_perc10 -q

# ffmpeg patch series still replays onto pristine n9.0.1 (18/18):
git -C /path/to/ffmpeg checkout -B verify n9.0.1
for p in ffmpeg-patches/00*-*.patch; do git -C /path/to/ffmpeg am --3way "$p" || break; done

Pre-fix evidence (origin/master sources, same probe binary, discriminants 5–8 passed
through a cast because the enumerators did not exist yet):

MEDIAN(5)  rc=-22 score=-1.000000
PERC5(6)   rc=-22 score=-1.000000
PERC10(7)  rc=-22 score=-1.000000
PERC20(8)  rc=-22 score=-1.000000

Post-fix, same probe:

MEDIAN(5)  rc=0 score=2.500000
PERC5(6)   rc=0 score=1.150000
PERC10(7)  rc=0 score=1.300000
PERC20(8)  rc=0 score=1.600000

Known follow-ups

  • The vmaf CLI still has no --pool flag; its report carries min / max / mean /
    harmonic_mean and its stdout line reports the mean. docs/reference/faq.md claimed such
    a flag existed and has been corrected. Adding one — and/or widening the pooled_metrics
    schema to carry percentiles — is a deliberate output-surface decision left out of this PR
    (ADR-1188 "Neutral / follow-ups").

@lusoris lusoris added this to the 1.0.0 — First release milestone Sep 5, 2026
lusoris pushed a commit that referenced this pull request Sep 5, 2026
…-03 (PR #1340)

Move the pooling-enum row from Open bugs to Recently closed now that the four
order-statistic enumerators ship (ADR-1188), and point the Confirmed
not-affected entry for Netflix/vmaf#818 at the closed row. Per ADR-0165 the
ledger update lands in the same PR as the fix.
lusoris pushed a commit that referenced this pull request Sep 6, 2026
…-03 (PR #1340)

Move the pooling-enum row from Open bugs to Recently closed now that the four
order-statistic enumerators ship (ADR-1188), and point the Confirmed
not-affected entry for Netflix/vmaf#818 at the closed row. Per ADR-0165 the
ledger update lands in the same PR as the fix.
@lusoris
lusoris force-pushed the fix/t-upstream-818-pooling-enum-no-percentil branch from b365939 to fc96927 Compare September 6, 2026 03:04
lusoris pushed a commit that referenced this pull request Sep 6, 2026
…-03 (PR #1340)

Move the pooling-enum row from Open bugs to Recently closed now that the four
order-statistic enumerators ship (ADR-1188), and point the Confirmed
not-affected entry for Netflix/vmaf#818 at the closed row. Per ADR-0165 the
ledger update lands in the same PR as the fix.
@lusoris
lusoris force-pushed the fix/t-upstream-818-pooling-enum-no-percentil branch from fc96927 to f9fca45 Compare September 6, 2026 06:39
@lusoris

lusoris commented Sep 6, 2026

Copy link
Copy Markdown
Contributor Author

Duplicate resolution: this PR is the keeper for percentile temporal pooling

#1311 implements the same feature (its Part A, ADR-1181). Comparing the two diffs, this
PR wins on every axis and #1311's percentile half should be dropped — full evidence in
#1311 (comment). Summary:

Both implementations produce the same numbers (#1311's 72.71734120 vs this PR's
72.717340155042217 for perc10 on the golden pair), so this is a completeness and
memory-safety decision, not a disagreement about the formula.

#1311 stays open — it also carries the unrelated Windows UTF-8 path contract
(ADR-1182 / Netflix#1568), which nothing else covers. It has been asked to de-scope to
that half.

Ledger: both PRs move T-UPSTREAM-818-POOLING-ENUM-NO-PERCENTILES-2026-09-03 to
Recently closed in docs/state.md. This PR keeps that row; #1311 drops it. Only one row
per id survives, or scripts/ci/check-state-md-rows.sh fails.

Follow-up to pick up here or in a separate PR: #1311 is the only place the Go binding
learns the new methods — PoolMethod / String() / ParsePoolMethod / toC() in
pkg/libvmaf/direct.go, the ScoreDirectRequest.PoolMethod and StreamConfig.PoolMethod
fields that replace the hardcoded C.VMAF_POOL_METHOD_MEAN in ScoreDirect and
StreamScorer.Finish, and the two table tests. This PR covers the Rust side
(bindings/rust/vmafx/src/score.rs) but not Go. That work should be re-targeted here
rather than lost when #1311 is de-scoped.

lusoris pushed a commit that referenced this pull request Sep 6, 2026
…-03 (PR #1340)

Move the pooling-enum row from Open bugs to Recently closed now that the four
order-statistic enumerators ship (ADR-1188), and point the Confirmed
not-affected entry for Netflix/vmaf#818 at the closed row. Per ADR-0165 the
ledger update lands in the same PR as the fix.
lusoris pushed a commit that referenced this pull request Sep 6, 2026
…t tidy-clean

Three follow-ups found while rebasing this branch onto current master:

- docs/state.md: the earlier "drop the duplicate rows a keep-both rebase
  created" commit removed the wrong copy — it deleted the branch's new
  Recently-closed row instead of the stale Open-bugs row, leaving the bug
  marked open while the moved-to comment claimed otherwise. Move the row for
  real (ADR-0165: exactly one row per id, citing PR #1340).
- core/test/test_pool_percentile.c: the file is new, so its clang-tidy ratchet
  baseline is 0 and its 18 findings were a regression (ADR-0141 / ADR-1142).
  Drive the expectations off PoolCase tables through a shared helper so each
  test body stays inside readability-function-size's 15-branch budget, use
  nullptr, and drop the deprecated VMAF_POOL_METHOD_NB references. The one
  remaining finding is the deliberate out-of-range discriminant cast, which is
  the subject of the negative test; it carries an inline ADR-1188 citation.
- docs/reference/faq.md: the list names eight pooling methods, not seven.

Behaviour is unchanged: 6/6 in test_pool_percentile, 115/115 in --suite=fast.
@lusoris
lusoris force-pushed the fix/t-upstream-818-pooling-enum-no-percentil branch from f9fca45 to 37c3da6 Compare September 6, 2026 09:48
@lusoris

lusoris commented Sep 6, 2026

Copy link
Copy Markdown
Contributor Author

Queue audit (read-only verification pass, 2026-09-06). Two findings on this PR, both checked locally rather than inferred.

1. ADR-1138 violation — nullptr in a C translation unit.
core/test/test_pool_percentile.c uses the C23 nullptr keyword on 16 code lines (return nullptr;, VmafContext *vmaf = nullptr;, ...). ADR-1138 (Accepted) is explicit that C translation units keep NULL, because the documented MSVC /std:clatest C23 feature set does not include nullptr and the required Windows MSVC + CUDA leg builds core/ with ninja -v -C core\build install (which builds the test executables too). No other C TU in the tree uses the keyword: every nullptr hit under core/**/*.c on master sits inside an ADR-1138 NOLINT comment. PR #1338 had to make exactly this fix twice (fd101e6fd, f8fcde881). The fix is mechanical: nullptr -> NULL.

2. Duplicate feature with PR #1311.
#1311 Part A implements the same four enumerators (VMAF_POOL_METHOD_MEDIAN / _PERC5 / _PERC10 / _PERC20), the same numpy-linear interpolation rule, and closes the same ledger row T-UPSTREAM-818-POOLING-ENUM-NO-PERCENTILES-2026-09-03. Both PRs add an ADR with the identical slug (docs/adr/1181-percentile-pooling-methods.md vs docs/adr/1188-percentile-pooling-methods.md). git merge-tree between the two heads conflicts in 8 files, including core/include/libvmaf/libvmaf.h, core/src/predict.c, core/src/output.cpp and docs/state.md. Only one of the two can land; that is a maintainer call, not something a rebase resolves.

Otherwise clean: no merge conflict against current master, the six-deliverable gate passes locally, and the new test file measures 0 clang-tidy findings under the repo .clang-tidy at -std=c23.

lusoris pushed a commit that referenced this pull request Sep 6, 2026
…-03 (PR #1340)

Move the pooling-enum row from Open bugs to Recently closed now that the four
order-statistic enumerators ship (ADR-1188), and point the Confirmed
not-affected entry for Netflix/vmaf#818 at the closed row. Per ADR-0165 the
ledger update lands in the same PR as the fix.
lusoris pushed a commit that referenced this pull request Sep 6, 2026
…t tidy-clean

Three follow-ups found while rebasing this branch onto current master:

- docs/state.md: the earlier "drop the duplicate rows a keep-both rebase
  created" commit removed the wrong copy — it deleted the branch's new
  Recently-closed row instead of the stale Open-bugs row, leaving the bug
  marked open while the moved-to comment claimed otherwise. Move the row for
  real (ADR-0165: exactly one row per id, citing PR #1340).
- core/test/test_pool_percentile.c: the file is new, so its clang-tidy ratchet
  baseline is 0 and its 18 findings were a regression (ADR-0141 / ADR-1142).
  Drive the expectations off PoolCase tables through a shared helper so each
  test body stays inside readability-function-size's 15-branch budget, use
  nullptr, and drop the deprecated VMAF_POOL_METHOD_NB references. The one
  remaining finding is the deliberate out-of-range discriminant cast, which is
  the subject of the negative test; it carries an inline ADR-1188 citation.
- docs/reference/faq.md: the list names eight pooling methods, not seven.

Behaviour is unchanged: 6/6 in test_pool_percentile, 115/115 in --suite=fast.
@lusoris
lusoris force-pushed the fix/t-upstream-818-pooling-enum-no-percentil branch from 37c3da6 to 44d32cb Compare September 6, 2026 11:09
lusoris pushed a commit that referenced this pull request Sep 6, 2026
…-03 (PR #1340)

Move the pooling-enum row from Open bugs to Recently closed now that the four
order-statistic enumerators ship (ADR-1188), and point the Confirmed
not-affected entry for Netflix/vmaf#818 at the closed row. Per ADR-0165 the
ledger update lands in the same PR as the fix.
lusoris pushed a commit that referenced this pull request Sep 6, 2026
…t tidy-clean

Three follow-ups found while rebasing this branch onto current master:

- docs/state.md: the earlier "drop the duplicate rows a keep-both rebase
  created" commit removed the wrong copy — it deleted the branch's new
  Recently-closed row instead of the stale Open-bugs row, leaving the bug
  marked open while the moved-to comment claimed otherwise. Move the row for
  real (ADR-0165: exactly one row per id, citing PR #1340).
- core/test/test_pool_percentile.c: the file is new, so its clang-tidy ratchet
  baseline is 0 and its 18 findings were a regression (ADR-0141 / ADR-1142).
  Drive the expectations off PoolCase tables through a shared helper so each
  test body stays inside readability-function-size's 15-branch budget, use
  nullptr, and drop the deprecated VMAF_POOL_METHOD_NB references. The one
  remaining finding is the deliberate out-of-range discriminant cast, which is
  the subject of the negative test; it carries an inline ADR-1188 citation.
- docs/reference/faq.md: the list names eight pooling methods, not seven.

Behaviour is unchanged: 6/6 in test_pool_percentile, 115/115 in --suite=fast.
@lusoris
lusoris force-pushed the fix/t-upstream-818-pooling-enum-no-percentil branch from 44d32cb to e5174f1 Compare September 6, 2026 12:55
lusoris pushed a commit that referenced this pull request Sep 6, 2026
…-03 (PR #1340)

Move the pooling-enum row from Open bugs to Recently closed now that the four
order-statistic enumerators ship (ADR-1188), and point the Confirmed
not-affected entry for Netflix/vmaf#818 at the closed row. Per ADR-0165 the
ledger update lands in the same PR as the fix.
@lusoris
lusoris force-pushed the fix/t-upstream-818-pooling-enum-no-percentil branch from f90bb93 to 16f2868 Compare September 6, 2026 18:12
lusoris pushed a commit that referenced this pull request Sep 6, 2026
…t tidy-clean

Three follow-ups found while rebasing this branch onto current master:

- docs/state.md: the earlier "drop the duplicate rows a keep-both rebase
  created" commit removed the wrong copy — it deleted the branch's new
  Recently-closed row instead of the stale Open-bugs row, leaving the bug
  marked open while the moved-to comment claimed otherwise. Move the row for
  real (ADR-0165: exactly one row per id, citing PR #1340).
- core/test/test_pool_percentile.c: the file is new, so its clang-tidy ratchet
  baseline is 0 and its 18 findings were a regression (ADR-0141 / ADR-1142).
  Drive the expectations off PoolCase tables through a shared helper so each
  test body stays inside readability-function-size's 15-branch budget, use
  nullptr, and drop the deprecated VMAF_POOL_METHOD_NB references. The one
  remaining finding is the deliberate out-of-range discriminant cast, which is
  the subject of the negative test; it carries an inline ADR-1188 citation.
- docs/reference/faq.md: the list names eight pooling methods, not seven.

Behaviour is unchanged: 6/6 in test_pool_percentile, 115/115 in --suite=fast.
lusoris pushed a commit that referenced this pull request Sep 7, 2026
…-03 (PR #1340)

Move the pooling-enum row from Open bugs to Recently closed now that the four
order-statistic enumerators ship (ADR-1188), and point the Confirmed
not-affected entry for Netflix/vmaf#818 at the closed row. Per ADR-0165 the
ledger update lands in the same PR as the fix.
lusoris pushed a commit that referenced this pull request Sep 7, 2026
…t tidy-clean

Three follow-ups found while rebasing this branch onto current master:

- docs/state.md: the earlier "drop the duplicate rows a keep-both rebase
  created" commit removed the wrong copy — it deleted the branch's new
  Recently-closed row instead of the stale Open-bugs row, leaving the bug
  marked open while the moved-to comment claimed otherwise. Move the row for
  real (ADR-0165: exactly one row per id, citing PR #1340).
- core/test/test_pool_percentile.c: the file is new, so its clang-tidy ratchet
  baseline is 0 and its 18 findings were a regression (ADR-0141 / ADR-1142).
  Drive the expectations off PoolCase tables through a shared helper so each
  test body stays inside readability-function-size's 15-branch budget, use
  nullptr, and drop the deprecated VMAF_POOL_METHOD_NB references. The one
  remaining finding is the deliberate out-of-range discriminant cast, which is
  the subject of the negative test; it carries an inline ADR-1188 citation.
- docs/reference/faq.md: the list names eight pooling methods, not seven.

Behaviour is unchanged: 6/6 in test_pool_percentile, 115/115 in --suite=fast.
@lusoris
lusoris force-pushed the fix/t-upstream-818-pooling-enum-no-percentil branch from 16f2868 to 40e28b9 Compare September 7, 2026 06:55
lusoris pushed a commit that referenced this pull request Sep 7, 2026
…-03 (PR #1340)

Move the pooling-enum row from Open bugs to Recently closed now that the four
order-statistic enumerators ship (ADR-1188), and point the Confirmed
not-affected entry for Netflix/vmaf#818 at the closed row. Per ADR-0165 the
ledger update lands in the same PR as the fix.
lusoris pushed a commit that referenced this pull request Sep 7, 2026
…t tidy-clean

Three follow-ups found while rebasing this branch onto current master:

- docs/state.md: the earlier "drop the duplicate rows a keep-both rebase
  created" commit removed the wrong copy — it deleted the branch's new
  Recently-closed row instead of the stale Open-bugs row, leaving the bug
  marked open while the moved-to comment claimed otherwise. Move the row for
  real (ADR-0165: exactly one row per id, citing PR #1340).
- core/test/test_pool_percentile.c: the file is new, so its clang-tidy ratchet
  baseline is 0 and its 18 findings were a regression (ADR-0141 / ADR-1142).
  Drive the expectations off PoolCase tables through a shared helper so each
  test body stays inside readability-function-size's 15-branch budget, use
  nullptr, and drop the deprecated VMAF_POOL_METHOD_NB references. The one
  remaining finding is the deliberate out-of-range discriminant cast, which is
  the subject of the negative test; it carries an inline ADR-1188 citation.
- docs/reference/faq.md: the list names eight pooling methods, not seven.

Behaviour is unchanged: 6/6 in test_pool_percentile, 115/115 in --suite=fast.
@lusoris
lusoris force-pushed the fix/t-upstream-818-pooling-enum-no-percentil branch from 40e28b9 to 9042800 Compare September 7, 2026 17:02
Lusoris and others added 3 commits September 7, 2026 20:32
`enum VmafPoolingMethod` exposed only {UNKNOWN, MIN, MAX, MEAN,
HARMONIC_MEAN}, so a C-API, Rust-binding or FFmpeg consumer could not ask
for the median or the perc5/perc10/perc20 summaries the Python harness has
always offered through `ListStats` — any discriminant past HARMONIC_MEAN
was rejected with -EINVAL. Closes the verified ledger row
T-UPSTREAM-818-POOLING-ENUM-NO-PERCENTILES-2026-09-03 (Netflix/vmaf#818).

Append VMAF_POOL_METHOD_MEDIAN / _PERC5 / _PERC10 / _PERC20 after
HARMONIC_MEAN so every existing discriminant keeps its value, and define
VMAF_HAVE_PERCENTILE_POOLING so an integration can detect them with
`#ifdef`. Percentiles cannot come from running accumulators, so the frame
walk moved into `pool_accumulate()`, which additionally retains the
per-frame scores in a geometrically-grown buffer — but only when the
requested method is an order statistic. `pool_reduce_percentile()` sorts
that buffer and interpolates linearly between ranks, sharing
`vmaf_percentile()` with the bootstrap confidence intervals via the new
header-only `core/src/percentile.h`. That rule is
`numpy.percentile(method="linear")`, so the C API and the Python harness
report the same pooled number for the same frames.

The accumulator path is untouched: MEAN / HARMONIC_MEAN run the same float
expressions in the same order, allocate nothing, and the Netflix golden
gate is unmoved (ADR-1118 isolation, 271 passed / 12 skipped). Percentiles
are order statistics and therefore ignore ADR-1118 perceptual weighting,
exactly as MIN / MAX do. `output.cpp` now iterates an explicit
`pool_report_order[]` instead of [1, VMAF_POOL_METHOD_NB), so appending
enumerators does not silently widen the `pooled_metrics` report schema.

Also: Rust `PoolingMethod::{Median, Perc5, Perc10, Perc20}`;
ffmpeg-patches/0018 maps the `pool=median|perc5|perc10|perc20` option
strings (and `max`, which FFmpeg's mapper never accepted) — all 18 patches
replay onto pristine n9.0.1 via `git am --3way`; docs/reference/faq.md no
longer claims a `--pool` CLI flag that does not exist.

Refs: ADR-1188
…-03 (PR #1340)

Move the pooling-enum row from Open bugs to Recently closed now that the four
order-statistic enumerators ship (ADR-1188), and point the Confirmed
not-affected entry for Netflix/vmaf#818 at the closed row. Per ADR-0165 the
ledger update lands in the same PR as the fix.
Each dropped row restates one origin/master already carries; master is the
authoritative record. Verified with scripts/ci/check-state-md-rows.sh.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Lusoris and others added 3 commits September 7, 2026 20:32
…t tidy-clean

Three follow-ups found while rebasing this branch onto current master:

- docs/state.md: the earlier "drop the duplicate rows a keep-both rebase
  created" commit removed the wrong copy — it deleted the branch's new
  Recently-closed row instead of the stale Open-bugs row, leaving the bug
  marked open while the moved-to comment claimed otherwise. Move the row for
  real (ADR-0165: exactly one row per id, citing PR #1340).
- core/test/test_pool_percentile.c: the file is new, so its clang-tidy ratchet
  baseline is 0 and its 18 findings were a regression (ADR-0141 / ADR-1142).
  Drive the expectations off PoolCase tables through a shared helper so each
  test body stays inside readability-function-size's 15-branch budget, use
  nullptr, and drop the deprecated VMAF_POOL_METHOD_NB references. The one
  remaining finding is the deliberate out-of-range discriminant cast, which is
  the subject of the negative test; it carries an inline ADR-1188 citation.
- docs/reference/faq.md: the list names eight pooling methods, not seven.

Behaviour is unchanged: 6/6 in test_pool_percentile, 115/115 in --suite=fast.
Each dropped row restates one origin/master already carries; master is the
authoritative record. Verified with scripts/ci/check-state-md-rows.sh.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
My rebase dedup kept the wrong side. Its rule -- when a bug id appears twice
after a keep-both rebase, keep the row matching origin/master -- is right when
the branch merely restates a row master already carries, and WRONG when the
branch is the PR that closes the bug. Then the branch's past-tense
Recently-closed row is the correct survivor and master's present-tense Open row
is the stale one.

Restored the closure row and left a tombstone where the Open row was, matching
the convention used elsewhere in this file. Exactly one row per bug id;
scripts/ci/check-state-md-rows.sh reports OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@lusoris
lusoris force-pushed the fix/t-upstream-818-pooling-enum-no-percentil branch from 9042800 to d84d273 Compare September 7, 2026 18:33
@lusoris
lusoris marked this pull request as ready for review September 7, 2026 18:34
@lusoris
lusoris enabled auto-merge (squash) September 7, 2026 18:34
…ally implements

The required Windows MSVC+CUDA lane rejected this file with 21 errors. Two
independent C-conformance problems, both invisible to gcc and clang:

1. `nullptr` in a C translation unit (19x, C2065 'undeclared identifier').
   ADR-1138 decided exactly this case: C TUs keep `NULL` because MSVC's
   documented /std:clatest C23 feature set does not list `nullptr` and the
   Windows build compiles core/ with cl.exe. The ADR called the keyword "an
   unverified bet against a required status check"; this file placed that bet
   and lost. Now `NULL`, with the file-scoped cited NOLINTBEGIN/END bracket
   the ADR prescribes.

2. A `static const double` used to initialise a `static const PoolCase[]`
   (C2099 'initializer is not a constant'). In C a `static const double` is a
   const-qualified *object*, not a constant expression, so it cannot appear in
   a static aggregate's initialiser. gcc accepts it as an extension and does
   not diagnose it even under `-std=c23 -pedantic-errors`, which is why the
   local build stayed silent. cl.exe correctly rejects it and then mis-pairs
   the remaining initialisers, producing the five cascading C2440
   'cannot convert from char [45] to double' errors -- the string literal
   sliding into the `.tol` slot vacated by the dropped constant.

   The six golden/harness constants become object-like macros, which are
   constant expressions in every context. GOLDEN_CNT above them was already a
   macro for the same reason.

Verified: `ninja -C build test/test_pool_percentile && ./test_pool_percentile`
-> 6 tests run, 6 passed, and the golden percentile values are unchanged --
the macros carry the identical literals.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
lusoris pushed a commit that referenced this pull request Sep 7, 2026
…ped tonight

PR #1340 added one new C test file that the required Windows MSVC+CUDA lane
rejected with 21 errors, on a branch that was green under local gcc. Neither
failure class was covered:

1. `nullptr` in a C translation unit (C2065 x19). ADR-1138 already decided
   that C TUs keep `NULL` — MSVC's documented /std:clatest C23 feature set
   does not implement the keyword — but nothing enforced it locally. A grep
   over changed `.c` files does, skipping comment lines because the ADR-1138
   NOLINT bracket names the keyword in prose.

2. A `static const double` used to initialise a `static const` aggregate
   (C2099, then five cascading C2440s). In C such a declaration is a
   const-qualified *object*, not a constant expression, so it may not appear
   in a static initialiser. This one cannot be grepped line-locally and, more
   importantly, no locally available compiler diagnoses it *at all* — gcc and
   clang both accept it silently under `-std=c23 -pedantic-errors`, and clang
   stays silent under `-Weverything` too. `find-nonconst-static-init.py`
   collects the file-scope `static const <scalar>` names and reports any that
   appear inside a braced `static` initialiser, with string and character
   literals masked so a name occurring in a message does not count.

Verified against the file that broke: planted as an untracked source, the
stage reports both classes and fails; with the merged fix in place it passes.
The scanner finds zero hits across all 438 `.c` files in the tree, and the
nullptr grep finds nothing outside ADR-1138 comment text — so neither check
starts life with a backlog to suppress.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@lusoris
lusoris merged commit b43fc44 into master Sep 7, 2026
77 checks passed
@lusoris
lusoris deleted the fix/t-upstream-818-pooling-enum-no-percentil branch September 7, 2026 19:16
lusoris pushed a commit that referenced this pull request Sep 7, 2026
…TS.md

Two hunks in this branch had been committed with their conflict markers still
in place — an `#include` block in `core/src/feature/sycl/integer_adm_sycl.cpp`
that needed both `adm_csf_fixed_point.h` and `adm_angle_flag.h`, and an
appended section in `core/src/feature/AGENTS.md` where both sides were wanted.
Both are keep-both resolutions: includes sorted, sections concatenated.

Rebasing onto master after #1340 landed added a third, in
`core/test/meson.build`: master's new `test_pool_percentile` executable and
this branch's `test_adm_angle_flag` claim the same position. Also keep-both —
the two are unrelated tests, and master registers `test_pool_percentile` in the
separate `test()` block further down, so only the `executable()` definitions
collide. The diff against master for that file is now purely additive.

Verified after the rebase: `meson test --suite=fast` is 119/119, with
`test_adm_angle_flag` and `test_pool_percentile` both OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
lusoris pushed a commit that referenced this pull request Sep 7, 2026
…TS.md

Two hunks in this branch had been committed with their conflict markers still
in place — an `#include` block in `core/src/feature/sycl/integer_adm_sycl.cpp`
that needed both `adm_csf_fixed_point.h` and `adm_angle_flag.h`, and an
appended section in `core/src/feature/AGENTS.md` where both sides were wanted.
Both are keep-both resolutions: includes sorted, sections concatenated.

Rebasing onto master after #1340 landed added a third, in
`core/test/meson.build`: master's new `test_pool_percentile` executable and
this branch's `test_adm_angle_flag` claim the same position. Also keep-both —
the two are unrelated tests, and master registers `test_pool_percentile` in the
separate `test()` block further down, so only the `executable()` definitions
collide. The diff against master for that file is now purely additive.

Verified after the rebase: `meson test --suite=fast` is 119/119, with
`test_adm_angle_flag` and `test_pool_percentile` both OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
lusoris pushed a commit that referenced this pull request Sep 7, 2026
…ped tonight

PR #1340 added one new C test file that the required Windows MSVC+CUDA lane
rejected with 21 errors, on a branch that was green under local gcc. Neither
failure class was covered:

1. `nullptr` in a C translation unit (C2065 x19). ADR-1138 already decided
   that C TUs keep `NULL` — MSVC's documented /std:clatest C23 feature set
   does not implement the keyword — but nothing enforced it locally. A grep
   over changed `.c` files does, skipping comment lines because the ADR-1138
   NOLINT bracket names the keyword in prose.

2. A `static const double` used to initialise a `static const` aggregate
   (C2099, then five cascading C2440s). In C such a declaration is a
   const-qualified *object*, not a constant expression, so it may not appear
   in a static initialiser. This one cannot be grepped line-locally and, more
   importantly, no locally available compiler diagnoses it *at all* — gcc and
   clang both accept it silently under `-std=c23 -pedantic-errors`, and clang
   stays silent under `-Weverything` too. `find-nonconst-static-init.py`
   collects the file-scope `static const <scalar>` names and reports any that
   appear inside a braced `static` initialiser, with string and character
   literals masked so a name occurring in a message does not count.

Verified against the file that broke: planted as an untracked source, the
stage reports both classes and fails; with the merged fix in place it passes.
The scanner finds zero hits across all 438 `.c` files in the tree, and the
nullptr grep finds nothing outside ADR-1138 comment text — so neither check
starts life with a backlog to suppress.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
lusoris pushed a commit that referenced this pull request Sep 7, 2026
…GPU filters

#1340 landed the percentile pooling C API (ADR-1188). This branch had
implemented the same feature independently under ADR-1181 -- byte-identical
enum names, order and semantics -- so its core half is now dead code that can
never merge: it collides with master on libvmaf.h, libvmaf.c, output.cpp,
predict.c and core/test/meson.build.

Reduced to the consumer surfaces #1340 did not ship, which are genuinely
additive:

  - pkg/libvmaf: a typed `PoolMethod` with a String() mapping to the
    on-the-wire names, so Go callers pick a pooling method without touching
    cgo enums.
  - ffmpeg-patches 0005 / 0006 / 0013: the libvmaf_sycl, libvmaf_vulkan and
    libvmaf_metal filters accept the four new `pool` values. The CPU
    libvmaf filter already did.
  - docs/usage/cli.md: documents them on the CLI.
  - python/test/command_line_test.py: end-to-end coverage through the CLI.

Dropped: the duplicate core implementation, core/src/pooling_percentile.h,
core/test/test_pooling_percentile.c, ADR-1181 and its index rows, and the
docs/api, docs/state and rebase-notes edits master already carries from #1340.
ADR-1181 is not superseded because it never landed -- ADR-1188 is the record.

The changelog fragment now describes only the consumer wiring; master already
carries `percentile-pooling-methods.md` for the API itself.

Verified against master's implementation rather than its own: `go build` and
`go vet ./pkg/libvmaf/` are clean and `gofmt -l` is empty, which also confirms
the enum values agree. `go test ./pkg/libvmaf/` cannot link here for an
unrelated reason -- the workstation has no DNN-enabled libvmaf installed, so
cgo fails on vmaf_dnn_session_run; plain master fails identically, and CI's Go
lane builds the library first.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
lusoris added a commit that referenced this pull request Sep 7, 2026
…one (#1342)

* fix(gpu): collapse the four divergent ADM angle_flag predicates onto one

`decouple()`'s 1-degree `angle_flag` test had four different spellings in
the tree. The CPU, AVX2 and AVX-512 paths narrow the exact int64 operands
to `float` and then compare in `double` — the form the Netflix golden gate
freezes. CUDA and HIP scale 0 compared the *exact* int64 products in
`double` (a strictly more accurate angle test, and therefore a different
one); SYCL did the whole comparison in `float`; Metal narrowed the exact
products to `float`. CUDA/HIP scales 1-3 already used the golden form, so
`s0` and `s123` disagreed inside a single backend.

`angle_flag` selects the enhancement-gain-limited branch of `decouple()`,
so a flipped flag moves the adm scores. Instrumenting the scalar CPU
decouple shows the divergence is reachable on the Netflix golden pair
itself: of 1 540 608 scale-0 pixels, the legacy SYCL form flips 2 and the
legacy Metal form flips 3; on a full-contrast 1080p noise clip the counts
are 11 / 11 / 18 (CUDA/HIP, SYCL, Metal).

The predicate now lives once, in `core/src/feature/adm_angle_flag.h`:

  * `adm_angle_flag_fp64()` — the golden expression, verbatim. The scalar
    CPU path, CUDA and HIP call it at both scales.
  * `adm_angle_flag_i64()` — a bit-identical evaluation in 64-bit integer
    arithmetic that uses no floating point of any width. SYCL calls it
    (Intel Arc A-series and most iGPUs expose no fp64, and one fp64
    instruction anywhere in that translation unit makes the runtime reject
    the whole SPIR-V module); `metal/integer_adm.metal` mirrors it in MSL
    (MSL has no `double` type).

Verified: `integer_adm.c` compiles to byte-identical machine code against
origin/master, so the CPU/golden lane cannot have moved. On the golden
576x324 pair with `adm_enhn_gain_limit=1.2`, the SYCL-vs-CPU adm gap drops
from 1.175e-05 to 8.220e-07 on the Arc A380. `adm_angle_flag_i64()` was
fuzzed against the golden expression over 200 M scale-0 quadruples and
104 M boundary-walk triples spanning all 62 magnitude classes, under gcc
and clang at -O0/-O2/-O3: zero mismatches.

Closes T-UPSTREAM-930-ADM-ANGLE-FLAG-PREDICATE-DIVERGENCE-2026-09-03.
Refs Netflix/vmaf#930.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs(state): move T-UPSTREAM-930 angle_flag divergence to Recently closed

Fixed by PR #1342 (a3155b2, ADR-1194). ADR-0165 requires the ledger
row to move in the same PR as the fix.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs(state): drop the duplicate rows a keep-both rebase created

Each dropped row restates one origin/master already carries; master is the
authoritative record. Verified with scripts/ci/check-state-md-rows.sh.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(adm): split the angle_flag round53 step out and finish the state.md move

Two loose ends on top of the ADR-1194 change:

1. `adm_angle_flag_i64()` tripped `readability-function-size` (71 lines
   against the 60-line threshold) once clang-tidy was pointed at the new
   header with `-header-filter='adm_angle_flag\.h'` — the default filter is
   anchored at `^core/` and so misses it when the build dir is a sibling.
   ADR-0141 wants the file clean, not suppressed, so the right-hand-side
   rounding is now `adm_angle_flag_round53_v()`. Pure extraction: no
   expression changes, and `core/test/test_adm_angle_flag.c` still passes
   7/7. `metal/integer_adm.metal` gets the same split so the manual MSL
   mirror stays line-for-line (the AGENTS.md lockstep invariant).

2. `docs/state.md` still carried T-UPSTREAM-930 under **Open bugs** with a
   comment claiming it had moved: an earlier duplicate-row cleanup dropped
   the Recently-closed copy instead of the stale open one. The open row is
   gone and the closed row is back at the head of Recently closed, citing
   PR #1342 and ADR-1194 (the previous text cited a commit sha that the
   rebase invalidated).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(adm): resolve the conflict markers left in the SYCL twin and AGENTS.md

Two hunks in this branch had been committed with their conflict markers still
in place — an `#include` block in `core/src/feature/sycl/integer_adm_sycl.cpp`
that needed both `adm_csf_fixed_point.h` and `adm_angle_flag.h`, and an
appended section in `core/src/feature/AGENTS.md` where both sides were wanted.
Both are keep-both resolutions: includes sorted, sections concatenated.

Rebasing onto master after #1340 landed added a third, in
`core/test/meson.build`: master's new `test_pool_percentile` executable and
this branch's `test_adm_angle_flag` claim the same position. Also keep-both —
the two are unrelated tests, and master registers `test_pool_percentile` in the
separate `test()` block further down, so only the `executable()` definitions
collide. The diff against master for that file is now purely additive.

Verified after the rebase: `meson test --suite=fast` is 119/119, with
`test_adm_angle_flag` and `test_pool_percentile` both OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(test): make M_PI reach test_adm_angle_flag.c on MinGW64

The Windows MinGW64 lane failed to compile the new test:

    ../test/test_adm_angle_flag.c:108:30: error: 'M_PI' undeclared

`M_PI` is not ISO C -- it is a POSIX/X-Open extension. glibc exposes it only
under `__USE_MISC` / `__USE_XOPEN`, which `-std=c23` switches off by defining
`__STRICT_ANSI__`; the Linux lanes only see it because meson adds
`-D_GNU_SOURCE`, which turns those back on. MinGW64 does not honour
`_GNU_SOURCE`, so on that lane the identifier simply does not exist.

Every other file in the tree that wants `M_PI` already handles this the same
way -- `_USE_MATH_DEFINES` before `<math.h>` for MSVC, then an `#ifndef M_PI`
fallback for MinGW (core/src/feature/adm_csf_tools.h, adm_tools.h,
adm_tools.c). This test was the one place that just included `<math.h>` and
hoped. It now follows the same two-step, with the literal copied from
adm_tools.h so `golden_cos_1deg_sq()` derives cos(1deg) from exactly the
constant `integer_adm.c` uses. (The two spellings in the tree,
`3.14159265358979323846` and `3.14159265358979323846264338327`, are the same
IEEE double, so this is consistency rather than a numeric change.)

Reproduced locally rather than inferred: compiling the pre-fix file with
`gcc -std=c23` and no `-D_GNU_SOURCE` gives the identical error on the
identical line, and the fixed file compiles clean under the same flags.
`meson test --suite=fast` is 119/119 with test_adm_angle_flag OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(metal): rename the `half` local — it is a built-in MSL type

The macOS Clang+Metal lane could not compile integer_adm.metal:

    integer_adm.metal:208:17: error: cannot combine with previous 'type-name'
                                     declaration specifier
    integer_adm.metal:209:20: error: expected '(' for function-style cast

`half` is Metal Shading Language's built-in 16-bit float type, so
`const ulong half = ...` is a redeclaration, and every later use parses as a
type name rather than a variable -- which is why one bad declaration produced
four errors across two lines. The name is fine in the C/CUDA/HIP twins this
rounding helper was ported from, which is how it got here; master has no
occurrence of it in any .metal file.

Renamed to `halfway`, with a comment naming the trap so the next port does not
repeat it. Swept the other 16 shaders for the same shape (a reserved MSL type
name used as a variable) and found none.

Also collapses a double blank line in docs/state.md that a keep-both
conflict resolution on this branch left behind -- markdownlint MD012, which
failed the required Pre-Commit check. `markdownlint-cli2 docs/state.md` now
reports 0 issues.

`meson test --suite=fast` remains 119/119.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(test): cite the reserved-identifier NOLINT the M_PI guard needs

The Tidy Ratchet went 0 -> 1 on test_adm_angle_flag.c after the MinGW fix:

    declaration uses identifier '_USE_MATH_DEFINES', which is a reserved
    identifier [bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp]

The leading underscore is not ours to choose -- `_USE_MATH_DEFINES` is the name
MSVC's and MinGW's <math.h> look for, so renaming it defeats the entire point
of defining it. That is a load-bearing platform contract, which is exactly the
case ADR-1142 rule 12 reserves NOLINT for, and core/src/libvmaf.c already
carries the same suppression for glibc's `__libc_single_threaded`.

The comment is deliberately split: `NOLINTNEXTLINE` anchors to the line it
appears on, so putting it at the top of a multi-line block points it at the
block's second line and suppresses nothing. Prose first, then a one-line
directive immediately above the `#define` -- the shape libvmaf.c uses.
Verified with clang-tidy 22.1.8 (the version CI runs): 1 warning before, 0
after.

M_PI still resolves under `gcc -std=c23` with no -D_GNU_SOURCE, and
test_adm_angle_flag still passes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Lusoris <lusoris@pm.me>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
lusoris pushed a commit that referenced this pull request Sep 7, 2026
…GPU filters

#1340 landed the percentile pooling C API (ADR-1188). This branch had
implemented the same feature independently under ADR-1181 -- byte-identical
enum names, order and semantics -- so its core half is now dead code that can
never merge: it collides with master on libvmaf.h, libvmaf.c, output.cpp,
predict.c and core/test/meson.build.

Reduced to the consumer surfaces #1340 did not ship, which are genuinely
additive:

  - pkg/libvmaf: a typed `PoolMethod` with a String() mapping to the
    on-the-wire names, so Go callers pick a pooling method without touching
    cgo enums.
  - ffmpeg-patches 0005 / 0006 / 0013: the libvmaf_sycl, libvmaf_vulkan and
    libvmaf_metal filters accept the four new `pool` values. The CPU
    libvmaf filter already did.
  - docs/usage/cli.md: documents them on the CLI.
  - python/test/command_line_test.py: end-to-end coverage through the CLI.

Dropped: the duplicate core implementation, core/src/pooling_percentile.h,
core/test/test_pooling_percentile.c, ADR-1181 and its index rows, and the
docs/api, docs/state and rebase-notes edits master already carries from #1340.
ADR-1181 is not superseded because it never landed -- ADR-1188 is the record.

The changelog fragment now describes only the consumer wiring; master already
carries `percentile-pooling-methods.md` for the API itself.

Verified against master's implementation rather than its own: `go build` and
`go vet ./pkg/libvmaf/` are clean and `gofmt -l` is empty, which also confirms
the enum values agree. `go test ./pkg/libvmaf/` cannot link here for an
unrelated reason -- the workstation has no DNN-enabled libvmaf installed, so
cgo fails on vmaf_dnn_session_run; plain master fails identically, and CI's Go
lane builds the library first.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
lusoris added a commit that referenced this pull request Sep 7, 2026
…rc20) (ADR-1181) (#1392)

* feat(pooling): wire percentile pooling into Go, the CLI docs and the GPU filters

#1340 landed the percentile pooling C API (ADR-1188). This branch had
implemented the same feature independently under ADR-1181 -- byte-identical
enum names, order and semantics -- so its core half is now dead code that can
never merge: it collides with master on libvmaf.h, libvmaf.c, output.cpp,
predict.c and core/test/meson.build.

Reduced to the consumer surfaces #1340 did not ship, which are genuinely
additive:

  - pkg/libvmaf: a typed `PoolMethod` with a String() mapping to the
    on-the-wire names, so Go callers pick a pooling method without touching
    cgo enums.
  - ffmpeg-patches 0005 / 0006 / 0013: the libvmaf_sycl, libvmaf_vulkan and
    libvmaf_metal filters accept the four new `pool` values. The CPU
    libvmaf filter already did.
  - docs/usage/cli.md: documents them on the CLI.
  - python/test/command_line_test.py: end-to-end coverage through the CLI.

Dropped: the duplicate core implementation, core/src/pooling_percentile.h,
core/test/test_pooling_percentile.c, ADR-1181 and its index rows, and the
docs/api, docs/state and rebase-notes edits master already carries from #1340.
ADR-1181 is not superseded because it never landed -- ADR-1188 is the record.

The changelog fragment now describes only the consumer wiring; master already
carries `percentile-pooling-methods.md` for the API itself.

Verified against master's implementation rather than its own: `go build` and
`go vet ./pkg/libvmaf/` are clean and `gofmt -l` is empty, which also confirms
the enum values agree. `go test ./pkg/libvmaf/` cannot link here for an
unrelated reason -- the workstation has no DNN-enabled libvmaf installed, so
cgo fails on vmaf_dnn_session_run; plain master fails identically, and CI's Go
lane builds the library first.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* test(core): restore command_line_test.py assertion strings to match ADR-1188 output

ADR-1188 intentionally keeps pool_report_order limited to the historical four
methods (min, max, mean, harmonic_mean) so the default XML output does not
include median/perc5/perc10/perc20. Revert the speculative additions in
command_line_test.py so vmafexec tests pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Lusoris <lusoris@pm.me>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant