Skip to content

Diagram — Availability State Machine

Referenced from docs/technical/ARCHITECTURE.md and docs/availability-contract.md. This is the published heartbeat state machine (AvailabilityDetectorLogic.Evaluate, hysteresis applied) — the hard gate (AvailabilityGate.Evaluate) is memory-free and re-evaluates fresh signals on every job, so it has no state machine of its own; see the note at the bottom.

stateDiagram-v2
    [*] --> Available: WorkerAvailability.InitialAvailable

    Available --> Gaming: Steam RunningAppID != 0<br/>or allowlisted process seen
    Available --> Streaming: obs64/obs32 process seen
    Available --> ManualUnavailable: override file = "unavailable" (active)

    Gaming --> Streaming: OBS starts while gaming<br/>(streaming outranks gaming — blockLevel: all)
    Gaming --> GamingGrace: last gaming signal clears
    GamingGrace --> Gaming: gaming signal re-trips<br/>(timer resets to zero, not extended)
    GamingGrace --> Available: 90s continuous quiet elapsed

    Streaming --> StreamingGrace: OBS process exits
    StreamingGrace --> Streaming: streaming signal re-trips<br/>(timer resets to zero, not extended)
    StreamingGrace --> Available: 120s continuous quiet elapsed

    Available --> ManualAvailable: override file = "available" (active)
    Gaming --> ManualAvailable: override file = "available" (active, wins over live signal)
    Streaming --> ManualAvailable: override file = "available" (active, wins over live signal)

    ManualUnavailable --> Available: override expires or flips to "auto"
    ManualAvailable --> Available: override expires or flips to "auto"
    ManualUnavailable --> ManualAvailable: override flips "unavailable" → "available"
    ManualAvailable --> ManualUnavailable: override flips "available" → "unavailable"

    note right of GamingGrace
        blockLevel: heavy during this state
        (trivial + control still run)
        Grace = 90s per AvailabilityDetectorLogic.GamingGrace
    end note

    note right of StreamingGrace
        blockLevel: all during this state
        (only control still runs)
        Grace = 120s per AvailabilityDetectorLogic.StreamingGrace
    end note

    note left of ManualUnavailable
        blockLevel: all.
        Wins over every live signal —
        checked BEFORE Steam/OBS reads.
    end note

Important asymmetry: this diagram is the detector's, not the gate's

AvailabilityDetectorLogic (this diagram) exists only to smooth the published /health availability object so a consumer like Mission Control never sees available: true flicker on in the half-second between "alt-tabbed out" and "actually still playing." It is advisory.

AvailabilityGate.Evaluate — the function that actually decides whether a job is allowed to run — has no states and no memory at all. Every single job re-reads live signals (IAvailabilitySignalsProvider.GetSignals()) fresh and applies the same six ordered rules (see ARCHITECTURE.md § Gate rules) from scratch, every time. If the detector's published state is stale or wrong for a few seconds — which the 90s/120s grace windows above guarantee it sometimes will be — the gate is never fooled by that staleness, because it never looks at the detector's state at all.