Skip to content

cell M6: an emulated crossing (DU TX-power ramps), there and back, and a hover — nothing forced - #539

Merged
josephnef merged 5 commits into
masterfrom
feat/m6-crossing
Nov 6, 2026
Merged

josephnef merged 5 commits into
masterfrom
feat/m6-crossing

Conversation

@josephnef

Copy link
Copy Markdown
Collaborator

M6 (#260, EPIC #254): the first handovers on the rig with nothing forced. A UE crosses from one cell to the other and back, and the CU's A3 decides at its default thresholds. This closes the "no crossing test" item.

What

  • DU, test knob CELL_DU_TX_RAMP=T:DB,…: the DU's TX power offset, piecewise linear in seconds from CELL_DU_TX_RAMP_T0 (unix seconds, so the DUs of one test ramp together; the hosts are NTP-synced) or from service start, and held at the ends. It is applied in 0.5 dB steps via SetTxPowerOffsetQdb and logged as cell.du.txpwr (wanted vs applied). Beacons and DL follow it.
  • UE: logs each measurement report it builds (cell.ue.mr: serving cell, sf, RSSI per cell).
  • Docs: docs/cell-m6.md gains a section on the emulated crossing. It replaces the two open items ("a moving UE", "no crossing test") with an honest limit: emulated by TX power, uplink path loss unchanged, one UE, one ramp rate.

Rig (testrig scripts/m6_crossing.sh, keyed, one channel, UE1 only)

Cell 1 fades by 16 dB over 15 s while cell 2 rises, they hold, then reverse. The UE's measured margin sweeps from about −13 to +24 dB and back. The A3 runs at its defaults (3 dB offset, 1 dB hysteresis, 640 ms TTT, 2 s dwell).

Crossing 1 → 2 → 1, 3/3 pass: exactly two handovers per run, both triggered by the A3 on the UE's own measurements.

KPI (6 handovers) min median max
UE margin reaching the A3 entry (4 dB) → switch 38 sf 41.5 sf 51 sf
margin at the CU's trigger 7 dB 9.5 dB 13 dB
interruption 1 sf 1 sf 2 sf
uplinks missing at the CU (DAPS) 0 0 0
  • Where the time goes: 24 sf of crossing → switch is the handover lead. The rest is the TTT plus the 1 s reporting chain (UE MR, DU F1 report, CU round). At about 2 dB/s of margin, that chain is why the trigger lands 3–9 dB past the entry.
  • Each switch passes the TSF-continuity check, is heard in slot ≥ 99 % at the target, and is released by the source.

Hover just under the entry, 3/3 pass: median margin +3 dB (p10–p90 +2 to +4), with 20–23 % of the UE's reports at or above the entry. No handover in any run.

Found on the way: the power-to-RSSI relation isn't linear on the rig. −8 dB of offset reads about −9 dB at the UE, and −9 dB about −12 dB. The cause is not established. The tests judge by the UE-measured margins, not the offsets.

🤖 Generated with Claude Code

…over — nothing forced

Every earlier handover had a forced trigger or a standing difference. Now
the UE crosses: CELL_DU_TX_RAMP (test) ramps a DU's TX power offset
piecewise linear on a shared wall-clock zero (CELL_DU_TX_RAMP_T0), beacons
and DL alike (cell.du.txpwr); the UE logs its measurement reports
(cell.ue.mr). The CU's A3 at its defaults decides.

Rig (testrig scripts/m6_crossing.sh, keyed, one channel, one UE):
- crossing 1 -> 2 -> 1, 3/3: exactly two handovers per run, from the UE's
  own measurements; UE margin at the A3 entry -> switch 38-51 sf (24 the
  lead), trigger margin 7-13 dB (the 1 s reporting chain at ~2 dB/s),
  interruption 1-2 sf, 0 uplinks missing (DAPS); TSF continuity, target in
  slot, source released.
- hover just under the entry (median +3 dB, 20-23 % of reports at/above
  it), 3/3: no handover.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Emulate unforced cell crossings with DU TX-power ramps

✨ Enhancement 📝 Documentation 🕐 20-40 Minutes

Grey Divider

AI Description

• Add synchronized DU TX-power ramps to emulate a UE crossing between cells without forcing
 handovers.
• Log UE measurement reports and DU power changes to trace the A3 decision.
• Document crossing and hover rig results, plus the limits of TX-power-based emulation.
Diagram

graph TD
  Schedule["Ramp schedule"] --> DU1["Cell 1 DU"] --> UE["UE measurements"] --> Reports["Measurement reports"] --> A3{"CU A3 decision"} --> Handover["Cell handover"]
  Schedule --> DU2["Cell 2 DU"] --> UE
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Inject synthetic UE measurements
  • ➕ Could make crossing cases deterministic without synchronized DU hosts.
  • ➖ Would bypass beacon reception and the radio power-to-RSSI behavior this rig run exercises.
2. Move the UE or use a channel emulator
  • ➕ Could model uplink path loss and over-the-air fading as well as downlink changes.
  • ➖ Requires additional rig hardware or motion control and is beyond this test's stated scope.

Recommendation: Keep the DU power-ramp approach for this first unforced crossing test: it exercises real UE measurements and the existing A3 path on the current rig. Retain the documented limitation that changing downlink transmit power does not emulate a moving UE's uplink path loss.

Files changed (2) +128 / -7

Enhancement (1) +77 / -0
main.cppRamp DU transmit power and log UE measurement reports +77/-0

Ramp DU transmit power and log UE measurement reports

• Parses test-only, piecewise-linear DU power offsets with an optional shared Unix start time, applies them in 0.5 dB steps, and logs requested versus applied power. Logs each nonempty UE measurement report's serving cell, superframe, and per-cell RSSI.

examples/cell/main.cpp

Documentation (1) +51 / -7
cell-m6.mdDocument unforced crossing and hover rig results +51/-7

Document unforced crossing and hover rig results

• Adds the two-cell ramp setup, measured crossing and hover outcomes, and an explanation of reporting-chain delay. Replaces open crossing-test items with explicit limits of the emulation, including unchanged uplink path loss.

docs/cell-m6.md

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Handover rig results omit the source-path failure test ✓ Resolved
Description
The M6 section reports only the crossing and hover runs, so the DAPS results are uplinks-missing
counts during a healthy crossing, not a source-path failure. The ticket's rule requires a
source-path failure run that shows uplinks delivered via the target when the source fails, plus a
reported DAPS gain. The PR text says the crossing 'closes the no crossing test item' but says
nothing about a failure case, so that part of the validation is unaddressed.

2. Ramp silently skips on a mistyped anchor ✓ Resolved
Description
ramp_t0_unix is parsed with std::atof from CELL_DU_TX_RAMP_T0, so a malformed or non-numeric
value becomes 0 and the code falls back to service-start timing with no warning. DUs meant to share
one ramp zero would then ramp out of phase, misaligning the crossing the test is meant to measure.

3. Refused power writes are never retried ✓ Resolved
Description
After SetTxPowerOffsetQdb(want_qdb) returns, ramp_set_qdb is set to want_qdb regardless of the
return value, so a refused write is not retried until the target moves by another 0.5 dB. The ramp
then stays off on that DU for the rest of the run, and the only trace is a cell.du.txpwr line with
applied_qdb different from want_qdb that no check reads.


Informational

4. Hover criterion looser than its claim ✓ Resolved
Description
The docs say the hover runs had no handover, but the same paragraph says the test allows one. The
stated pass criterion is therefore looser than the reported result, so a regression that produces
one spurious handover would still pass.

Grey Divider

Tip of the day
💡 Did you know, you can switch off images and animations for a plain-text comment

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

…p the crossing found), source failure mid-crossing, strict ramp knobs

