clone

a desktop tv and program guide for over-the-air broadcasts: what's on air, what your tuner gets, live tv, recordings

main
airwaves / internal / reception / reception_test.go
reception_test.go 2.6K
1package reception23import (4 "math"5 "testing"67 "airwaves/internal/terrain"8)910func flat(km, elev float64, n int) terrain.Profile {11 e := make([]float64, n)12 for i := range e {13 e[i] = elev14 }15 return terrain.Profile{DistanceKm: km, Elevations: e}16}1718var rooftop = Presets[2]1920func TestLineOfSightIsFreeSpace(t *testing.T) {21 // A 1000 kW UHF tower 730 m above a flat plain 21 km away, like22 // Lookout Mountain seen from downtown Denver.23 tx := Transmitter{RFChannel: 35, ERPkW: 1000, RCAMSLm: 2340}24 e := Predict(tx, flat(21, 1609, 200), rooftop)25 if !e.LineOfSight || e.DiffractionDB != 0 {26 t.Fatalf("expected a clear path, got %+v", e)27 }28 fspl := 32.45 + 20*math.Log10(21) + 20*math.Log10(599)29 if math.Abs(e.PathLossDB-fspl) > 0.1 {30 t.Errorf("path loss %.1f, want free space %.1f", e.PathLossDB, fspl)31 }32 if e.Tier != Strong {33 t.Errorf("tier %s, want strong", e.Tier)34 }35}3637func TestRidgeAddsDiffraction(t *testing.T) {38 tx := Transmitter{RFChannel: 25, ERPkW: 1000, RCAMSLm: 1900}39 p := flat(100, 1600, 300)40 // A 600 m ridge two thirds of the way along the path.41 for i := 190; i < 210; i++ {42 p.Elevations[i] = 220043 }44 clear := Predict(tx, flat(100, 1600, 300), rooftop)45 blocked := Predict(tx, p, rooftop)46 if blocked.LineOfSight {47 t.Error("ridge path reported line of sight")48 }49 if blocked.DiffractionDB < 15 {50 t.Errorf("diffraction %.1f dB, want a substantial loss", blocked.DiffractionDB)51 }52 if blocked.NoiseMarginDB >= clear.NoiseMarginDB {53 t.Errorf("ridge did not reduce margin: %.1f vs %.1f", blocked.NoiseMarginDB, clear.NoiseMarginDB)54 }55}5657func TestPresetsOrderIndoorWorst(t *testing.T) {58 tx := Transmitter{RFChannel: 9, ERPkW: 45, RCAMSLm: 2360}59 p := flat(23, 1640, 200)60 in, attic, roof := Predict(tx, p, Presets[0]), Predict(tx, p, Presets[1]), Predict(tx, p, Presets[2])61 if !(in.NoiseMarginDB < attic.NoiseMarginDB && attic.NoiseMarginDB < roof.NoiseMarginDB) {62 t.Errorf("margins not ordered: indoor %.1f attic %.1f rooftop %.1f", in.NoiseMarginDB, attic.NoiseMarginDB, roof.NoiseMarginDB)63 }64}6566func TestTierThresholds(t *testing.T) {67 for m, want := range map[float64]Tier{25: Strong, 20: Strong, 12: Good, 5: Fair, 0: Weak, -6: Unlikely} {68 if got := tierFor(m); got != want {69 t.Errorf("tierFor(%v) = %s, want %s", m, got, want)70 }71 }72 if !Fair.Receivable() || Weak.Receivable() {73 t.Error("Receivable cutoff should sit between fair and weak")74 }75}7677func TestKnifeEdge(t *testing.T) {78 if knifeEdgeDB(-1) != 0 {79 t.Error("clear path should have no loss")80 }81 // Grazing incidence is about 6 dB.82 if l := knifeEdgeDB(0); math.Abs(l-6) > 0.1 {83 t.Errorf("J(0) = %.2f, want about 6", l)84 }85}