diff --git a/src/Mimir.BufferSmoke/Program.cs b/src/Mimir.BufferSmoke/Program.cs index 51a6601..4cb80cf 100644 --- a/src/Mimir.BufferSmoke/Program.cs +++ b/src/Mimir.BufferSmoke/Program.cs @@ -1709,18 +1709,28 @@ static int RunMuninnMoveIdentitySmoke() State: "bluetooth-waiting", Detail: "Muninn sees this trusted PS Move in BlueZ and will attempt bounded pickup while disconnected.", ObservedAt: "2026-06-17T02:24:21.0000000Z"); + var unreachable = new MuninnMoveIdentityDocument( + IdentityId: "nightwing:move-000704a12345:move-identity", + HostId: "nightwing", + MoveId: "move-000704a12345", + SourcePath: "bluetooth:00:07:04:A1:23:45", + BluetoothHostAddress: "5C:93:A2:9C:A8:A8", + State: "bluetooth-unreachable", + Detail: "Muninn sees this trusted PS Move in BlueZ and will attempt bounded pickup while disconnected. Last pickup attempt: Failed to connect: org.bluez.Error.Failed br-connection-create-socket", + ObservedAt: "2026-06-17T02:24:22.0000000Z"); var encoded = MessagePackSerializer.Serialize(identity); var decoded = MessagePackSerializer.Deserialize(encoded); var snapshot = MimirMuninnMoveEvidenceAdapter.BuildIdentitySnapshots([decoded]).Single(); var expectedHash = MimirMuninnMoveEvidenceAdapter.Fnva64("starfire:move-000704a800d0"); - var roster = MimirMuninnMoveEvidenceAdapter.BuildIdentityRoster([decoded, nightwingWaiting, connected, pickupReady]); + var roster = MimirMuninnMoveEvidenceAdapter.BuildIdentityRoster([decoded, nightwingWaiting, connected, pickupReady, unreachable]); var rotating = roster.Single(entry => entry.MoveId == "move-000704a800d0"); var pickedUp = roster.Single(entry => entry.MoveId == "move-000704a6be5f"); var ready = roster.Single(entry => entry.MoveId == "move-000704a39772"); + var blocked = roster.Single(entry => entry.MoveId == "move-000704a12345"); Console.WriteLine( - $"muninn-move-identity-smoke identity={snapshot.IdentityId} move={snapshot.MoveId} source={snapshot.SourcePath} bluetoothHost={snapshot.BluetoothHostAddress} state={snapshot.State} observedNs={snapshot.ObservedAtNs} controllerHash=0x{snapshot.ControllerIdHash:X} roster={roster.Count} rotating={rotating.StateSummary}/{rotating.PickupReadiness} usbHosts={string.Join(',', rotating.UsbHostIds)} pickupHosts={string.Join(',', rotating.BluetoothPickupHostIds)} pickedUp={pickedUp.StateSummary}/{pickedUp.PickupReadiness} ready={ready.StateSummary}/{ready.PickupReadiness}"); + $"muninn-move-identity-smoke identity={snapshot.IdentityId} move={snapshot.MoveId} source={snapshot.SourcePath} bluetoothHost={snapshot.BluetoothHostAddress} state={snapshot.State} observedNs={snapshot.ObservedAtNs} controllerHash=0x{snapshot.ControllerIdHash:X} roster={roster.Count} rotating={rotating.StateSummary}/{rotating.PickupReadiness} usbHosts={string.Join(',', rotating.UsbHostIds)} pickupHosts={string.Join(',', rotating.BluetoothPickupHostIds)} pickedUp={pickedUp.StateSummary}/{pickedUp.PickupReadiness} ready={ready.StateSummary}/{ready.PickupReadiness} blocked={blocked.StateSummary}/{blocked.PickupReadiness}"); return snapshot.IdentityId == identity.IdentityId && snapshot.HostId == "starfire" && @@ -1730,7 +1740,7 @@ static int RunMuninnMoveIdentitySmoke() snapshot.State == "usb-visible" && snapshot.ObservedAtNs > 0 && snapshot.ControllerIdHash == expectedHash && - roster.Count == 3 && + roster.Count == 4 && rotating.HasUsbWitness && rotating.UsbHostIds.SequenceEqual(["starfire"]) && rotating.BluetoothPickupHostIds.SequenceEqual(["nightwing"]) && @@ -1745,7 +1755,12 @@ static int RunMuninnMoveIdentitySmoke() ready.StateSummary == "bluetooth-waiting" && ready.PickupReadiness == "pickup-ready" && ready.IsBluetoothPickupReady && - ready.UsbBlockingHostIds.Length == 0 + ready.UsbBlockingHostIds.Length == 0 && + blocked.StateSummary == "bluetooth-unreachable" && + blocked.PickupReadiness == "bluetooth-unreachable" && + !blocked.IsBluetoothPickupReady && + blocked.BluetoothPickupHostIds.Length == 0 && + blocked.UsbBlockingHostIds.Length == 0 ? 0 : 1; } diff --git a/src/Mimir.Runtime/Synchronization/MimirMuninnMoveEvidence.cs b/src/Mimir.Runtime/Synchronization/MimirMuninnMoveEvidence.cs index d387496..1549858 100644 --- a/src/Mimir.Runtime/Synchronization/MimirMuninnMoveEvidence.cs +++ b/src/Mimir.Runtime/Synchronization/MimirMuninnMoveEvidence.cs @@ -231,10 +231,13 @@ public static class MimirMuninnMoveEvidenceAdapter .ToArray(); var hasConnectedBluetooth = snapshots.Any(identity => identity.State.Equals("bluetooth-connected", StringComparison.OrdinalIgnoreCase)); + var hasUnreachableBluetooth = snapshots.Any(identity => + identity.State.Equals("bluetooth-unreachable", StringComparison.OrdinalIgnoreCase)); var pickupReadiness = ResolvePickupReadiness( usbHostIds, waitingPickupHostIds, - hasConnectedBluetooth); + hasConnectedBluetooth, + hasUnreachableBluetooth); var sourcePaths = snapshots .Select(identity => identity.SourcePath) .Where(path => !string.IsNullOrWhiteSpace(path)) @@ -268,7 +271,8 @@ public static class MimirMuninnMoveEvidenceAdapter private static string ResolvePickupReadiness( string[] usbHostIds, string[] waitingPickupHostIds, - bool hasConnectedBluetooth) + bool hasConnectedBluetooth, + bool hasUnreachableBluetooth) { if (usbHostIds.Length > 0) { @@ -280,6 +284,11 @@ public static class MimirMuninnMoveEvidenceAdapter return "bluetooth-connected"; } + if (hasUnreachableBluetooth) + { + return "bluetooth-unreachable"; + } + if (waitingPickupHostIds.Length > 0) { return "pickup-ready"; @@ -300,8 +309,9 @@ public static class MimirMuninnMoveEvidenceAdapter { "usb-visible" => 0, "bluetooth-connected" => 1, - "bluetooth-waiting" => 2, - "bluetooth-known" => 3, + "bluetooth-unreachable" => 2, + "bluetooth-waiting" => 3, + "bluetooth-known" => 4, _ => 9 };