feat(filters): add ft2 mode to filtering options - #1122
Conversation
- Add 'FT2' to MODES arrays in PSKFilterManager and ActivateFilterManager - Add FT2 to callsign.js MODES, detectMode comment detection, and DIGITAL_ISLANDS frequencies - Add FT2 to VOACAP heatmap mode selector
Use the official FT2 calling frequencies: 160m: 1.843, 80m: 3.578, 60m: 5.360, 40m: 7.052, 30m: 10.144, 20m: 14.084, 17m: 18.108, 15m: 21.144, 12m: 24.923, 10m: 28.184
accius
left a comment
There was a problem hiding this comment.
Thanks for this — FT2 support is absolutely something we want, and the filter/band-plan/rig-mapping parts of this PR are exactly right. One correctness problem in the frequency islands needs fixing before merge, though:
FT4 spots get re-labeled FT2
detectMode() in src/utils/callsign.js matches DIGITAL_ISLANDS with a ±5 kHz tolerance, first match wins, and this PR inserts the FT2 islands before the FT4 block. FT2 dials sit only 4 kHz above FT4 dials on nearly every band:
| Band | FT4 dial | FT2 dial (this PR) | gap |
|---|---|---|---|
| 40m | 7.0475 | 7.052 | 4.5 kHz |
| 30m | 10.140 | 10.144 | 4 kHz |
| 20m | 14.080 | 14.084 | 4 kHz |
| 17m | 18.104 | 18.108 | 4 kHz |
| 15m | 21.140 | 21.144 | 4 kHz |
| 12m | 24.919 | 24.923 | 4 kHz |
| 10m | 28.180 | 28.184 | 4 kHz |
Every one of those gaps is inside the ±5 kHz window, and FT2 is checked first — so effectively all FT4 spots would be detected as FT2. On 80m the FT2 dial (3.578) is also exactly 5 kHz from the FT8 dial (3.573), so FT2 spots there would match FT8 (which is checked first).
useBandHealth.js has the same structure with ±3 kHz (ISLAND_TOL_MHZ); there the 80m pair (3.575 vs 3.578) collides.
Suggested fix
Because spotted frequencies sit 0–3 kHz above the dial (audio offset), a symmetric window can't separate two dials 4 kHz apart no matter the ordering. The clean fix is an asymmetric window for the island check:
// signals are always above dial: match [dial, dial + 3.1 kHz]
const offset = mhz - island.mhz;
if (offset >= -0.0005 && offset <= 0.0031) return island.mode;applied to both callsign.js and useBandHealth.js (keeping a small negative allowance for rounded-down spot freqs). That separates FT4 (dial…dial+3.1) from FT2 (dial+4…) cleanly and also tightens the existing FT8/FT4 boundary. If you'd rather keep the symmetric-tolerance style, ordering FT4 before FT2 with tolerance ≤ 2 kHz for those two modes also works, but the asymmetric window matches the physics better.
A couple of detectMode unit tests around the boundaries (e.g. 14.081 → FT4, 14.085 → FT2) would lock this in — src/utils/dxClusterFilters.test.js shows the test setup.
Happy to help if any of this is unclear. Note for us on the maintainer side: the new spot-window balancing regexes (/\bFT[84]\b/ in server/routes/dxcluster.js and the FT8/FT4 cap in src/utils/dxClusterFilters.js) should learn FT2 when this lands — we'll handle that in a follow-up.
|
I've made the fix to callsign.js, but in |
accius
left a comment
There was a problem hiding this comment.
Thanks for this — the additive parts (filter lists, band plan, FT2 dial islands) are fine, but the tolerance rewrite regresses FT8 classification, and FT2 needs to be added at every layer that classifies modes, not just the client. Four things:
1. Don't regress below-dial FT8 matching. The PR replaces the symmetric Math.abs(diff) <= 0.005 window with an asymmetric [-0.5 kHz, +3.1 kHz] one — but the comment right above it (src/utils/callsign.js) documents exactly why ±5 kHz exists: 24.911 spots against the 24.915 12m FT8 dial were being missed at ±3 kHz. With this change that spot (offset −4 kHz) fails to classify again, and it's worse than it used to be: balanceSpotWindow treats null-mode spots as voice, so misclassified FT8 skimmer spots now eat the 25% human-spot reserve from the July mode-balancing work. I get the motivation — FT2 islands sit only 3–4.5 kHz from FT4/FT8 islands, so ±5 kHz would collide — but the fix should be nearest-island matching (or keeping ±5 kHz and using the tight window only to disambiguate actual FT2/FT4 overlaps), not narrowing the window for everything. Please also add a test for the 24.911 case; nothing in CI covers this path, which is why it's green.
2. Dead 160m FT2 island. FT8 1.840 is checked before FT2 1.843, and 1.843 is within +3 kHz of 1.840 under the new window — so a spot at the FT2 dial classifies as FT8. Only 1.8431–1.8461 can ever return FT2.
3. Server-side layers. balancePathsByMode in server/routes/dxcluster.js uses /\bFT[84]\b/ — FT2 comments match neither that nor CW|RTTY|PSK, so FT2 skimmer spots land in the voice bucket and consume the human-spot reserve there too. Needs FT[842]. (The ohc-cluster store's FT8/FT4 cap has the same gap; that's a separate deployable, fine as a follow-up.)
4. VOACAP heatmap. FT2 is added to the mode dropdown, but neither MODE_ADVANTAGE_DB table (src/utils/propagationAdjust.js and its mirror server/utils/propagationPhysics.js — they must change together, per the comment there) has an FT2 entry, so modeAdvantageDb falls back to 0 and an FT2 heatmap renders identically to SSB instead of with the ~30 dB digital advantage. Either add FT2 to both tables or drop it from that dropdown for now.
Happy to merge once the classification window is safe for FT8 and the server regex is covered.
Without this, FT2 heatmaps rendered identically to SSB (0 dB advantage). FT2 gets 32 dB — between FT4 (30) and FT8 (34).
|
Fixed VOACAP. Its unclear how sensitive FT2 currently is, the authors claim its better than FT4, so I've put it in between FT8 and FT4. Issue #3: I'll have a look at the other issues later, these are a bit tricky to reason through constantly |
Change /\bFT[84]\b/ to /\bFT[842]\b/ so FT2 spots are classified as digital skimmer traffic instead of voice/human spots. Without this, FT2 skimmer spots consumed the human-spot reserve (25% of the display window) instead of the digital skimmer bucket (50%).
|
@lbatalha — correction on my review, with apologies: I gave you contradictory guidance, and after verifying FT2 against primary sources today, point 1 of my review was wrong. Your asymmetric window is correct — please keep it. Retracting point 1. FTx signals occupy dial + 0–3 kHz (audio passband above dial), so a legitimate FT8 spot can't sit 4 kHz below the dial. The What I verified about FT2 today (it's newer than most references): it's real but experimental — implemented in Decodium (IU8LMC, Feb 2026) and independently in WSJT-X Improved 3.1.0 (DG2YCB), not official WSJT-X. Your 10-band frequency list matches the published (tentative) community QRGs exactly — nice work. Cluster spots do literally carry One real correction needed — VOACAP sensitivity. 160m island (point 2), softened but still real: FT2's 1.843 dial is exactly FT8's 1.840 + 3.0 kHz, so with FT8 checked first and a +3.1 kHz window, a comment-less spot at exactly 1.8430 classifies FT8. Since comment-marked spots never reach the island check, this only affects bare human spots — fine to resolve by just placing the FT2 island before FT8's 1.840 in the list (a bare spot at exactly the FT2 dial is more likely FT2), or to punt with a code comment noting the inherent ambiguity. With the window kept as-is, the comment updated, FT2 at ~25 in both tables, and the boundary tests, this is good to merge. Sorry again for the churn — the earlier guidance on this thread was right and my review shouldn't have contradicted it. |
…ries - MODE_ADVANTAGE_DB: FT2 32 → 25 in both mirror engines. The "better than FT4" claim is about speed (3.75 s periods), not sensitivity — the ft2.it technical document specs FT2's decode threshold at -12/-13 dB vs FT4's -17.5 and FT8's -21, making it the least sensitive FTx mode. - Replace the stale "24.911 was being missed" comment that argued for the old ±5 kHz window: FTx audio passbands sit entirely above the dial, so that spot was busted, and the asymmetric window is correct. - Document the inherent 160m FT8/FT2 dial adjacency (1.843 = 1.840 + 3 kHz) at the island entry. - Add detectMode boundary tests: FT4/FT2 separation at 4 kHz spacing, below-dial rejection, 160m adjacency, comment precedence, kHz input. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
@lbatalha — to save you the back-and-forth I caused, I've pushed the remaining items directly to your branch (301b44f): FT2 → 25 in both propagation tables (with a comment explaining the speed-vs-sensitivity distinction so nobody trips on it again), the stale 24.911 comment replaced with the passband rationale for your asymmetric window, the 160m adjacency documented at the island entry, and boundary tests in a new src/utils/callsign.test.js. Full suite is green locally. Your window and useBandHealth stay exactly as you had them. If CI agrees, I'll merge — thanks for bearing with the review churn, and for a solid first FT2 integration. |
What does this PR do?
This PR adds FT2 support to existing filters in the DX Cluster and PSKReporter Panels
It also adds FT2 and the currently used test dial frequencies to the bandplan hook/utils (so clicking FT2 spots sets the radio to a DIGI mode)
Type of change
How to test
Checklist
Screenshots (if visual change)