From dd6d46bff9552c43974613e2cfe2dd1333b6031f Mon Sep 17 00:00:00 2001 From: Strycher Date: Mon, 10 Aug 2026 03:57:33 -0400 Subject: [PATCH] fix(#550): poll CoreScope with backoff, not once at T+0 Observer counts accrue over time (the packet must propagate the mesh and be reported before CoreScope has any record), so an instant query at send+0.2s always missed it. Now polls at ~10/30/60/120s, keeping the highest count as observers report in; bails on disconnect. Part of #524. Agent: QuietSnow (session 31eaba02) --- lib/connector/meshcore_connector.dart | 37 ++++++++++++++++++++------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/lib/connector/meshcore_connector.dart b/lib/connector/meshcore_connector.dart index 2b7bdaf..b07c6aa 100644 --- a/lib/connector/meshcore_connector.dart +++ b/lib/connector/meshcore_connector.dart @@ -4754,6 +4754,17 @@ class MeshCoreConnector extends ChangeNotifier { /// After sending a channel message, fetch its firmware hash then CoreScope's /// observer count and stamp both on the message. Best-effort and gated; any /// failure leaves the message showing radio-only. + /// Backoff schedule for polling CoreScope after a send. Observer counts + /// accrue over time: the packet must propagate the mesh and be reported by + /// observers before CoreScope has any record, then the count keeps climbing + /// for a few seconds. Cumulative wall-clock: ~10s, 30s, 60s, 120s. + static const List _coreScopePollSchedule = [ + Duration(seconds: 10), + Duration(seconds: 20), + Duration(seconds: 30), + Duration(seconds: 60), + ]; + Future _fetchAndStoreCoreScopeCount( int channelIndex, int ts, @@ -4774,15 +4785,23 @@ class MeshCoreConnector extends ChangeNotifier { messageId, (m) => m.copyWith(onAirHash: pkt.hashHex), ); - final count = await _coreScopeService.fetchObserverCount(pkt.hashHex); - if (count == null) return; - _updateChannelMessageById( - channelIndex, - messageId, - (m) => m.copyWith(coreScopeObserverCount: count), - ); - appLogger.info('stored observer count=$count', tag: 'CoreScope'); - notifyListeners(); + // Poll with backoff, keeping the highest count as observers report in. + var best = 0; + for (final delay in _coreScopePollSchedule) { + await Future.delayed(delay); + if (!isConnected) return; + final count = await _coreScopeService.fetchObserverCount(pkt.hashHex); + if (count != null && count > best) { + best = count; + _updateChannelMessageById( + channelIndex, + messageId, + (m) => m.copyWith(coreScopeObserverCount: count), + ); + appLogger.info('observer count=$count', tag: 'CoreScope'); + notifyListeners(); + } + } } catch (e) { appLogger.warn( 'CoreScope observer-count fetch failed: $e',