Game audio bar is now ALWAYS visible (TASK 4 gating removed, 2026-08-15): the show/hide machinery kept hiding the desktop/game meter whenever no full-screen game with sound was up or no TRAX music played, which the creator read as the meter being lost. Deleted IGameAudioDetector/GameAudioDetector/GameAudioHysteresis (Services/), the VM's 250ms DispatcherTimer poll + OnGameAudioActiveChanged/OnGameAudioPollTick + IsGameAudioBarVisible + _gameAudioBarActive/_gameAudioTimer/_gameAudioDetector, and the two detector test files (GameAudioDetectorTests, GameAudioHysteresisTests); MainWindow.xaml drops the Visibility binding so the bar renders unconditionally. Desktop audio is just automatic WASAPI loopback now. Docs in the same commit: ai.md (audio section + Services inventory), Services/index.md, ViewModels/index.md, TASKS.md TASK 9 item 7 follow-up, HANDOFF.md shipped state + stale poll-timer landmine removed. Build 0 warnings, 189 tests passing (8 detector tests removed)
This commit is contained in:
@@ -1,76 +0,0 @@
|
||||
using Xunit;
|
||||
using ytLive.Services;
|
||||
|
||||
namespace ytLive.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// TASK 4 game audio bar: the default detector's provider wiring — it composes
|
||||
/// the full-screen monitor + loopback level into the hysteresis and raises
|
||||
/// IsGameAudioActiveChanged on transitions. The transition math itself lives in
|
||||
/// GameAudioHysteresisTests.
|
||||
/// </summary>
|
||||
public class GameAudioDetectorTests
|
||||
{
|
||||
[Fact]
|
||||
public void Poll_RaisesChanged_WhenGameAppearsAndLeaves()
|
||||
{
|
||||
var now = new DateTime(2026, 8, 13, 12, 0, 0);
|
||||
int? monitor = 0;
|
||||
var level = 0f;
|
||||
var detector = new GameAudioDetector(() => monitor, () => level, () => now);
|
||||
var changes = new List<bool>();
|
||||
detector.IsGameAudioActiveChanged += a => changes.Add(a);
|
||||
|
||||
level = 0.9f;
|
||||
detector.Poll();
|
||||
Assert.False(detector.IsGameAudioActive);
|
||||
|
||||
now = now.AddMilliseconds(600);
|
||||
detector.Poll();
|
||||
Assert.True(detector.IsGameAudioActive);
|
||||
Assert.Equal(new[] { true }, changes);
|
||||
|
||||
level = 0f;
|
||||
now = now.AddSeconds(2);
|
||||
detector.Poll();
|
||||
Assert.True(detector.IsGameAudioActive);
|
||||
Assert.Equal(new[] { true }, changes);
|
||||
|
||||
monitor = null;
|
||||
detector.Poll();
|
||||
Assert.True(detector.IsGameAudioActive);
|
||||
|
||||
now = now.AddSeconds(2);
|
||||
detector.Poll();
|
||||
Assert.False(detector.IsGameAudioActive);
|
||||
Assert.Equal(new[] { true, false }, changes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Poll_StaysInactive_WhenNoFullScreenMonitor()
|
||||
{
|
||||
var detector = new GameAudioDetector(() => null, () => 0.9f);
|
||||
var changes = 0;
|
||||
detector.IsGameAudioActiveChanged += _ => changes++;
|
||||
|
||||
for (var i = 0; i < 5; i++)
|
||||
detector.Poll();
|
||||
|
||||
Assert.False(detector.IsGameAudioActive);
|
||||
Assert.Equal(0, changes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Poll_StaysInactive_WhileLoopbackSilent()
|
||||
{
|
||||
var detector = new GameAudioDetector(() => 0, () => 0f);
|
||||
var changes = 0;
|
||||
detector.IsGameAudioActiveChanged += _ => changes++;
|
||||
|
||||
for (var i = 0; i < 5; i++)
|
||||
detector.Poll();
|
||||
|
||||
Assert.False(detector.IsGameAudioActive);
|
||||
Assert.Equal(0, changes);
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
using Xunit;
|
||||
using ytLive.Services;
|
||||
|
||||
namespace ytLive.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// TASK 4 game audio bar: the pure show/hide state machine. Show = a full-screen
|
||||
/// app holds sound for half a second; hide = the app leaves fullscreen for a
|
||||
/// second. Silence never hides an active bar — only the game leaving the preview
|
||||
/// does (per the creator's rule).
|
||||
/// </summary>
|
||||
public class GameAudioHysteresisTests
|
||||
{
|
||||
private static readonly DateTime T0 = new(2026, 8, 13, 12, 0, 0);
|
||||
|
||||
[Fact]
|
||||
public void StaysInactive_WhileSilent()
|
||||
{
|
||||
var h = new GameAudioHysteresis();
|
||||
|
||||
for (var i = 0; i < 30; i++)
|
||||
{
|
||||
h.Update(true, false, T0.AddSeconds(i));
|
||||
Assert.False(h.IsActive);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StaysInactive_UntilSoundHoldsHalfSecond()
|
||||
{
|
||||
var h = new GameAudioHysteresis();
|
||||
h.Update(true, true, T0);
|
||||
Assert.False(h.IsActive);
|
||||
h.Update(true, true, T0.AddMilliseconds(400));
|
||||
Assert.False(h.IsActive);
|
||||
|
||||
h.Update(true, true, T0.AddMilliseconds(600));
|
||||
Assert.True(h.IsActive);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NeverHides_WhileGameStillFullScreen_EvenWhenSilent()
|
||||
{
|
||||
var h = new GameAudioHysteresis();
|
||||
h.Update(true, true, T0);
|
||||
h.Update(true, true, T0.AddSeconds(1));
|
||||
Assert.True(h.IsActive);
|
||||
|
||||
for (var i = 2; i < 40; i++)
|
||||
{
|
||||
h.Update(true, false, T0.AddSeconds(i));
|
||||
Assert.True(h.IsActive);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Hides_AfterAppLeavesFullScreen()
|
||||
{
|
||||
var h = new GameAudioHysteresis();
|
||||
h.Update(true, true, T0);
|
||||
h.Update(true, true, T0.AddSeconds(1));
|
||||
Assert.True(h.IsActive);
|
||||
|
||||
h.Update(false, true, T0.AddSeconds(2));
|
||||
Assert.True(h.IsActive);
|
||||
|
||||
h.Update(false, true, T0.AddSeconds(4));
|
||||
Assert.False(h.IsActive);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StaysInactive_WhenNoFullScreen_EvenWithSound()
|
||||
{
|
||||
var h = new GameAudioHysteresis();
|
||||
|
||||
for (var i = 0; i < 30; i++)
|
||||
{
|
||||
h.Update(false, true, T0.AddSeconds(i));
|
||||
Assert.False(h.IsActive);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user