From 5071b1eea44ab9b66bd08342a0c45a25d1f2a27f Mon Sep 17 00:00:00 2001 From: Natalie Bunduwongse Date: Tue, 12 May 2026 14:20:28 +1200 Subject: [PATCH] chore(audience-sdk): remove em dashes from comments and trim verbose phrasing Co-Authored-By: Claude Sonnet 4.6 --- .../Audience/Editor/AudienceMobileBuildSettings.cs | 4 ++-- .../Audience/Editor/iOSFrameworkPostProcessor.cs | 8 ++++---- .../Audience/Editor/iOSInfoPlistPostProcessor.cs | 8 ++++---- .../Editor/iOSPrivacyManifestPostProcessor.cs | 2 +- src/Packages/Audience/Runtime/AudienceConfig.cs | 2 +- src/Packages/Audience/Runtime/ImmutableAudience.cs | 12 ++++++------ .../Runtime/Plugins/Android/proguard-user.txt | 2 +- .../Audience/Runtime/Unity/Mobile/ATTBridge.cs | 4 ++-- .../Runtime/Unity/Mobile/AttributionContext.cs | 4 ++-- .../Audience/Runtime/Unity/Mobile/GAIDBridge.cs | 4 ++-- .../Audience/Runtime/Unity/Mobile/IDFVBridge.cs | 2 +- .../Runtime/Unity/Mobile/InstallReferrerBridge.cs | 2 +- .../Tests/Editor/iOSInfoPlistPostProcessorTests.cs | 4 ++-- .../Audience/Tests/Runtime/ImmutableAudienceTests.cs | 8 ++++---- .../Audience/Tests/Runtime/Unity/GAIDBridgeTests.cs | 2 +- .../Runtime/Unity/InstallReferrerBridgeTests.cs | 6 +++--- .../Tests/Runtime/Utility/TimerDisposalTests.cs | 2 +- 17 files changed, 38 insertions(+), 38 deletions(-) diff --git a/src/Packages/Audience/Editor/AudienceMobileBuildSettings.cs b/src/Packages/Audience/Editor/AudienceMobileBuildSettings.cs index 41a367f8b..7049b3464 100644 --- a/src/Packages/Audience/Editor/AudienceMobileBuildSettings.cs +++ b/src/Packages/Audience/Editor/AudienceMobileBuildSettings.cs @@ -27,7 +27,7 @@ public sealed class AudienceMobileBuildSettings : ScriptableObject [SerializeField] [Tooltip("Copy shown in the iOS App Tracking Transparency prompt. " + - "Apple rejects empty or generic strings — describe what is " + + "Apple rejects empty or generic strings. Describe what is " + "collected and why.")] private string trackingUsageDescription = DefaultTrackingUsageDescription; @@ -64,7 +64,7 @@ private static void CreateAsset() if (guids.Length > 1) { Debug.LogWarning( - $"[ImmutableAudience] Multiple AudienceMobileBuildSettings assets found — " + + $"[ImmutableAudience] Multiple AudienceMobileBuildSettings assets found - " + $"using '{path}'. Remove the duplicates to avoid unexpected build behaviour."); } return AssetDatabase.LoadAssetAtPath(path); diff --git a/src/Packages/Audience/Editor/iOSFrameworkPostProcessor.cs b/src/Packages/Audience/Editor/iOSFrameworkPostProcessor.cs index 8659c7e40..c4c1f4b74 100644 --- a/src/Packages/Audience/Editor/iOSFrameworkPostProcessor.cs +++ b/src/Packages/Audience/Editor/iOSFrameworkPostProcessor.cs @@ -19,7 +19,7 @@ namespace Immutable.Audience.Editor /// AdSupport.framework hosts ASIdentifierManager (the IDFA /// accessor). Without it linked, the runtime NSClassFromString /// lookup returns nil and the IDFA is silently dropped from - /// game_launch — the Info.plist key alone does not load this + /// game_launch. The Info.plist key alone does not load this /// framework. Linked as a required dep; the framework has shipped on /// every iOS release since 6.0. /// @@ -27,7 +27,7 @@ namespace Immutable.Audience.Editor /// AppTrackingTransparency.framework hosts ATTrackingManager. /// Unity often auto-links this when NSUserTrackingUsageDescription /// is present, but the auto-link heuristic is undocumented and - /// version-sensitive — an explicit weak link is immune to that drift and + /// version-sensitive. An explicit weak link is immune to that drift and /// keeps the binary loadable on iOS < 14 (the runtime code already /// guards via NSClassFromString). /// @@ -40,7 +40,7 @@ namespace Immutable.Audience.Editor internal static class iOSFrameworkPostProcessor { // Runs just after the Info.plist post-processor (9050). Order is - // independent in practice — both edit different files — but keeping + // independent in practice (both edit different files), but keeping // them adjacent makes the build log obvious. internal const int CallbackOrder = 9051; @@ -76,7 +76,7 @@ internal static void OnPostProcessBuild(BuildTarget target, string pathToBuiltPr #endif } - // Reads the iOS-target define list specifically — the post-processor + // Reads the iOS-target define list specifically. The post-processor // mutates iOS build output regardless of which target the editor is // currently focused on. private static bool AttributionDefineEnabled() diff --git a/src/Packages/Audience/Editor/iOSInfoPlistPostProcessor.cs b/src/Packages/Audience/Editor/iOSInfoPlistPostProcessor.cs index edd5d644a..4061373ab 100644 --- a/src/Packages/Audience/Editor/iOSInfoPlistPostProcessor.cs +++ b/src/Packages/Audience/Editor/iOSInfoPlistPostProcessor.cs @@ -18,7 +18,7 @@ namespace Immutable.Audience.Editor /// /// Both keys are gated by the AUDIENCE_MOBILE_ATTRIBUTION /// scripting define so a studio that hasn't opted into attribution - /// ships a clean Info.plist — Apple flags apps that include + /// ships a clean Info.plist. Apple flags apps that include /// either key without the corresponding code paths. /// /// Values come from the @@ -88,11 +88,11 @@ private static void ValidateBuildSettings() $" NSUserTrackingUsageDescription: {description}\n" + $" SKAdNetworkItems: {ids.Length} id(s)\n" + (ids.Length == 0 - ? " (no SKAdNetwork ids configured — set them on the AudienceMobileBuildSettings asset)\n" + ? " (no SKAdNetwork ids configured - set them on the AudienceMobileBuildSettings asset)\n" : string.Concat(System.Array.ConvertAll(ids, id => $" - {id}\n")))); } - // Reads the iOS-target define list specifically — the post-processor + // Reads the iOS-target define list specifically. The post-processor // mutates iOS build output regardless of which target the editor is // currently focused on. private static bool AttributionDefineEnabled() @@ -114,7 +114,7 @@ internal static void ApplyTrackingUsageDescription( ? settings.TrackingUsageDescription : AudienceMobileBuildSettings.DefaultTrackingUsageDescription; - // Always overwrite — the settings asset is the source of truth, + // Always overwrite. The settings asset is the source of truth, // beating any placeholder a lower-order post-processor wrote. root.SetString("NSUserTrackingUsageDescription", description); } diff --git a/src/Packages/Audience/Editor/iOSPrivacyManifestPostProcessor.cs b/src/Packages/Audience/Editor/iOSPrivacyManifestPostProcessor.cs index 2f39b2a10..e8a12821e 100644 --- a/src/Packages/Audience/Editor/iOSPrivacyManifestPostProcessor.cs +++ b/src/Packages/Audience/Editor/iOSPrivacyManifestPostProcessor.cs @@ -62,7 +62,7 @@ internal static void OnPostProcessBuild(BuildTarget target, string pathToBuiltPr /// /// Adds the attribution-specific privacy declarations to an existing /// (already Unity-merged) PrivacyInfo.xcprivacy plist root. - /// Idempotent — safe to call on a manifest that already has these entries. + /// Idempotent. Safe to call on a manifest that already has these entries. /// internal static void ApplyAttributionPrivacyEntries(PlistElementDict root) { diff --git a/src/Packages/Audience/Runtime/AudienceConfig.cs b/src/Packages/Audience/Runtime/AudienceConfig.cs index d0dd7090a..cd7c7148f 100644 --- a/src/Packages/Audience/Runtime/AudienceConfig.cs +++ b/src/Packages/Audience/Runtime/AudienceConfig.cs @@ -68,7 +68,7 @@ public class AudienceConfig /// collected at runtime. Without the define, this setter is a /// no-op. /// - /// Studios who set neither ship a clean binary — no AD_ID permission, + /// Studios who set neither ship a clean binary: no AD_ID permission, /// no native attribution code, NSPrivacyTracking = false. /// public bool EnableMobileAttribution { get; set; } = false; diff --git a/src/Packages/Audience/Runtime/ImmutableAudience.cs b/src/Packages/Audience/Runtime/ImmutableAudience.cs index 7b5237433..d2a58765c 100644 --- a/src/Packages/Audience/Runtime/ImmutableAudience.cs +++ b/src/Packages/Audience/Runtime/ImmutableAudience.cs @@ -742,7 +742,7 @@ private static void SyncConsentToBackend(AudienceConfig config, ConsentLevel lev /// /// /// - /// Studios decide when to show the prompt — Apple's HIG requires it + /// Studios decide when to show the prompt. Apple's HIG requires it /// to fire at a moment that makes the value to the player obvious, /// not at SDK Init. The IDFA, when authorized, is collected /// automatically on the next via @@ -751,7 +751,7 @@ private static void SyncConsentToBackend(AudienceConfig config, ConsentLevel lev /// /// /// Resolves to - /// in three distinct cases — callers who use this signal to decide + /// in three distinct cases. Callers who use this signal to decide /// whether to retry should consult the SDK log to disambiguate: /// /// @@ -1148,7 +1148,7 @@ private static void FireGameLaunch( if (skanRegistered == true) properties["skanRegistered"] = true; - // iOS ATT/IDFA snapshot — merged after Unity context so attribution + // iOS ATT/IDFA snapshot, merged after Unity context so attribution // keys are authoritative if both sources happen to set the same key. // idfa and gaid are cross-app device identifiers, same privacy class // as userId; gate them at Full-only. State-class keys (attStatus, @@ -1170,7 +1170,7 @@ private static void FireGameLaunch( } // Fires install_referrer_received exactly once per install. Cache - // file presence alone isn't enough — on first launch the bridge may + // file presence alone isn't enough. On first launch the bridge may // write the cache after Init has already run, so the event must be // dispatched at the next Init that observes a cache hit. The on-disk // "sent" marker provides idempotency across that boundary. @@ -1193,7 +1193,7 @@ private static void FireInstallReferrerReceivedOnce(AudienceConfig config, strin } catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException) { - // Marker write failed — the event will re-fire on the next + // Marker write failed. The event will re-fire on the next // launch. Pipeline-side dedup or the cost of one duplicate is // less bad than never sending the event at all. Log.Warn(AudienceLogs.InstallReferrerSentMarkerWriteFailed(ex)); @@ -1220,7 +1220,7 @@ private static string AttStatusToString(int status) // RequestTrackingAuthorizationAsync resolves). // // First observation (no file): persists the baseline and returns without - // firing — game_launch already captures the initial state on that Init. + // firing. game_launch already captures the initial state on that Init. private static void CheckAndFireAttStatusChanged( AudienceConfig config, ConsentLevel consent, diff --git a/src/Packages/Audience/Runtime/Plugins/Android/proguard-user.txt b/src/Packages/Audience/Runtime/Plugins/Android/proguard-user.txt index f90a9b305..fad7cf61b 100644 --- a/src/Packages/Audience/Runtime/Plugins/Android/proguard-user.txt +++ b/src/Packages/Audience/Runtime/Plugins/Android/proguard-user.txt @@ -13,7 +13,7 @@ # Keep Google Play Services AdvertisingIdClient symbols (GAID). # # AdvertisingIdClient and its inner Info class are reflected via JNI from -# GAIDBridge — they have no managed-side reference for R8 to follow, so +# GAIDBridge - they have no managed-side reference for R8 to follow, so # fullMode in studio projects can drop them. Defensive rules ensure the # getAdvertisingIdInfo / getId / isLimitAdTrackingEnabled surface survives. -keep class com.google.android.gms.ads.identifier.** { *; } diff --git a/src/Packages/Audience/Runtime/Unity/Mobile/ATTBridge.cs b/src/Packages/Audience/Runtime/Unity/Mobile/ATTBridge.cs index c923c48a1..daee0cda5 100644 --- a/src/Packages/Audience/Runtime/Unity/Mobile/ATTBridge.cs +++ b/src/Packages/Audience/Runtime/Unity/Mobile/ATTBridge.cs @@ -47,7 +47,7 @@ internal static class ATTBridge // Hold a single delegate instance for the lifetime of the process. If // we passed a fresh delegate to each P/Invoke call, IL2CPP could GC // the wrapper before Apple's completion handler fires (which can be - // seconds or minutes later — the user may swipe the prompt away or + // seconds or minutes later. The user may swipe the prompt away or // background the app). private static readonly NativeStatusCallback _staticCallback = OnATTStatus; @@ -60,7 +60,7 @@ internal static class ATTBridge // - MonoPInvokeCallback so IL2CPP emits the trampoline that lets // native code call back into managed code. // - Preserve so managed-code stripping at High doesn't drop this - // method (it has no managed callers — only the function pointer). + // method (no managed callers; only the function pointer). // Without both, the await in RequestAsync never completes on a // shipping build with stripping enabled. [MonoPInvokeCallback(typeof(NativeStatusCallback))] diff --git a/src/Packages/Audience/Runtime/Unity/Mobile/AttributionContext.cs b/src/Packages/Audience/Runtime/Unity/Mobile/AttributionContext.cs index c1a97e072..73fecf1f0 100644 --- a/src/Packages/Audience/Runtime/Unity/Mobile/AttributionContext.cs +++ b/src/Packages/Audience/Runtime/Unity/Mobile/AttributionContext.cs @@ -14,7 +14,7 @@ namespace Immutable.Audience.Unity.Mobile // Android: gaid + gaidLimitAdTracking are read from the GAIDBridge disk // cache populated by the previous launch's background fetch (Google's // AdvertisingIdClient is sync + must run off main thread, so first launch - // ships nothing — gaidLimitAdTracking shows up on launch #2 onwards). + // ships nothing; gaidLimitAdTracking shows up on launch #2 onwards). internal static class AttributionContext { // Maps Apple's ATTrackingManagerAuthorizationStatus to the wire @@ -33,7 +33,7 @@ internal static string AttStatusToString(int status) } // persistentDataPath is required for Android (GAID disk cache); iOS - // ignores it. Returns a possibly-empty dict — never null — so callers + // ignores it. Returns a possibly-empty dict (never null) so callers // can merge unconditionally. internal static IReadOnlyDictionary Capture(string? persistentDataPath = null) { diff --git a/src/Packages/Audience/Runtime/Unity/Mobile/GAIDBridge.cs b/src/Packages/Audience/Runtime/Unity/Mobile/GAIDBridge.cs index 8c7fcb5b4..b593938ff 100644 --- a/src/Packages/Audience/Runtime/Unity/Mobile/GAIDBridge.cs +++ b/src/Packages/Audience/Runtime/Unity/Mobile/GAIDBridge.cs @@ -14,7 +14,7 @@ namespace Immutable.Audience.Unity.Mobile /// Reads gaid + limitAdTracking via AdvertisingIdClient. Google requires /// the call run off the main thread, so we dispatch on a dedicated worker /// and cache the result to disk for the next launch. GAID can change - /// (user reset), so we refresh every launch — first launch ships nothing, + /// (user reset), so we refresh every launch. First launch ships nothing; /// launch #2+ ships the previously-cached value. /// internal static class GAIDBridge @@ -101,7 +101,7 @@ internal static void WriteCacheEntry(string persistentDataPath, string gaidOrEmp private static void StartFetchNative(string persistentDataPath) { // Dedicated Thread (not ThreadPool) so Attach/Detach pair on the - // same one-shot worker — ThreadPool reuse strands JVM state. + // same one-shot worker. ThreadPool reuse strands JVM state. var thread = new Thread(() => FetchOnWorkerThread(persistentDataPath)) { IsBackground = true, diff --git a/src/Packages/Audience/Runtime/Unity/Mobile/IDFVBridge.cs b/src/Packages/Audience/Runtime/Unity/Mobile/IDFVBridge.cs index bc0288cd3..984e3c948 100644 --- a/src/Packages/Audience/Runtime/Unity/Mobile/IDFVBridge.cs +++ b/src/Packages/Audience/Runtime/Unity/Mobile/IDFVBridge.cs @@ -9,7 +9,7 @@ namespace Immutable.Audience.Unity.Mobile { internal static class IDFVBridge { - // Replaceable in tests — captures NativeImpl by default. + // Replaceable in tests; captures NativeImpl by default. internal static Func Impl = NativeImpl; internal static string? GetIDFV() => Impl(); diff --git a/src/Packages/Audience/Runtime/Unity/Mobile/InstallReferrerBridge.cs b/src/Packages/Audience/Runtime/Unity/Mobile/InstallReferrerBridge.cs index 47eb5625b..15a572b1c 100644 --- a/src/Packages/Audience/Runtime/Unity/Mobile/InstallReferrerBridge.cs +++ b/src/Packages/Audience/Runtime/Unity/Mobile/InstallReferrerBridge.cs @@ -135,7 +135,7 @@ private static void StartFetchNative(string persistentDataPath) // // Each AndroidJavaClass / AndroidJavaObject holds a JNI global // reference; leaking them stranded a JNI handle every Init. - // `client` is the exception — ownership transfers to the + // `client` is the exception: ownership transfers to the // listener which disposes it in its endConnection finally. using (var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer")) using (var activity = unityPlayer.GetStatic("currentActivity")) diff --git a/src/Packages/Audience/Tests/Editor/iOSInfoPlistPostProcessorTests.cs b/src/Packages/Audience/Tests/Editor/iOSInfoPlistPostProcessorTests.cs index da0865631..ac0801136 100644 --- a/src/Packages/Audience/Tests/Editor/iOSInfoPlistPostProcessorTests.cs +++ b/src/Packages/Audience/Tests/Editor/iOSInfoPlistPostProcessorTests.cs @@ -45,7 +45,7 @@ public void ApplyTrackingUsageDescription_WithCustomCopy_WritesIt() [Test] public void ApplyTrackingUsageDescription_WithEmptyCopy_FallsBackToDefault() { - // Whitespace falls through to the default — Apple rejects empty. + // Whitespace falls through to the default. Apple rejects empty. var settings = ScriptableObject.CreateInstance(); SetPrivate(settings, "trackingUsageDescription", " "); @@ -61,7 +61,7 @@ public void ApplyTrackingUsageDescription_WithEmptyCopy_FallsBackToDefault() [Test] public void ApplyTrackingUsageDescription_OverwritesExistingValue() { - // Settings asset is the source of truth — beat any placeholder + // Settings asset is the source of truth. Beat any placeholder // a lower-order post-processor wrote. var root = new PlistDocument().root; root.SetString("NSUserTrackingUsageDescription", "stale placeholder"); diff --git a/src/Packages/Audience/Tests/Runtime/ImmutableAudienceTests.cs b/src/Packages/Audience/Tests/Runtime/ImmutableAudienceTests.cs index 0e0d4c712..d3dd3ed7c 100644 --- a/src/Packages/Audience/Tests/Runtime/ImmutableAudienceTests.cs +++ b/src/Packages/Audience/Tests/Runtime/ImmutableAudienceTests.cs @@ -1524,7 +1524,7 @@ public void Init_GameLaunch_StripsGaid_WhenConsentAnonymous() // // Dedicated event (not a game_launch property): install attribution // is install-level state, decoupled from launch / session lifecycles. - // Fires once per install — cache landing late on first launch is + // Fires once per install. Cache landing late on first launch is // recovered by the next Init via the on-disk "sent" marker. // ----------------------------------------------------------------- @@ -1561,7 +1561,7 @@ public void Init_GameLaunch_NeverIncludesInstallReferrer() .Select(File.ReadAllText) .First(c => c.Contains("\"game_launch\"")); Assert.IsFalse(launchFile.Contains("installReferrer"), - "game_launch must never carry installReferrer — it ships on its own event"); + "game_launch must never carry installReferrer; it ships on its own event"); } [Test] @@ -1585,7 +1585,7 @@ public void Init_DoesNotFireInstallReferrerReceived_WhenProviderReturnsNull() [Test] public void Init_DoesNotFireInstallReferrerReceived_WhenProviderReturnsEmpty() { - // Empty cache file marks "fetched, no referrer" — bridge maps + // Empty cache file marks "fetched, no referrer". Bridge maps // that to null on read, but defend the contract here too. ImmutableAudience.MobileInstallReferrerProvider = () => string.Empty; var config = MakeConfig(); @@ -1766,7 +1766,7 @@ public void Init_FiresInstallReferrerReceived_AfterConsentUpgradedToFull() [Test] public async Task RequestTrackingAuthorization_NoProvider_ReturnsNotDetermined() { - // No provider set — non-iOS environment. + // No provider set (non-iOS environment). var status = await ImmutableAudience.RequestTrackingAuthorizationAsync(); Assert.AreEqual(TrackingAuthorizationStatus.NotDetermined, status); } diff --git a/src/Packages/Audience/Tests/Runtime/Unity/GAIDBridgeTests.cs b/src/Packages/Audience/Tests/Runtime/Unity/GAIDBridgeTests.cs index a2c0c81bc..24742013e 100644 --- a/src/Packages/Audience/Tests/Runtime/Unity/GAIDBridgeTests.cs +++ b/src/Packages/Audience/Tests/Runtime/Unity/GAIDBridgeTests.cs @@ -124,7 +124,7 @@ public void EnsureFetchStarted_CalledTwiceInSameProcess_FetchesOnce() public void EnsureFetchStarted_TerminalCacheExists_StillFetches() { // Unlike the install referrer (terminal once written), GAID can - // change (user reset) — we always refresh the cache for the next + // change (user reset), so we always refresh the cache for the next // launch even when a value already exists. GAIDBridge.WriteCacheEntry(_testDir, "stale-gaid", limitAdTracking: false); diff --git a/src/Packages/Audience/Tests/Runtime/Unity/InstallReferrerBridgeTests.cs b/src/Packages/Audience/Tests/Runtime/Unity/InstallReferrerBridgeTests.cs index 7f7dd1242..03b779852 100644 --- a/src/Packages/Audience/Tests/Runtime/Unity/InstallReferrerBridgeTests.cs +++ b/src/Packages/Audience/Tests/Runtime/Unity/InstallReferrerBridgeTests.cs @@ -58,7 +58,7 @@ public void GetCachedInstallReferrer_NonEmptyFile_ReturnsContent() [Test] public void GetCachedInstallReferrer_EmptyFile_ReturnsNull() { - // Empty file marks "fetched, no referrer" — caller treats it as + // Empty file marks "fetched, no referrer". Caller treats it as // "nothing to emit" but EnsureFetchStarted treats it as terminal. InstallReferrerBridge.WriteCacheEntry(_testDir, string.Empty); @@ -104,7 +104,7 @@ public void EnsureFetchStarted_CalledTwiceInSameProcess_FetchesOnce() public void EnsureFetchStarted_TerminalCacheExists_SkipsFetch() { // Simulate a previous launch that wrote a cache entry. The fetch - // must NOT run again — Google's referrer is stable per install. + // must NOT run again. Google's referrer is stable per install. InstallReferrerBridge.WriteCacheEntry(_testDir, "utm_source=test"); var fetchCalls = 0; @@ -118,7 +118,7 @@ public void EnsureFetchStarted_TerminalCacheExists_SkipsFetch() [Test] public void EnsureFetchStarted_EmptyCacheExists_SkipsFetch() { - // Empty cache = "fetched, no referrer" — terminal state, no retry. + // Empty cache = "fetched, no referrer": terminal state, no retry. InstallReferrerBridge.WriteCacheEntry(_testDir, string.Empty); var fetchCalls = 0; diff --git a/src/Packages/Audience/Tests/Runtime/Utility/TimerDisposalTests.cs b/src/Packages/Audience/Tests/Runtime/Utility/TimerDisposalTests.cs index 3ad3254f4..0b3a152df 100644 --- a/src/Packages/Audience/Tests/Runtime/Utility/TimerDisposalTests.cs +++ b/src/Packages/Audience/Tests/Runtime/Utility/TimerDisposalTests.cs @@ -39,7 +39,7 @@ public void DisposeAndWait_LongCallback_ReturnsFalseAndLeaksHandle() // Mono player builds (Unity Standalone Mono targets) signal // Timer.Dispose's wait handle ahead of in-flight callbacks // completing, which breaks this unit test's premise. Production - // code is unaffected — DrainHeartbeatTimer is exercised + // code is unaffected. DrainHeartbeatTimer is exercised // end-to-end by the SampleApp PlayMode tests on the same Mono // builds and works correctly. This unit test asserts a // lower-level WaitHandle invariant that doesn't hold under Mono.