from __future__ import annotations import numpy as np from galileo_rx.navigation.e1b_prompt_stream import E1BPromptEpoch from galileo_rx.navigation.e1b_symbol_sync import E1BSymbolSynchronizer def _epoch(index: int, sign: int, *, slip: bool = False) -> E1BPromptEpoch: start = index * 16 prompt = sign * np.exp(1j * 0.37) return E1BPromptEpoch( 1, start, start + 16, float(prompt.real), float(prompt.imag), 0.0, 1000.0, 0.0, 50.0, True, True, slip, False, True, ) def test_symbol_sync_preserves_global_polarity_ambiguity() -> None: truth = np.asarray([1, -1] * 12, dtype=np.int8) synchronizer = E1BSymbolSynchronizer(minimum_axis_epochs=4, minimum_confidence=0.9) symbols = synchronizer.process_many([_epoch(i, int(-value)) for i, value in enumerate(truth)]) hard = np.asarray([item.hard_value for item in symbols[4:]], dtype=np.int8) # Either polarity is valid here; page synchronization resolves it later. expected_bits = (truth[4:] > 0).astype(np.int8) assert np.array_equal(hard, expected_bits) or np.array_equal(hard, 1 - expected_bits) assert all(not item.inverted for item in symbols) def test_cycle_slip_invalidates_and_restarts_axis_lock() -> None: sync = E1BSymbolSynchronizer(minimum_axis_epochs=3, minimum_confidence=0.1) symbols = [sync.process(_epoch(i, 1, slip=i == 5)) for i in range(10)] assert symbols[5].valid is False assert symbols[6].valid is False assert symbols[7].valid is True assert sync.loss_count == 1