diff --git a/HANDOFF.md b/HANDOFF.md
index b66a13b..61ed72d 100644
--- a/HANDOFF.md
+++ b/HANDOFF.md
@@ -12,9 +12,9 @@
counts only audio whose write reached the pipe, and starts its clock at session open. Measured
across three recordings: +2.1% long → 226 ms short → 137 ms short → **lengths now match (34 ms in
13 s)**.
-- **What is left is one number.** `5ae5280` gives the mic delay a 0..500 slider (off/175/350) and logs
- the applied value. Measured on the last clip: `-185 ms`, i.e. sound still 185 ms EARLY, so the value
- to try is **350–360 ms**. `Audio.MicSyncOffsetMs` is stored per profile.
+- **Sync answer (measured, settled).** Mic delay ships at **0** and should stay off; with it off the audio
+ path runs ~100 ms behind, so the **global** LIP-SYNC offset is **−130 ms**. Both offsets default to 0 —
+ a correction belongs to one camera, never to the product (`AudioSyncSettingsTests` pins it).
- **675 tests green, 0 warnings.** `scripts/avsync.py` measures any recording in seconds.
## Next action
diff --git a/Services/LayoutStore.Settings.cs b/Services/LayoutStore.Settings.cs
index a2c6817..622239c 100644
--- a/Services/LayoutStore.Settings.cs
+++ b/Services/LayoutStore.Settings.cs
@@ -117,9 +117,12 @@ public partial class LayoutStore : IDisposable
return int.TryParse(value, out var ms) ? Math.Clamp(ms, -500, 500) : 0;
}
- /// The shipped default for the microphone-only delay. The creator's measurement for a
- /// C920 pipeline (asked for +150..+200 ms); a camera with less latency wants less.
- public const int DefaultMicSyncOffsetMs = 175;
+ ///
+ /// The shipped default for the microphone-only delay: **zero**. A sync offset is a correction for one
+ /// camera on one desk, never a product default — shipping a number would shift every creator's audio
+ /// to fix one machine's camera, and would hide whether the fault is theirs or ours.
+ ///
+ public const int DefaultMicSyncOffsetMs = 0;
/// Microphone-only sync delay (0..500 ms), so the voice can wait for the camera's picture
/// without moving desktop or alert audio. Absent means the shipped default.
diff --git a/docs/ai/output.md b/docs/ai/output.md
index 04852cc..468ef5a 100644
--- a/docs/ai/output.md
+++ b/docs/ai/output.md
@@ -294,9 +294,9 @@ devices, no timers).
samples reach the pipe, so the voice runs ahead of the lips. `AudioSyncDelay` now takes a channel
count, and `FillAndMix` runs a second line (**mono**) over `micChunk` *before* the mix — loopback and
alert audio are not tied to a camera frame and must not move with it. Seam: `Func micSyncOffsetMs`
- (VM `MicSyncOffsetMs`, **0..500 ms**, persisted `Audio.MicSyncOffsetMs`, shipped default **175** —
- the creator's own C920 measurement, not a constant: the right number belongs to the machine).
- A **slider 0..500** plus `off / 175 / 350` presets sit in the LIP-SYNC bay under the whole-mix
+ (VM `MicSyncOffsetMs`, **0..500 ms**, persisted `Audio.MicSyncOffsetMs`, shipped default **0 / off** — a sync offset corrects one camera on
+ one desk, so it is opt-in, never a product default).
+ A **slider 0..500** (default 0) plus `off / 175 / 350` presets sit in the LIP-SYNC bay under the whole-mix
slider — presets alone could not express the value a slow camera actually needs. `Audio live: started`
logs the applied number, because the value lives in the layout DB and a preset click is invisible in a
recording. Both lines are
diff --git a/ytLive.Tests/AudioSyncSettingsTests.cs b/ytLive.Tests/AudioSyncSettingsTests.cs
new file mode 100644
index 0000000..6b32936
--- /dev/null
+++ b/ytLive.Tests/AudioSyncSettingsTests.cs
@@ -0,0 +1,47 @@
+using System;
+using System.IO;
+using Microsoft.Data.Sqlite;
+using Xunit;
+using ytLive.Services;
+
+namespace ytLive.Tests;
+
+///
+/// Sync offsets ship at zero. A correction belongs to one camera on one desk, so a fresh profile must
+/// not arrive with audio already shifted — a non-zero default would move every creator's voice to fix
+/// one machine and would hide whether the fault is theirs or ours.
+///
+public sealed class AudioSyncSettingsTests : IDisposable
+{
+ private readonly string _db = Path.Combine(Path.GetTempPath(), $"ytLlive-syncsettings-{Guid.NewGuid():N}.db");
+
+ public void Dispose()
+ {
+ SqliteConnection.ClearAllPools();
+ try { File.Delete(_db); } catch (IOException) { /* best effort */ }
+ }
+
+ [Fact]
+ public void Both_offsets_start_at_zero_and_only_then_take_a_value()
+ {
+ using (var store = new LayoutStore(_db))
+ {
+ Assert.Equal(0, store.LoadMicSyncOffsetMs()); // opt-in, not pre-applied
+ Assert.Equal(0, store.LoadAudioSyncOffsetMs());
+
+ // A mic delay cannot be negative — advancing is the global offset's job.
+ store.SaveMicSyncOffsetMs(-120);
+ Assert.Equal(0, store.LoadMicSyncOffsetMs());
+
+ store.SaveMicSyncOffsetMs(600); // beyond the slider's range
+ Assert.Equal(500, store.LoadMicSyncOffsetMs());
+
+ store.SaveMicSyncOffsetMs(240);
+ store.SaveAudioSyncOffsetMs(-130);
+ }
+
+ using var reopened = new LayoutStore(_db);
+ Assert.Equal(240, reopened.LoadMicSyncOffsetMs());
+ Assert.Equal(-130, reopened.LoadAudioSyncOffsetMs());
+ }
+}