diff --git a/AGENTS.md b/AGENTS.md
index 344abaa..09c13f1 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -24,7 +24,7 @@ dotnet run --project .\Sdk\tests\SmokeTests\SmokeTests.csproj -- --filter-trait
dotnet pack .\Sdk\SVappsLAB.iRacingTelemetrySDK\SVappsLAB.iRacingTelemetrySDK.csproj
```
-Run `Category=live` smoke tests only with iRacing running in an active Windows session. Run `Category=manual` tests only when the local developer setup matches the test requirements.
+`Category=live` smoke tests need iRacing running in an active Windows session; without it they report as skipped (via `Assert.SkipUnless`), not failed. `Category=manual` tests are marked `Explicit = true`, so they never run by default - add `--explicit on` to run them, and only when the local developer setup matches the test requirements.
`global.json` pins `test.runner` to `Microsoft.Testing.Platform`, so `dotnet test` needs `--project`/`--solution` (a bare positional path is rejected) and does not understand VSTest-style `--filter`. Use `dotnet run --project`, not `dotnet test`, for `SmokeTests` - its `launchSettings.json` injects a default `--filter-class` that `dotnet test` combines with (rather than overrides with) any `--filter-trait` you add, silently yielding "zero tests ran". See `Sdk/tests/README.md` for details.
diff --git a/README.md b/README.md
index eb08d53..d089bf1 100644
--- a/README.md
+++ b/README.md
@@ -370,10 +370,10 @@ dotnet test --project .\Sdk\tests\UnitTests\UnitTests.csproj
# repeatable offline smoke tests using bundled IBT files
dotnet run --project .\Sdk\tests\SmokeTests\SmokeTests.csproj -- --filter-trait Category=ibt
-# live smoke tests, requires an active iRacing session
+# live smoke tests, requires an active iRacing session (reported as skipped otherwise)
dotnet run --project .\Sdk\tests\SmokeTests\SmokeTests.csproj -- --filter-trait Category=live
-# all test projects, including tests that may require live/manual setup
+# all test projects (live tests skip without iRacing; manual tests are explicit and do not run)
dotnet test --solution .\Sdk\SVappsLAB.iRacingTelemetrySDK.slnx
```
See [Sdk/tests/README.md](./Sdk/tests/README.md) for manual test commands and filtering notes.
diff --git a/Samples/DumpVariables_DumpSessionInfo/DumpVariables_DumpSessionInfo.csproj b/Samples/DumpVariables_DumpSessionInfo/DumpVariables_DumpSessionInfo.csproj
index c19ab02..2f3f14e 100644
--- a/Samples/DumpVariables_DumpSessionInfo/DumpVariables_DumpSessionInfo.csproj
+++ b/Samples/DumpVariables_DumpSessionInfo/DumpVariables_DumpSessionInfo.csproj
@@ -9,7 +9,7 @@
-
+
diff --git a/Samples/LocationAndWarnings/LocationAndWarnings.csproj b/Samples/LocationAndWarnings/LocationAndWarnings.csproj
index c19ab02..2f3f14e 100644
--- a/Samples/LocationAndWarnings/LocationAndWarnings.csproj
+++ b/Samples/LocationAndWarnings/LocationAndWarnings.csproj
@@ -9,7 +9,7 @@
-
+
diff --git a/Samples/MinimalExampleAsync/MinimalExampleAsync.csproj b/Samples/MinimalExampleAsync/MinimalExampleAsync.csproj
index c19ab02..2f3f14e 100644
--- a/Samples/MinimalExampleAsync/MinimalExampleAsync.csproj
+++ b/Samples/MinimalExampleAsync/MinimalExampleAsync.csproj
@@ -9,7 +9,7 @@
-
+
diff --git a/Samples/MinimalExampleSync/MinimalExampleSync.csproj b/Samples/MinimalExampleSync/MinimalExampleSync.csproj
index 16850ca..b8d826c 100644
--- a/Samples/MinimalExampleSync/MinimalExampleSync.csproj
+++ b/Samples/MinimalExampleSync/MinimalExampleSync.csproj
@@ -9,7 +9,7 @@
-
+
diff --git a/Samples/SimControl/SimControl.csproj b/Samples/SimControl/SimControl.csproj
index 7df6a9b..c029faf 100644
--- a/Samples/SimControl/SimControl.csproj
+++ b/Samples/SimControl/SimControl.csproj
@@ -9,6 +9,6 @@
-
+
diff --git a/Samples/SpeedRPMGear/SpeedRPMGear.csproj b/Samples/SpeedRPMGear/SpeedRPMGear.csproj
index 2a80474..cc43274 100644
--- a/Samples/SpeedRPMGear/SpeedRPMGear.csproj
+++ b/Samples/SpeedRPMGear/SpeedRPMGear.csproj
@@ -11,7 +11,7 @@
-
+
diff --git a/Sdk/SVappsLAB.iRacingTelemetrySDK/Models/WeekendInfo.cs b/Sdk/SVappsLAB.iRacingTelemetrySDK/Models/WeekendInfo.cs
index d128cf6..ec7fba8 100644
--- a/Sdk/SVappsLAB.iRacingTelemetrySDK/Models/WeekendInfo.cs
+++ b/Sdk/SVappsLAB.iRacingTelemetrySDK/Models/WeekendInfo.cs
@@ -14,8 +14,6 @@
* limitations under the License.
**/
-using System;
-
#nullable disable
namespace SVappsLAB.iRacingTelemetrySDK;
@@ -76,6 +74,7 @@ public class WeekendInfo
public int NumCarTypes { get; set; } // 1
public string AIRosterName { get; set; } // ""
public int HeatRacing { get; set; } // 0
+ public string AltAssetTag { get; set; } // "(blank), IMSA, NEC, NOTC, PMNA, INDY, NOABSTC, NOABS, ROOKIE, WSC2017, DTM"
public string BuildType { get; set; } // Release
public string BuildTarget { get; set; } // Members
public string BuildVersion { get; set; } // 2020.12.08.06
diff --git a/Sdk/SVappsLAB.iRacingTelemetrySDK/SVappsLAB.iRacingTelemetrySDK.csproj b/Sdk/SVappsLAB.iRacingTelemetrySDK/SVappsLAB.iRacingTelemetrySDK.csproj
index 1631603..914e80d 100644
--- a/Sdk/SVappsLAB.iRacingTelemetrySDK/SVappsLAB.iRacingTelemetrySDK.csproj
+++ b/Sdk/SVappsLAB.iRacingTelemetrySDK/SVappsLAB.iRacingTelemetrySDK.csproj
@@ -24,8 +24,8 @@
-
-
+
+
@@ -43,7 +43,7 @@
iRacing iRacingSDK irsdk IBT telemetry SDK API sim-control replay broadcast
NUGET.md
LICENSE
- 2.3.0
+ 2.4.0
Scott Velez
SVappsLAB
Copyright (c) 2026 SVappsLAB
diff --git a/Sdk/tests/README.md b/Sdk/tests/README.md
index c5b430c..618daae 100644
--- a/Sdk/tests/README.md
+++ b/Sdk/tests/README.md
@@ -24,18 +24,23 @@ Live tests require iRacing running on Windows in an active session. Load a track
dotnet run --project .\Sdk\tests\SmokeTests\SmokeTests.csproj -- --filter-trait Category=live
```
+If iRacing isn't running (its shared-memory status isn't "connected"), the live tests are reported as
+**skipped** via `Assert.SkipUnless` rather than timing out and failing. Being in iRacing doesn't guarantee
+telemetry is flowing, though - you still need to be in the car.
+
## Manual Tests
-Manual tests depend on local developer setup, local telemetry files, timing-sensitive behavior, or deliberate inspection. Run them intentionally:
+Manual tests depend on local developer setup, local telemetry files, timing-sensitive behavior, or deliberate inspection.
+They are marked `Explicit = true`, so no test run includes them unless you pass `--explicit on`:
```powershell
-dotnet run --project .\Sdk\tests\SmokeTests\SmokeTests.csproj -- --filter-trait Category=manual
-dotnet run --project .\Sdk\tests\UnitTests\UnitTests.csproj -- --filter-trait Category=manual
+dotnet run --project .\Sdk\tests\SmokeTests\SmokeTests.csproj -- --filter-trait Category=manual --explicit on
+dotnet run --project .\Sdk\tests\UnitTests\UnitTests.csproj -- --filter-trait Category=manual --explicit on
```
## All Tests
-The full solution test run may include tests that require live iRacing data or local manual-test setup.
+The full solution test run skips live tests when iRacing isn't running and never runs explicit (manual) tests.
```powershell
dotnet test --solution .\Sdk\SVappsLAB.iRacingTelemetrySDK.slnx
diff --git a/Sdk/tests/SmokeTests/IBT/IBTYamlValidation.cs b/Sdk/tests/SmokeTests/IBT/IBTYamlValidation.cs
index 74abc21..bf6504a 100644
--- a/Sdk/tests/SmokeTests/IBT/IBTYamlValidation.cs
+++ b/Sdk/tests/SmokeTests/IBT/IBTYamlValidation.cs
@@ -14,15 +14,10 @@ public class IBTYamlValidation
public IBTYamlValidation(ITestOutputHelper output) => _output = output;
- [Fact]
+ [Fact(Explicit = true)]
public async Task ValidateYamlInAllIbtFiles()
{
- // skip if directory doesn't exist
- if (!Directory.Exists(TelemetryDir))
- {
- _output.WriteLine($"skipping: telemetry directory not found: {TelemetryDir}");
- return;
- }
+ Assert.SkipUnless(Directory.Exists(TelemetryDir), $"telemetry directory not found: {TelemetryDir}");
var ibtFiles = Directory.GetFiles(TelemetryDir, "*.ibt");
Assert.True(ibtFiles.Length > 0, "no IBT files found");
diff --git a/Sdk/tests/SmokeTests/Live/Live.cs b/Sdk/tests/SmokeTests/Live/Live.cs
index 77b493f..98da90d 100644
--- a/Sdk/tests/SmokeTests/Live/Live.cs
+++ b/Sdk/tests/SmokeTests/Live/Live.cs
@@ -14,6 +14,7 @@
* limitations under the License.
**/
+using System.IO.MemoryMappedFiles;
using Microsoft.Extensions.Logging;
using SVappsLAB.iRacingTelemetrySDK;
@@ -26,6 +27,27 @@ public class Live : Base
public Live(ITestOutputHelper output) : base(output)
{
+ Assert.SkipUnless(IsIRacingConnected(), "iRacing is not running with an active session");
+ }
+
+ static bool IsIRacingConnected()
+ {
+ const int STATUS_OFFSET = 4; // irsdk_header.status follows the int 'ver' field
+ const int STATUS_CONNECTED = 1;
+
+ if (!OperatingSystem.IsWindows())
+ return false;
+
+ try
+ {
+ using var mmFile = MemoryMappedFile.OpenExisting(@"Local\IRSDKMemMapFileName");
+ using var accessor = mmFile.CreateViewAccessor(0, STATUS_OFFSET + sizeof(int), MemoryMappedFileAccess.Read);
+ return (accessor.ReadInt32(STATUS_OFFSET) & STATUS_CONNECTED) != 0;
+ }
+ catch (FileNotFoundException)
+ {
+ return false;
+ }
}
public static TheoryData>> TestModes =>
diff --git a/Sdk/tests/SmokeTests/SmokeTests.csproj b/Sdk/tests/SmokeTests/SmokeTests.csproj
index 63e61f4..fb4ce72 100644
--- a/Sdk/tests/SmokeTests/SmokeTests.csproj
+++ b/Sdk/tests/SmokeTests/SmokeTests.csproj
@@ -46,13 +46,13 @@
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
+
+
+
diff --git a/Sdk/tests/UnitTests/IBTPlaybackGovernor.cs b/Sdk/tests/UnitTests/IBTPlaybackGovernor.cs
index 921bf6e..95d7f27 100644
--- a/Sdk/tests/UnitTests/IBTPlaybackGovernor.cs
+++ b/Sdk/tests/UnitTests/IBTPlaybackGovernor.cs
@@ -66,7 +66,7 @@ public IBTPlaybackGovernor(LogFixture logFixture)
{1000, 5}
};
- [Theory]
+ [Theory(Explicit = true)]
[Trait("Category", "manual")]
[MemberData(nameof(Data))]
public async Task GovernorTests(int speedMultiplier, int secsOfDataToSimulate)
diff --git a/Sdk/tests/UnitTests/UnitTests.csproj b/Sdk/tests/UnitTests/UnitTests.csproj
index 3a68bc5..5372e49 100644
--- a/Sdk/tests/UnitTests/UnitTests.csproj
+++ b/Sdk/tests/UnitTests/UnitTests.csproj
@@ -28,14 +28,14 @@
-
+
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+