Compare commits
5
Commits
s2-host-fault
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
59da73c013 | ||
|
|
0e395e5c0c | ||
|
|
774922c6a9 | ||
|
|
63c246d976 | ||
|
|
52d842160d |
@@ -11,3 +11,9 @@
|
|||||||
# the .iss script and .ico are the tracked sources.
|
# the .iss script and .ico are the tracked sources.
|
||||||
/packaging/windows/peerspeak.exe
|
/packaging/windows/peerspeak.exe
|
||||||
/packaging/windows/output/
|
/packaging/windows/output/
|
||||||
|
|
||||||
|
# Nix: the symlink `nix build` drops, and direnv's local cache. flake.nix and
|
||||||
|
# flake.lock ARE tracked — the lock is what pins the toolchain.
|
||||||
|
/result
|
||||||
|
/result-*
|
||||||
|
/.direnv/
|
||||||
|
|||||||
@@ -137,6 +137,35 @@ covers internals). When you ship a feature, add it here.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Known bugs
|
||||||
|
|
||||||
|
Defects found by code review, not yet fixed.
|
||||||
|
|
||||||
|
1. **Adaptive playout delay never shrinks back in real conversation**
|
||||||
|
(`src/core/jitter.rs`) — `target_delay` grows +1 per disruption up to
|
||||||
|
`MAX_DELAY_FRAMES` (12 frames = 240 ms) but only shrinks after
|
||||||
|
`CLEAN_RUN_TO_SHRINK` = 250 consecutive cleanly-played frames, i.e. **5 s of
|
||||||
|
unbroken audio**. `clean_run` is reset in five places; two of them fire on
|
||||||
|
every natural pause in speech: the benign-underrun branch (`jitter.rs:201`,
|
||||||
|
talker went quiet) and the subsequent re-prime (`jitter.rs:181`). Because the
|
||||||
|
sender skips transmitting entirely while the noise gate is closed
|
||||||
|
(`src/core/mod.rs:2041`), a pause between sentences *always* underruns the
|
||||||
|
receiver and zeroes the clean run — twice.
|
||||||
|
|
||||||
|
Net effect: the controller is a one-way ratchet. A single burst of jitter
|
||||||
|
early in a call pins up to 240 ms of extra playout latency for the rest of
|
||||||
|
the session, because no conversational speaker talks for 5 continuous
|
||||||
|
seconds without the gate closing. The AIMD "decrease" half is effectively
|
||||||
|
unreachable under the workload the app is built for.
|
||||||
|
|
||||||
|
Likely fix: let `clean_run` survive a benign idle→re-prime transition rather
|
||||||
|
than resetting it. Silence is not evidence the link is bad, so it should not
|
||||||
|
count against the clean run. Distinguish "talker stopped" (benign) from
|
||||||
|
"playout broke" (real) at `jitter.rs:195-202`.
|
||||||
|
|
||||||
|
Found 2026-07-31 by code review. Not yet reproduced in a live call — pairs
|
||||||
|
with field-test debt item 5 below.
|
||||||
|
|
||||||
## Known field-test debt (the 🧪 rows above, collected)
|
## Known field-test debt (the 🧪 rows above, collected)
|
||||||
|
|
||||||
Re-run on a real desktop ↔ dopedart call before calling these done:
|
Re-run on a real desktop ↔ dopedart call before calling these done:
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
# Implementation plan: whole-desktop screen-share audio without self-echo
|
# Implementation plan: whole-desktop screen-share audio without self-echo
|
||||||
|
|
||||||
**Status:** 🟢 **v4 — three review rounds applied. Approved to start Phase 0a.**
|
**Status:** 🟢 **v4 — three review rounds applied.** *Progress as of 2026-08-07:* phases 0a, 0b,
|
||||||
**Date:** 2026-07-21
|
0c step 1, 1, 2, 3, 3r, 4 and 5 are merged, and the **phase-5 major gate PASSED on 2026-07-26**
|
||||||
|
(§1). The current front is **0c step 2**, sliced S1–S5: S1 and S2 merged, **S3a built but
|
||||||
|
unmerged** (pixelpass `s3a-module-ledger`), S3b/S4/S5 not started. **0d and phases 6–9 are not
|
||||||
|
built** — nothing merged to date mutates the graph, so the feature has no user-visible behaviour
|
||||||
|
yet. Phase 6 is the first mutation.
|
||||||
|
**Date:** 2026-07-21 (v4); status line refreshed 2026-08-07
|
||||||
**Design of record:** [`screenshare-audio-exclusion-plan.md`](screenshare-audio-exclusion-plan.md) v3.4 (`8768cd2`), converged round 7.
|
**Design of record:** [`screenshare-audio-exclusion-plan.md`](screenshare-audio-exclusion-plan.md) v3.4 (`8768cd2`), converged round 7.
|
||||||
**Scope:** *ordering, gates and acceptance criteria only.*
|
**Scope:** *ordering, gates and acceptance criteria only.*
|
||||||
|
|
||||||
@@ -100,13 +105,20 @@ if it differs, failing closed.
|
|||||||
1 (r8 carriers) ──────────────────────────────────────► 5 (re-run)
|
1 (r8 carriers) ──────────────────────────────────────► 5 (re-run)
|
||||||
```
|
```
|
||||||
|
|
||||||
⚠️ **Status 2026-07-25 (evening): 3r is BUILT AND MERGED; the re-run has not happened yet.**
|
✅ **Status 2026-07-26: the phase-5 gate PASSED on run 2 — all 13 §5.1 rows completed.**
|
||||||
The phase-5 gate failed on its first live run and put 3r into the DAG; 3r's own four-part
|
Record: [`screenshare-audio-exclusion-phase5-results.md`](screenshare-audio-exclusion-phase5-results.md)
|
||||||
gate now passes, including the live prop-recovery row on this host. Phase 5's machinery is
|
(audit build pixelpass `main` @ `91c4ded`, release profile). Phase 1 was the hard prerequisite
|
||||||
built and correct — it is the audit that found the defect, twice — so "5 (re-run)" is a
|
of the re-run for both carriers (plan §3) and was satisfied — rows 4 and 5 passed at the real
|
||||||
*re-run of the matrix*, not a rebuild. **Phase 6 still does not start** until a passing
|
tagging sites. **Phase 6 is no longer blocked by this gate.** What still blocks it is the rest
|
||||||
results file exists. **Phase 1 is a hard prerequisite of the re-run for both carriers**
|
of the DAG: `0b → 6` is satisfied and merged, but `0c → 0d → 6` is not — **0c step 2 remains
|
||||||
(plan §3).
|
open** (S1 and S2 merged; S3a built but UNMERGED on pixelpass `s3a-module-ledger`; S3b, S4 and
|
||||||
|
S5 not started), and **0d has not been built at all**.
|
||||||
|
|
||||||
|
⚠️ **Superseded, kept for the trail — status 2026-07-25 (evening): "3r is BUILT AND MERGED; the
|
||||||
|
re-run has not happened yet."** The phase-5 gate failed on its first live run and put 3r into
|
||||||
|
the DAG; 3r's own four-part gate then passed, including the live prop-recovery row on this host.
|
||||||
|
Phase 5's machinery was built and correct throughout — it is the audit that found the defect,
|
||||||
|
twice — so "5 (re-run)" was a *re-run of the matrix*, not a rebuild. Run 2 is that re-run.
|
||||||
|
|
||||||
⚠️ **A smoke run of the audit against the fixed observer immediately found a second defect
|
⚠️ **A smoke run of the audit against the fixed observer immediately found a second defect
|
||||||
(design v3.6 §6.8): a fail-closed `unresolved-ancestry` mark was being promoted to permanent
|
(design v3.6 §6.8): a fail-closed `unresolved-ancestry` mark was being promoted to permanent
|
||||||
|
|||||||
Generated
+48
@@ -0,0 +1,48 @@
|
|||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1785989512,
|
||||||
|
"narHash": "sha256-HFQhkQcl5D1hUNoen3SGHCSFCt2Bg6uP+HgbrnA3InQ=",
|
||||||
|
"owner": "nixos",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "445d861c6d31b4af0c79d8d4be2331f762a361d7",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nixos",
|
||||||
|
"ref": "nixos-26.05",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": "nixpkgs",
|
||||||
|
"rust-overlay": "rust-overlay"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rust-overlay": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1786076960,
|
||||||
|
"narHash": "sha256-jfR6OhwurCKn1tREyfOcK/Omxf1Q/DzDDFbnEr1mBLs=",
|
||||||
|
"owner": "oxalica",
|
||||||
|
"repo": "rust-overlay",
|
||||||
|
"rev": "57a23bfaf4f7017267294b161175db1e32eb1c85",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "oxalica",
|
||||||
|
"repo": "rust-overlay",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
||||||
@@ -0,0 +1,171 @@
|
|||||||
|
{
|
||||||
|
description = "PeerSpeak — decentralized P2P voice chat (Rust/iroh/PipeWire/Opus/iced)";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
# Pinned to the same channel the hosts run (nixos-config tracks
|
||||||
|
# nixos-26.05), so the libraries this shell links and dlopens are built
|
||||||
|
# against the same release as the PipeWire daemon and Vulkan ICD actually
|
||||||
|
# running on the machine. Floating to unstable here would reintroduce
|
||||||
|
# precisely the client/server version skew the pin exists to prevent.
|
||||||
|
nixpkgs.url = "github:nixos/nixpkgs/nixos-26.05";
|
||||||
|
|
||||||
|
# The Rust toolchain is pinned SEPARATELY from the system libraries, and
|
||||||
|
# deliberately so. nixpkgs 26.05 ships rustc 1.95.0, but this crate was
|
||||||
|
# developed and verified against 1.97.1 — close enough to build and pass
|
||||||
|
# every test, but not close enough for clippy, which flags a
|
||||||
|
# `collapsible_match` on 1.95 that 1.97 does not. Taking the compiler from
|
||||||
|
# here decouples "which Rust the project targets" from "which release the
|
||||||
|
# audio stack came from", so a nixpkgs bump can never silently move the
|
||||||
|
# compiler under the lint gate again.
|
||||||
|
#
|
||||||
|
# This is the reproducible alternative to rustup: same exact-version
|
||||||
|
# control, but the choice is recorded in flake.lock, so darp5 or a fresh
|
||||||
|
# clone resolves the identical toolchain instead of whatever rustup happens
|
||||||
|
# to fetch that day.
|
||||||
|
rust-overlay = {
|
||||||
|
url = "github:oxalica/rust-overlay";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs =
|
||||||
|
{ nixpkgs, rust-overlay, ... }:
|
||||||
|
let
|
||||||
|
system = "x86_64-linux";
|
||||||
|
pkgs = import nixpkgs {
|
||||||
|
inherit system;
|
||||||
|
overlays = [ rust-overlay.overlays.default ];
|
||||||
|
};
|
||||||
|
|
||||||
|
# Matches what CachyOS shipped (rust 1:1.97.1-1), which is the toolchain
|
||||||
|
# every green result in the handoff was produced with.
|
||||||
|
#
|
||||||
|
# `default` is the rustup "default" profile — rustc, cargo, rust-std,
|
||||||
|
# rustfmt and clippy — so those are NOT listed separately below.
|
||||||
|
#
|
||||||
|
# rust-src and the windows-gnu target exist for win-cross-build.sh, which
|
||||||
|
# needs `-Z build-std=std,panic_abort` for the self-contained .exe. That
|
||||||
|
# script still expects to run in the peerspeak-win distrobox for the
|
||||||
|
# mingw toolchain; carrying the target here just means the Rust half is
|
||||||
|
# already in place if it is ever driven from the host.
|
||||||
|
rustToolchain = pkgs.rust-bin.stable."1.97.1".default.override {
|
||||||
|
extensions = [ "rust-src" ];
|
||||||
|
targets = [ "x86_64-pc-windows-gnu" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
# Libraries that iced/winit/wgpu open with dlopen at RUNTIME rather than
|
||||||
|
# linking at build time. Because nothing links them, they never land in
|
||||||
|
# the binary's rpath — under `cargo run` the loader finds them only
|
||||||
|
# through LD_LIBRARY_PATH. Leaving them out builds fine and then panics
|
||||||
|
# at window creation, which is a genuinely confusing failure, so they are
|
||||||
|
# listed explicitly instead of discovered the hard way.
|
||||||
|
runtimeLibs = with pkgs; [
|
||||||
|
vulkan-loader # wgpu's Vulkan backend (iced's renderer)
|
||||||
|
libxkbcommon # winit keyboard handling
|
||||||
|
wayland # wayland-sys, dlopen'd on a Wayland session
|
||||||
|
libx11 # x11-dl, dlopen'd on the X11 fallback path
|
||||||
|
libxcursor
|
||||||
|
libxrandr
|
||||||
|
libxi
|
||||||
|
];
|
||||||
|
|
||||||
|
# Screen sharing spawns pixelpass as a CHILD PROCESS, and pixelpass in
|
||||||
|
# turn drives GStreamer as a subprocess. That makes these tools a
|
||||||
|
# dependency of peerspeak's own test suite, not just of pixelpass:
|
||||||
|
# `tests/screenshare_host_fault.rs` starts a real pixelpass host, which
|
||||||
|
# aborts at its preflight if gst-launch-1.0 is missing.
|
||||||
|
#
|
||||||
|
# Deliberately duplicated from pixelpass's flake rather than importing it
|
||||||
|
# as an input. The two projects are mutually optional by design — neither
|
||||||
|
# is a dependency of the other, and the coupling is a runtime subprocess
|
||||||
|
# contract. Making one flake consume the other would quietly reintroduce
|
||||||
|
# exactly the build-level dependency that rule exists to prevent.
|
||||||
|
screenshareTools = with pkgs; [
|
||||||
|
gst_all_1.gstreamer
|
||||||
|
gst_all_1.gst-plugins-base
|
||||||
|
gst_all_1.gst-plugins-good
|
||||||
|
gst_all_1.gst-plugins-bad
|
||||||
|
gst_all_1.gst-plugins-ugly
|
||||||
|
gst_all_1.gst-libav
|
||||||
|
pipewire # pipewiresrc (Wayland capture; ships in this pkg)
|
||||||
|
];
|
||||||
|
in
|
||||||
|
{
|
||||||
|
devShells.${system}.default = pkgs.mkShell {
|
||||||
|
nativeBuildInputs = [
|
||||||
|
rustToolchain
|
||||||
|
]
|
||||||
|
++ (with pkgs; [
|
||||||
|
# The supply-chain gates .gitea/workflows/ci.yml runs, so the same
|
||||||
|
# checks are reproducible locally before a push. These were `cargo
|
||||||
|
# install`ed on the CachyOS side, which does not carry over — those
|
||||||
|
# binaries link that distro's glibc and will not run here.
|
||||||
|
# cargo-deny reads deny.toml; cargo-audit reads .cargo/audit.toml.
|
||||||
|
cargo-audit
|
||||||
|
cargo-deny
|
||||||
|
# Debian packaging (`cargo deb --no-build`). Note the .deb itself
|
||||||
|
# should still be built inside a Debian/Ubuntu distrobox so the
|
||||||
|
# binary links that distro's glibc — see the packaging notes in
|
||||||
|
# Cargo.toml.
|
||||||
|
cargo-deb
|
||||||
|
|
||||||
|
pkg-config
|
||||||
|
|
||||||
|
# pipewire-sys and libspa-sys generate their bindings with bindgen,
|
||||||
|
# which needs a real libclang present at build time.
|
||||||
|
clang
|
||||||
|
|
||||||
|
# audiopus_sys prefers the system libopus via pkg-config but falls
|
||||||
|
# back to a vendored CMake build; cmake keeps that fallback working
|
||||||
|
# rather than failing obscurely inside a build script.
|
||||||
|
cmake
|
||||||
|
|
||||||
|
# build.rs shells out to `git rev-parse --short=8 HEAD` to stamp
|
||||||
|
# PEERSPEAK_GIT_SHORT into the binary (surfaced in Settings).
|
||||||
|
git
|
||||||
|
])
|
||||||
|
++ screenshareTools
|
||||||
|
++ [
|
||||||
|
pkgs.pulseaudio # `pactl`, used by pixelpass's audio routing
|
||||||
|
pkgs.mpv # the screen-share viewer
|
||||||
|
];
|
||||||
|
|
||||||
|
buildInputs =
|
||||||
|
with pkgs;
|
||||||
|
[
|
||||||
|
alsa-lib # alsa-sys, pulled in by rodio/cpal
|
||||||
|
libopus # audiopus_sys, linked dynamically
|
||||||
|
pipewire # pipewire-sys + libspa-sys: the Linux audio backend
|
||||||
|
]
|
||||||
|
++ runtimeLibs;
|
||||||
|
|
||||||
|
# bindgen finds libclang through this variable specifically — having
|
||||||
|
# clang on PATH is not sufficient.
|
||||||
|
LIBCLANG_PATH = "${pkgs.llvmPackages.libclang.lib}/lib";
|
||||||
|
|
||||||
|
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath runtimeLibs;
|
||||||
|
|
||||||
|
# NixOS keeps every GStreamer plugin in its own store path, so the
|
||||||
|
# gst-launch-1.0 that pixelpass spawns discovers them ONLY through this
|
||||||
|
# search path. Same reasoning as hosts/darp5 and hosts/cazen in
|
||||||
|
# nixos-config.
|
||||||
|
GST_PLUGIN_SYSTEM_PATH_1_0 =
|
||||||
|
pkgs.lib.makeSearchPathOutput "lib" "lib/gstreamer-1.0" screenshareTools;
|
||||||
|
|
||||||
|
# Only greet an interactive shell. shellHook also runs under
|
||||||
|
# `nix develop --command …`, where printing this would interleave the
|
||||||
|
# banner with the command's own output.
|
||||||
|
shellHook = ''
|
||||||
|
if [ -t 1 ]; then
|
||||||
|
echo "peerspeak — rustc $(rustc --version | cut -d' ' -f2) / cargo $(cargo --version | cut -d' ' -f2)"
|
||||||
|
echo " cargo build --release build"
|
||||||
|
echo " cargo test lib tests"
|
||||||
|
echo " cargo clippy --all-targets -- -D warnings lint"
|
||||||
|
echo
|
||||||
|
echo "Screen sharing spawns pixelpass as a child process — it must be"
|
||||||
|
echo "on PATH. Build it from ../pixelpass and add its target/release."
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user