Conditional handover for LEO: time-to-exit estimation and the full standardized NTN trigger set
Toolkit · Install · Examples · Docs
A LEO satellite is a cell that leaves. Terrestrial handover logic asks which neighbour looks best right now, which in orbit is a question that answers itself wrongly: the best-looking neighbour is often the one about to set. This module estimates time to exit, how long a candidate will still be serviceable, from real orbit propagation, and conditions the handover decision on it.
The payoff is measurable and it is not subtle. Over ten seeds on a 780 km shell, a TTE-aware policy reaches 83.14% handover success on 134.6 handovers per run with no ping-pong at all, against an A3 RSRP baseline that spends 463.3 handovers to reach 69.16% with a 50.23% ping-pong rate. A location-based policy scores higher on raw success, 98.69%, and pays 57.07% ping-pong for it.
The standardized trigger set is implemented rather than approximated: TS 38.331 CondEvent A3, D1 on a fixed reference location, T1 on its absolute broadcast epoch, and Rel-18 D2 on a moving ephemeris reference, plus the elevation and timing-advance mechanisms TR 38.821 studies. Each evaluates its own condition against live geometry, and they fire at different times because the conditions are different: on the shipped two-cell pass, D2 and TA at 8 s, D1 at 69 s, A3 at 81 s, T1 at 125 s, and elevation at 248 s, when the serving satellite finally reaches the 10 degree floor.
What a firing trigger does not always do is perform the cell change. ntn-cho-handover-traffic reports trigger-decided and fallback-decided handovers separately, and on that pass five of the six classes fire without deciding a handover, because the vendored NR A3-RSRP and X2 machinery underneath has already moved the terminal by the time they trip. Only D2 decided one. The counts are reported apart rather than summed so that the CHO layer is never credited with a cell change the radio would have made on its own.
Inside the toolkit, where the module is already present and built:
./ns3 run "ntn-cho-real-stack --trigger=d2 --duration=60"
./ns3 run "ntn-cho-full-constellation --algorithm=tte-aware --simTime=600"Standalone, into an existing ns-3.43 tree:
git clone -b main https://github.com/Muhammaduazir69/ntn-cho-framework.git contrib/ntn-cho
./ns3 configure --enable-modules='' --enable-examples --enable-tests
./ns3 buildINSTALL.md in this directory carries the full dependency list. Most examples in
this module build on ntn-traffic, the toolkit's real-stack spine, so the
toolkit tree is the path of least resistance.
- Required: the SNS3
satellitemodule —NtnOrbitPredictor/NtnTteEstimatoruse itsSatSGP4MobilityModeland antenna-gain patterns for SGP4 propagation and beam geometry. - Examples: the real-stack examples (
ntn-cho-leo-basic,ntn-cho-handover-traffic,ntn-cho-real-stack) additionally use the sibling toolkit modulesntn-traffic(NtnRealStackHelper— real mmwave NR NTN cell with measured traffic) andntn-constellation(Sgp4MobilityModel+WalkerConstellation), plus the in-treemmwaveandltestacks.ntn-cho-full-constellationusesNtnRealisticTrafficHelperfromntn-traffic; in the standalone App Store package this helper is vendored into the module (self-contained). - Optional: the
ns3-aimodule, only forNtnAiInterface(the learning-based path). The C++ triggers build and run without it.
ntn-cho implements 3GPP Release-17 Conditional Handover (CHO) for Non-Terrestrial Networks (NTN), with a focus on LEO satellite constellations where rapid beam-coverage changes drive frequent, often premature, handovers. The module adds a Time-to-Exit (TTE)-aware candidate selection that admits a target beam only when it will stay in coverage long enough to be worth the switch. Alongside the TTE-aware novelty it implements the NTN handover trigger classes with precise standards positioning — Rel-17 normative CondEvents measurement-based A4-style (event A3 baseline), location-based (CondEventD1) and time-based (CondEventT1, ephemeris-scheduled), the Rel-18 CondEventD2 (distance with MOVING ephemeris-derived reference locations, TS 38.331 §5.5.4.15a), and the TR 38.821 §6-studied elevation-based and timing-advance-based mechanisms (studied, not standardized CondEvents) — plus two forward-looking mechanisms: Rel-19 conditional LTM (L1-filtered measurements with a MAC-CE-style fast cell switch) and trajectory-predictive CHO (forecast serving outage, maximum predicted time-of-stay), with optional RACH-less execution from ephemeris/GNSS TA pre-compensation. It is built around a 3GPP-aligned CHO state machine, an orbit/beam predictor, and a 3GPP TR 38.811 NTN measurement model so that handover decisions fall out of live geometry rather than hardcoded scripts.
Honest scope of the signaling layer: the module models CHO at the decision/timing level. The radio underneath runs the mmwave ideal RRC (UseIdealRrc=true, bearers set up synchronously) with no over-the-air conditional-reconfiguration PDU exchanged, and the RACH is latency accounting — the handover interruption is priced as slant-RTT + processing (2·d/c pre-compensation when RACH-less), not a real PRACH / Msg1–4 procedure. The trigger classes and counters (GetMechanismStats()) are exact and measured; the lower-layer handover protocol is abstracted. The core network is the LTE EPC (MME/SGW/PGW, S1-AP, real GTP-U), not a 5GC.
See the CHANGELOG.
- Six NTN trigger classes in
NtnChoAlgorithm(Rel-17 A3/D1/T1 + Rel-18 D2 + TR 38.821-studied elevation/TA;combineWithA4enforces the Rel-17 rule that T1/D1/D2 are configured together with the A4 measurement leg):TRIGGER_EVENT_A3,TRIGGER_LOCATION_D1,TRIGGER_TIME_T1(CondEventT1 handover window from the serving cell's remaining time-of-service),TRIGGER_ELEVATION(serving elevation belowelevationMinDeg, candidate above floor +elevationHystDeg, elevation derived from the ephemeris/GNSS slant range atorbitAltitudeKm), andTRIGGER_TIMING_ADVANCE(serving TA abovetaServingMax, or a candidate at leasttaAdvantagelower). For these classes the admitted set already encodes the standardized condition, so selection takes the strongest measured candidate (max SINR). - Real-stack examples —
ntn-cho-leo-basic,ntn-cho-handover-trafficand the newntn-cho-real-stackrun on a real mmwave NR NTN cell (NtnRealStackHelper: SpectrumPhy + MAC + HARQ + RLC/PDCP + RRC + EPC) with SGP4 Walker satellite orbits and 3GPP TR 38.811 §6.1.1.1 class UE mobility; the serving SINR is measured off the mmwave PHY trace, not closed-form. NtnTr38811MobilityModel— the TR 38.811 UE classes are now a real ns-3MobilityModel(ECEF, SGP4-compatible frame), so the per-class motion drives the radio stack, Doppler, and the TTE estimator directly.- Standards-validation test suite (
ntn-standards-validation) — checks the mobility architecture against orbital theory and published NTN figures (see Build, run & test below). - Doppler is SIGNED in the measurement model — the shift flips from positive to negative across a LEO pass, so approaching vs. receding geometry is modelled correctly.
- CSV sentinel hygiene — no
serving_sat=4294967295orsinr=-100sentinel values leaking intoue_tracks,handover_events, orkpi_timeseries;avg_sinraverages only currently-served UEs; the first-handovertime_of_stayis no longer inflated. - TTE refinement fix — the binary-search refinement in
NtnTteEstimator::FindBeamExitTimenow propagates the satellite through time viaGetBeamSnapshotAtTime(...,tMid)at each bracket midpoint, exactly as the coarse search and the siblingFindDistanceExitTimedo. Previously the refinement evaluated a frozenNow()gain, so the bracket never crossed the threshold and the result collapsed to the coarse-grid granularity; the Time-to-Exit is now a genuinely refined exit time on the real propagated SGP4 geometry. - GeoJSON writers fixed in
ntn-cho-full-constellation—satellite_positions.geojsonnow carries the full serving-satellite track (oneFeatureper timestep; it was previously written once, leaving a single point) andbeam_footprints.geojsonis now populated with the serving beam's ground point and a 3 dB spot radius per timestep (it was an emptyFeatureCollection).
Model (model/):
NtnChoAlgorithm(ntn-cho-algorithm.h) — 3GPP Rel-17 CHO algorithm with TTE-aware candidate selection and theCHO_IDLE → CHO_PREPARED → CHO_CONDITION_MONITORING → CHO_EXECUTING → CHO_COMPLETEDstate machine. Trigger types:TRIGGER_EVENT_A3,TRIGGER_LOCATION_D1,TRIGGER_TIME_BASED,TRIGGER_TTE_AWARE,TRIGGER_THZ_BEAM_QUALITY,TRIGGER_LTM_CONDITIONAL(Rel-19 conditional LTM),TRIGGER_TRAJECTORY_PREDICTIVE(PCHO), and the standardized NTN classesTRIGGER_TIME_T1,TRIGGER_ELEVATION,TRIGGER_TIMING_ADVANCE.ChoConfigcarries the per-class parameters (t1WindowDuration,elevationMinDeg/elevationHystDeg,orbitAltitudeKm,taServingMax/taAdvantage, the LTM/PCHO knobs, andrachLess+rachDuration/choExecutionDelayfor RACH-less execution);GetMechanismStats()reports LTM switches, PCHO triggers, RACH-less vs RACH executions, and per-handover interruption.NtnTteEstimator(ntn-tte-estimator.h) — estimates Time-to-Exit for satellite beam coverage, per-candidate and in batch. A coarse forward scan brackets the beam-exit instant and a binary-search refinement (FindBeamExitTime/FindDistanceExitTime) narrows it, both propagating the satellite through time withGetBeamSnapshotAtTimeso the TTE is computed on the real SGP4 beam geometry rather than a frozenNow()snapshot.NtnOrbitPredictor(ntn-orbit-predictor.h) — predicts satellite/beam positions and coverage over time and reports visible satellites and best beams per UE position.NtnMeasurementModel(ntn-measurement-model.h) — computes RSRP/SINR from satellite beams using the 3GPP TR 38.811 NTN channel scenarios.NtnTr38811MobilityModel(ntn-tr38811-mobility-model.h) — the 3GPP TR 38.811 §6.1.1.1 NTN UE classes (handheld static/pedestrian, vehicular, HST, maritime, aviation, fixed IoT) as a real ns-3MobilityModelin ECEF, the same frame as the satellite side's SGP4 models; includes thentngeogeometry utilities (geodetic↔ECEF, elevation, slant range) andNtnTr38811MobilityHelperfor installing UE populations.NtnAiInterface(ntn-ai-interface.h) — ns3-ai shared-memory bridge exposing a candidate-cell observation/action space for AI-driven handover decisions.
Helper (helper/):
NtnChoHelper(ntn-cho-helper.h) — top-level helper that wires up a CHO scenario (channel scenario, trigger type, carrier frequency) and reports aggregated KPI results.NtnRealisticMobilityHelper(ntn-realistic-mobility.h) — generates UE populations with realistic per-class motion following the seven 3GPP TR 38.811 §6.1.1.1 NTN UE classes, with built-in scenario profiles (NtnMobilityScenarios).
All five examples build under build/contrib/ntn-cho/examples/. Each can be launched either through ./ns3 run or directly via the built binary with LD_LIBRARY_PATH=build/lib.
Smoke test for the CHO algorithm on a real mmwave NR NTN cell (NtnRealStackHelper): one SGP4 serving satellite, TR 38.811 class UEs under its sub-point, real UDP traffic over the radio, and the CHO algorithm exercised on a 200 ms cadence with the measured per-UE SINR from the mmwave PHY trace.
./ns3 run "ntn-cho-leo-basic --trigger=tte-aware --simTime=12 --numUes=4"
LD_LIBRARY_PATH=build/lib ./build/contrib/ntn-cho/examples/ns3.43-ntn-cho-leo-basic-default --trigger=tte-aware --simTime=12 --numUes=4Outputs: sim_health.csv (written via NtnRealStackHelper::WriteHealthReport()) in --outputDir, plus the measured mean SINR and throughput on stdout.
Key args: simTime, trigger (a3|location|tte-aware), tteMinimum, numUes, satEirpDbm, outputDir.
Full Walker constellation NTN-CHO run: multi-beam satellites, proper initial serving assignment, calibrated TTE values and a realistic HO-failure model, with the four algorithms (a3 / location / time / tte-aware) selectable for comparison. A real UDP data plane (via NtnRealisticTrafficHelper) runs alongside the constellation-scale measurement loop.
./ns3 run "ntn-cho-full-constellation --algorithm=tte-aware --numUes=50 --outputDir=/tmp/ntn-full"
LD_LIBRARY_PATH=build/lib ./build/contrib/ntn-cho/examples/ns3.43-ntn-cho-full-constellation-default --algorithm=tte-aware --numUes=50 --outputDir=/tmp/ntn-fullOutputs (in --outputDir): handover_events.csv, measurements.csv, tte_computations.csv, satellite_tracks.csv, ue_tracks.csv, kpi_timeseries.csv, kpi_summary.txt, the GeoJSON layers (satellite_positions.geojson, ue_positions.geojson, beam_footprints.geojson, handover_events.geojson), and sim_health.csv (via NtnRealisticTrafficHelper). The satellite-position, UE-position and beam-footprint layers carry one Feature per timestep. handover_events.geojson is legitimately an empty FeatureCollection when the run's geometry produces no handover (e.g. the serving satellite stays at high elevation for the whole short window) — that is correct behaviour, one Feature per handover when they occur, not a stub.
Key args: simTime, numUes, scenario, algorithm (a3|location|time|tte-aware), d1Threshold, qualityTh, tteMinimum, outputDir, rngRun, verbose, numPlanes, satsPerPlane, trafficUes (UEs carrying the real UDP plane; 0 = all).
Real UDP downlink to TR 38.811 UEs on a real mmwave NR NTN cell, handed over by the actual NtnChoAlgorithm while the constellation flies real SGP4 Walker orbits: the serving satellite passes zenith and recedes, the in-plane neighbour approaches, and the handover falls out of the genuine orbital crossover. The serving SINR fed to the algorithm is measured from the PHY; the candidate SINR is ephemeris-predicted (measured baseline plus the real Friis slant-range ratio). All nine trigger mechanisms are selectable — including Rel-17 A3/D1/T1, Rel-18 D2 and the TR 38.821-studied elevation/TA classes.
./ns3 run "ntn-cho-handover-traffic --simSeconds=60 --trigger=elevation"
./ns3 run "ntn-cho-handover-traffic --trigger=t1 --rachLess=1"
LD_LIBRARY_PATH=build/lib ./build/contrib/ntn-cho/examples/ns3.43-ntn-cho-handover-traffic-default --simSeconds=60 --trigger=taOutputs: sim_health.csv in --outputDir, per-handover lines on stdout (measured serving SINR, predicted candidate SINR, interruption), and a summary line with the measured serving-cell goodput across the handover, mean SINR, SINR at handover, last interruption, and RACH-less execution count.
Key args: simSeconds, numUes, leoAltKm, freqGHz, satEirpDbm, tteMinSec, trigger (tte-aware|ltm|pcho|a3|d1|t1|elevation|ta), rachLess, satsPerPlane, outputDir.
Real-stack flagship: the TTE-aware CHO (or Rel-19 LTM / trajectory-predictive PCHO) decides on the measured mmwave SINR while serving and candidate satellites fly real SGP4 Walker-Delta orbits and the UEs move under TR 38.811 class mobility. The summary reports the mechanism counters (GetMechanismStats()): LTM fast switches, PCHO trajectory triggers, RACH-less vs RACH executions, last interruption, and the last ephemeris-pre-computed TA.
./ns3 run "ntn-cho-real-stack --duration=60 --trigger=pcho --rachLess=1"
LD_LIBRARY_PATH=build/lib ./build/contrib/ntn-cho/examples/ns3.43-ntn-cho-real-stack-default --duration=60 --trigger=pcho --rachLess=1Outputs: sim_health.csv in --outputDir and the CHO/mechanism summary on stdout (measured mean SINR, measured DL throughput, evaluations, handovers, mechanism counters).
Key args: duration, numUes, altitude, satEirpDbm, freqGhz, tteMin, trigger (tte-aware|ltm|pcho), rachLess, satsPerPlane, outputDir.
Demonstrates the per-class realistic mobility generator: spawns one UE per 3GPP TR 38.811 §6.1.1.1 class and writes its trajectory to CSV for inspection/plotting.
./ns3 run "ntn-realistic-mobility-demo --outputDir=/tmp/mob_demo --simTime=600"
LD_LIBRARY_PATH=build/lib ./build/contrib/ntn-cho/examples/ns3.43-ntn-realistic-mobility-demo-default --outputDir=/tmp/mob_demo --simTime=600Outputs: mobility_trace.csv in --outputDir.
Key args: outputDir, simTime, dt, rngRun.
./ns3 configure --enable-examples --enable-tests && ./ns3 build
./test.py -s ntn-cho
./test.py -s ntn-standards-validationTwo test suites ship with the module:
ntn-cho— unit tests for the CHO algorithm, the CHO state machine, and the NTN measurement model.ntn-standards-validation— validates the mobility architecture against orbital theory and published NTN figures: SGP4 propagation vs Keplerian theory (orbital radius, speedsqrt(mu/a), quarter-period arc at 550 km / 53°), the zenith-pass Doppler envelope (Doppler null at culmination, S-band shift inside the published LEO envelope), ENU pass geometry (culmination at zenith, monotonic elevation decay), and the spherical-Earth elevation/slant relation used by the elevation trigger — including the TR 38.821 LEO-600 reference point (~1932 km slant at 10° elevation).
See INSTALL.md for full setup.
@misc{uzair2026ntncho,
author = {Muhammad Uzair},
title = {ntn-cho: Time-to-Exit-Aware Conditional Handover for
Non-Terrestrial Networks in ns-3},
year = {2026},
note = {ns-3 App Store module, v1.0.0. ORCID 0009-0002-4104-2680}
}3GPP TS 38.331 (conditional reconfiguration, CondEvent A3, A4, D1, D2, T1, SIB19), TS 38.300 (handover procedure), TS 38.133 (measurement reporting), TS 38.321 (random access), TR 38.821 (NTN mobility, handover interruption budget, Set-1 reference parameters), TS 38.423 (Xn handover preparation).
conditional handover, CHO, LEO satellite handover, time-to-exit, TTE, NTN mobility management, CondEventD2, CondEventT1, CondEventA3, moving reference location, ephemeris trigger, SGP4, orbit propagation, ping-pong handover, handover interruption time, RACH-less handover, Rel-17 NTN, Rel-18 NTN, ns-3, satellite communications.
Muhammad Uzair, Independent Researcher ORCID 0009-0002-4104-2680
Part of the ns3-ntn-toolkit, a pre-integrated ns-3.43 platform for 6G non-terrestrial network research. Mirrored on GitLab.
GPL-2.0-only, matching ns-3.