- CU bug found by the crossing: it planned from the DUs' REPORTED positions,
  which lag a schedule by RAN_LEAD_SF (> a plan round). With two equally good
  layouts it re-issued them in turn every round, each swap an uplink in the
  old slot (target in slot 91-98 % after a switch). It now plans each UE from
  its own issued schedule (the report only for a UE the schedule does not
  name: a handover arrival). After: 8 schedules a run, every switch in slot.
- CELL_DU_TX_RAMP_T0 parsed strictly (strtod, > 0) or the DU refuses; the
  anchor used is logged (cell.du.txpwr_t0); a ramp on a radio without TX
  power control is refused; a clamped write warns.
- docs: the source-failure crossing (DAPS 0/0 missing in the crossing window
  vs 11/13 without), the final-code KPIs, the hover's criterion and result
  stated apart, the CU bug.

Rig (testrig m6_crossing.sh, final code): crossing 3/3, hover 3/3 (0
handovers), source failure with DAPS 2/2 (0 missing in the window).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@josephnef

Copy link
Copy Markdown
Collaborator Author

Qodo review addressed in 2790f8b:

  1. Source-path failure (requirement gap): added to the crossing test. DU1 is killed the moment its UE accepts the handover, mid-crossing, with DAPS and without. Uplinks missing from the CU's stream in the crossing window (switch −24 … +20 network sf): 0 / 0 with DAPS vs 11 / 13 without (2 + 2 runs). The standalone DAPS result (m6_daps.sh, source killed, 0/0 vs 11/16 keyed) stays in its own section.
  2. Mistyped anchor: CELL_DU_TX_RAMP_T0 is parsed with strtod and an end-pointer check, and must be > 0, or the DU refuses (exit 2). The anchor used is logged as cell.du.txpwr_t0.
  3. Refused power writes: a ramp on a radio without runtime TX power control (GetTxPowerCaps().supported false) is refused at start. On Kestrel the call never refuses, it only clamps to the PA window and returns the applied value, so a retry of the same target cannot change it: a clamp now warns once on stderr, and every step logs wanted vs applied.
  4. Hover wording: the doc now states the criterion (at most one handover, never back) and the result (0 handovers in all 3 runs) separately.

