Skip to content

feat(filters): add ft2 mode to filtering options - #1122

Merged
accius merged 8 commits into
accius:Stagingfrom
lbatalha:add-ft2-filtering
Jul 28, 2026
Merged

feat(filters): add ft2 mode to filtering options#1122
accius merged 8 commits into
accius:Stagingfrom
lbatalha:add-ft2-filtering

Conversation

@lbatalha

@lbatalha lbatalha commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

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

  • Bug fix
  • New feature
  • Performance improvement
  • Refactor / code cleanup
  • Documentation
  • Translation
  • Map layer plugin

How to test

  1. Check DX Cluster and PSKReporter panel filter options for FT2
  2. Enable Filter
  3. Check that results only show FT2 spots

Checklist

  • App loads without console errors

Screenshots (if visual change)

image image

lbatalha added 3 commits July 9, 2026 17:38
- 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 accius left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@lbatalha

lbatalha commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator Author

I've made the fix to callsign.js, but in useBandHealth.js the existing window is already pretty tight at 3khz, seems fine?

@accius accius left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

lbatalha added 2 commits July 28, 2026 22:37
Without this, FT2 heatmaps rendered identically to SSB (0 dB advantage).
FT2 gets 32 dB — between FT4 (30) and FT8 (34).
@lbatalha

Copy link
Copy Markdown
Collaborator Author

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: balancePathsByMode did not exist when I first made these changes :P. Fixed the regex.

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%).
@accius

accius commented Jul 28, 2026

Copy link
Copy Markdown
Owner

@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 24.911 case memorialized in the old code comment was a busted/rounded spot, not behavior worth preserving — and a symmetric window genuinely cannot separate dials 4 kHz apart, exactly as the earlier suggestion on this thread said. The [-0.5, +3.1] kHz window matches the physics. Two small asks in its place: update that stale 24.911 comment so it stops confusing the next reader (including me), and add the boundary tests (e.g. 14.081 → FT4, 14.085 → FT2).

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 FT2 in FT8-style comments (verified live on 14084.0 today), so the comment-keyword path you added does the heavy lifting; RBN skimmers don't decode FT2 yet.

One real correction needed — VOACAP sensitivity. FT2: 32 is too generous. The authors' 'better than FT4' claim is about speed (3.75 s periods, half of FT4), not sensitivity: the FT2 technical document specs decode threshold at −12/−13 dB vs FT4's −17.5 and FT8's −21 (same 2.5 kHz reference). Scaled to our table (FT8 34, FT4 30) that puts FT2 at ~25 — below FT4, not between FT4 and FT8. Same value in both mirror tables, please.

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>
@accius

accius commented Jul 28, 2026

Copy link
Copy Markdown
Owner

@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.

@accius
accius merged commit 0cba692 into accius:Staging Jul 28, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants