Zum Inhalt

apple/PhysarumApp/Tests/AmbientControllerAudioTests.swift

import XCTest
import PhysarumEngine
@testable import Physarum

/// T2b: the ATV controller holds phone-streamed audio factors for the render loop and relaxes to
/// neutral on stream drop (staleness) or disconnect — never frozen on the last beat.
final class AmbientControllerAudioTests: XCTestCase {
    private func make(staleAfter: Double = 0.5) -> AmbientController {
        let factory = PresetSlots.Factory(presets: [PresetCatalog.fallbackPreset], version: 1)
        let s = AppState(slotStore: SlotStore(backend: InMemorySlotBackend(), factory: factory))
        return AmbientController(appState: s, fadeDuration: 0.5, blackHold: 0.2, dwell: 2.0, audioStaleAfter: staleAfter)
    }
    private var frame: AudioFactorFrame {
        var f = AudioFactors(); f[.moveSpeed] = 1.5; return AudioFactorFrame(f)
    }

    func testReceiveHoldsFrame() {
        let c = make()
        c.receiveAudioFactors(frame)
        XCTAssertEqual(c.remoteAudio, frame)
    }

    func testStaleClearsToNeutral() {
        let c = make(staleAfter: 0.5)
        c.receiveAudioFactors(frame)
        c.tick(0.3); XCTAssertEqual(c.remoteAudio, frame)   // still fresh
        c.tick(0.3)                                          // total 0.6 > 0.5 → stale
        XCTAssertNil(c.remoteAudio)
    }

    func testFreshFrameResetsStaleTimer() {
        let c = make(staleAfter: 0.5)
        c.receiveAudioFactors(frame)
        c.tick(0.4)
        c.receiveAudioFactors(frame)   // reset
        c.tick(0.4)                    // 0.4 since the reset < 0.5
        XCTAssertEqual(c.remoteAudio, frame)
    }

    func testDisconnectClearsAudio() {
        let c = make()
        c.receiveAudioFactors(frame)
        c.resumeAutonomousCycle()      // disconnect path
        XCTAssertNil(c.remoteAudio)
    }
}