test(phase-lock): a closed-loop simulation harness — both on-glass failures become CI regressions
ci / web (push) Successful in 1m7s
ci / docs-site (push) Successful in 1m14s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 23s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 27s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Successful in 12s
ci / rust-arm64 (push) Successful in 2m48s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 11s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 10s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 8s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 22s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 25s
deb / build-publish-client-arm64 (push) Successful in 2m48s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 3m1s
android / android (push) Successful in 5m31s
deb / build-publish-host (push) Successful in 6m5s
docker / builders-arm64cross (push) Successful in 18s
apple / swift (push) Successful in 4m41s
docker / deploy-docs (push) Successful in 46s
ci / rust (push) Successful in 6m45s
deb / build-publish (push) Successful in 5m9s
arch / build-publish (push) Successful in 9m4s
flatpak / build-publish (push) Successful in 6m54s
windows-host / package (push) Successful in 11m3s
windows-host / winget-source (push) Skipped
windows-host / canary-manifest (push) Successful in 25s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 1m10s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 2m35s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 2m53s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 19m13s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 18m27s
release / apple (push) Successful in 25m17s
apple / screenshots (push) Successful in 21m0s
ci / web (push) Successful in 1m7s
ci / docs-site (push) Successful in 1m14s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 23s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 27s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Successful in 12s
ci / rust-arm64 (push) Successful in 2m48s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 11s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 10s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 8s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 22s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 25s
deb / build-publish-client-arm64 (push) Successful in 2m48s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 3m1s
android / android (push) Successful in 5m31s
deb / build-publish-host (push) Successful in 6m5s
docker / builders-arm64cross (push) Successful in 18s
apple / swift (push) Successful in 4m41s
docker / deploy-docs (push) Successful in 46s
ci / rust (push) Successful in 6m45s
deb / build-publish (push) Successful in 5m9s
arch / build-publish (push) Successful in 9m4s
flatpak / build-publish (push) Successful in 6m54s
windows-host / package (push) Successful in 11m3s
windows-host / winget-source (push) Skipped
windows-host / canary-manifest (push) Successful in 25s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 1m10s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 2m35s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 2m53s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 19m13s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 18m27s
release / apple (push) Successful in 25m17s
apple / screenshots (push) Successful in 21m0s
The controller iterated twice on the user's gaming PC before this existed; never again. punktfunk_core::phase::circular_latch is extracted from the Android presenter (shared: iOS reports next, and the harness generates its synthetic reports through the IDENTICAL statistic the clients ship), with unit tests including the wrap-straddling cluster an arithmetic mean gets maximally wrong. The stream.rs harness models the plant — latch = (base − hold + noise) mod P, deterministic LCG noise — and drives PhaseController::adjust through five regimes: - tight jitter locks into the deadband within ~5 adjusts and stops moving; - a wrap-side start takes the SHORTEST way (travel < P/2 asserted); - an incoherent phase never steps (zero hold, ever); - a v1 pinned-median report trips the travel budget and DECAYS TO ZERO — the 2026-07-31 orbit and the parked-hold e2e tax, both as asserts; - a post-decay regime tightening re-locks. Gates: host bin suite 344 passed / 1 failed = the documented environmental qemu UDP-loopback test (clean main fails identically in this container); core phase tests 4/4; clippy --all-targets -D warnings clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -138,29 +138,6 @@ impl PresentMeter {
|
||||
}
|
||||
}
|
||||
|
||||
/// Circular (vector-mean) statistics of latch samples against the panel period: the mean latch
|
||||
/// mod the period (ns) and the coherence (‰). The mean is what a phase controller can actually
|
||||
/// steer under jitter — a median of a period-spanning distribution is immovable (the v1 lesson);
|
||||
/// the coherence says whether ANY phase exists to steer (0 = uniformly smeared, 1000 = locked).
|
||||
/// `None` under 8 samples — too little evidence to report a phase at all.
|
||||
fn circular_latch(samples_us: &[u64], period_ns: i64) -> Option<(u64, u16)> {
|
||||
if samples_us.len() < 8 || period_ns <= 0 {
|
||||
return None;
|
||||
}
|
||||
let period_us = period_ns as f64 / 1000.0;
|
||||
let (mut x, mut y) = (0.0f64, 0.0f64);
|
||||
for &s in samples_us {
|
||||
let theta = (s as f64 % period_us) / period_us * std::f64::consts::TAU;
|
||||
x += theta.cos();
|
||||
y += theta.sin();
|
||||
}
|
||||
let n = samples_us.len() as f64;
|
||||
let r = (x * x + y * y).sqrt() / n;
|
||||
let mean_theta = y.atan2(x).rem_euclid(std::f64::consts::TAU);
|
||||
let mean_ns = (mean_theta / std::f64::consts::TAU * period_ns as f64) as u64;
|
||||
Some((mean_ns, (r * 1000.0) as u16))
|
||||
}
|
||||
|
||||
/// p50/max of an unsorted µs sample vec, in ms. (0, 0) when empty.
|
||||
fn p50_max_ms(mut v: Vec<u64>) -> (f64, f64) {
|
||||
if v.is_empty() {
|
||||
@@ -380,8 +357,9 @@ impl Presenter {
|
||||
return None; // idle stream — nothing worth a line
|
||||
}
|
||||
let (pace_p50, pace_max) = p50_max_ms(std::mem::take(&mut self.pace_us));
|
||||
let circ =
|
||||
clock.and_then(|c| circular_latch(&latch, c.panel_period_ns().max(c.period_ns())));
|
||||
let circ = clock.and_then(|c| {
|
||||
punktfunk_core::phase::circular_latch(&latch, c.panel_period_ns().max(c.period_ns()))
|
||||
});
|
||||
let (latch_p50, latch_max) = p50_max_ms(latch);
|
||||
let period_ms = clock.map(|c| c.period_ns() as f64 / 1e6).unwrap_or(0.0);
|
||||
let panel_ms = clock
|
||||
|
||||
Reference in New Issue
Block a user