fix(#25): SNR-first repeater ranking (recency as a sub-dB tie-breaker)
DirectRepeater.ranking added recencyScore (0..1.8M) to the SNR term (1000/dB), so recency dominated (~1 dB == 1 second), contradicting the comment that recency only breaks ties. Scale recency to a 0-999 bonus so SNR (1000/dB) is primary and recency only decides sub-dB ties. Offset SNR by its floor (+32; SX126x SNR spans -32..+31.75 dB) so every live ranking stays >= 0, above the -1 stale sentinel; otherwise the scaled-down recency leaves live rankings negative and a stale repeater would outrank a live one. Adds DirectRepeater.ranking tests: SNR-first, recency tie-break, stale=-1, live-always-outranks-stale. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>pull/60/head
parent
5687916192
commit
5227507810
@ -0,0 +1,60 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:meshcore_open/connector/meshcore_connector.dart';
|
||||
|
||||
void main() {
|
||||
group('DirectRepeater.ranking (SNR-first)', () {
|
||||
test('a stronger signal outranks a more-recent but weaker one', () {
|
||||
// 5 dB better SNR, but heard 24 min earlier — signal margin must win.
|
||||
final strongOld = DirectRepeater(
|
||||
pubkeyFirstByte: 1,
|
||||
snr: 10.0,
|
||||
lastUpdated: DateTime.now().subtract(const Duration(minutes: 25)),
|
||||
);
|
||||
final weakRecent = DirectRepeater(
|
||||
pubkeyFirstByte: 2,
|
||||
snr: 5.0,
|
||||
lastUpdated: DateTime.now().subtract(const Duration(minutes: 1)),
|
||||
);
|
||||
expect(strongOld.ranking, greaterThan(weakRecent.ranking));
|
||||
});
|
||||
|
||||
test('recency breaks ties between equal-SNR repeaters', () {
|
||||
final older = DirectRepeater(
|
||||
pubkeyFirstByte: 1,
|
||||
snr: 10.0,
|
||||
lastUpdated: DateTime.now().subtract(const Duration(minutes: 20)),
|
||||
);
|
||||
final newer = DirectRepeater(
|
||||
pubkeyFirstByte: 2,
|
||||
snr: 10.0,
|
||||
lastUpdated: DateTime.now().subtract(const Duration(minutes: 1)),
|
||||
);
|
||||
expect(newer.ranking, greaterThan(older.ranking));
|
||||
});
|
||||
|
||||
test('a stale repeater ranks -1', () {
|
||||
final stale = DirectRepeater(
|
||||
pubkeyFirstByte: 1,
|
||||
snr: 30.0,
|
||||
lastUpdated: DateTime.now().subtract(const Duration(minutes: 31)),
|
||||
);
|
||||
expect(stale.ranking, -1);
|
||||
});
|
||||
|
||||
test('a live repeater always outranks a stale one', () {
|
||||
// Weak but live vs strong but stale — the live one must win, so the
|
||||
// stale sentinel (-1) has to sit below every live ranking.
|
||||
final weakLive = DirectRepeater(
|
||||
pubkeyFirstByte: 1,
|
||||
snr: -30.0,
|
||||
lastUpdated: DateTime.now().subtract(const Duration(minutes: 1)),
|
||||
);
|
||||
final strongStale = DirectRepeater(
|
||||
pubkeyFirstByte: 2,
|
||||
snr: 30.0,
|
||||
lastUpdated: DateTime.now().subtract(const Duration(minutes: 31)),
|
||||
);
|
||||
expect(weakLive.ranking, greaterThan(strongStale.ranking));
|
||||
});
|
||||
});
|
||||
}
|
||||
Loading…
Reference in new issue