Also, a CU bug the re-runs exposed: after a switch the target heard the UE in slot only 91–98 %. The CU planned from the DUs' reported positions. Those lag a schedule by RAN_LEAD_SF, which is more than a plan round, so with two equally good layouts it re-issued them in turn every round, one off-slot uplink per swap. It now plans from its own issued schedule. After the fix the crossing set is 3/3, the hover 3/3 and the source failure 2/2, all on the final code; a coordination/handover/DAPS/phase-planning/re-join regression is running.

…gger, seen once) but never a switch back

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@josephnef

Copy link
Copy Markdown
Collaborator Author

On hover criterion looser than its claim: the doc now gives the result (0 handovers in all 3 runs) and the reason the criterion is 'at most one, never back' rather than zero. At a +3 dB hover, the L3-filtered margin can legitimately hold the 4 dB entry for the time-to-trigger. One hover run of the earlier set did exactly that, once, and never switched back. A zero-handover assertion would fail correct A3 behaviour. What the test must catch is ping-pong (a switch back), and it fails on that. (8cf378f)

josephnef and others added 2 commits November 6, 2026 23:18
…e KPI

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@josephnef

Copy link
Copy Markdown
Collaborator Author

CI green; all four Qodo findings resolved. The remaining items are the two high-level alternatives (synthetic UE measurements; moving the UE or a channel emulator), for which Qodo recommends this PR's DU power-ramp approach, with the documented limit that it does not emulate the uplink path loss. Merging with skip-qodo-gate.

@josephnef josephnef added the skip-qodo-gate Bypass the Qodo review gate (outage / maintainer decision) label Nov 6, 2026
@josephnef
josephnef merged commit 408e03f into master Nov 6, 2026
27 of 28 checks passed
@josephnef
josephnef deleted the feat/m6-crossing branch November 6, 2026 22:08
@josephnef josephnef mentioned this pull request Nov 6, 2026
3 of 5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

skip-qodo-gate Bypass the Qodo review gate (outage / maintainer decision)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant