<?xml version="1.0" encoding="utf-8" ?>
<FXT>

  <!-- =============================================================== -->
  <!-- Reusable base: MAC-NR per-TTI scheduling context.               -->
  <!--                                                                 -->
  <!-- Every MAC-NR-anchored template (NR-RRC procedures, MAC CE,      -->
  <!-- PDCP-NR, RLC-NR, generic UL/DL-SCH fallback, RAR) extends this  -->
  <!-- so NDJSON / HTML / PDF readers can correlate SFN, slot, RNTI    -->
  <!-- type, UE id, HARQ process id, and FDD/TDD duplex without        -->
  <!-- dropping to tshark. Params for fields absent from a given       -->
  <!-- capture are silently skipped — e.g. mac-nr.sfn / mac-nr.slot    -->
  <!-- are only emitted when the capture carries the NR-FRAME-SLOT     -->
  <!-- framing tag (typical of srsRAN_Project gNB pcaps; phone         -->
  <!-- diag-interface captures often omit them).                       -->
  <!--                                                                 -->
  <!-- Param ordering matters: SFN is listed first because the PDF     -->
  <!-- backend caps params per message (default max_params = 15), so   -->
  <!-- the leading entries are the ones guaranteed to survive          -->
  <!-- truncation. NDJSON / HTML output keeps the full list regardless.-->
  <!--                                                                 -->
  <!-- SFN intentionally lives only as a param, not in the remark:     -->
  <!-- mac-nr.sfn is 10 bits → wraps every 10.24 s, so `[SFN N]` in    -->
  <!-- the lightbulb is ambiguous on any capture longer than one wrap  -->
  <!-- window. The remark uses frame.number, which is monotonic and    -->
  <!-- the identifier you'd click to jump back to the packet in        -->
  <!-- Wireshark. See github.com/eventhelix/visualether-rs#174 for the -->
  <!-- multi-field-remark proposal that would let us include both.     -->
  <!--                                                                 -->
  <!-- The NR base has no UL grant size or CRC status equivalents:     -->
  <!-- mac-nr does not register those as standalone fields the way     -->
  <!-- mac-lte does. Per-TTI grant size on NR has to be inferred from  -->
  <!-- mac-nr.subheader / sdu-length and slot timing.                  -->
  <!--                                                                 -->
  <!-- Coverage (counted 2026-05-23): all 23 <message> templates in    -->
  <!-- this file extend this base. There are no S1AP / NGAP / NAS-5GS  -->
  <!-- templates in samples/5g-nr-radio/ — those live in               -->
  <!-- samples/5g-srsran-ngap/ and samples/5g-attach/.                 -->
  <!-- =============================================================== -->
  <message-base name="mac-sched-context-nr">
    <param match="System Frame Number: (\d+)" replace="📅 SFN: $1">mac-nr.sfn</param>
    <param match="Slot: (\d+)" replace="🕒 Slot: $1">mac-nr.slot</param>
    <param match="RNTI Type: (.+) \(\d+\)" replace="🏷️ RNTI type: $1">mac-nr.rnti-type</param>
    <!-- mac-nr.ueid is the gNB's per-RRC-connection context index (it
         increments on every reconnect), NOT a device id — labelled "Conn".
         Device identity is the NAS 5G-S-TMSI / 5G-GUTI. The 📱 UE / 📡 gNB
         actor labels elsewhere are correct (it is one UE talking to the gNB). -->
    <param match="UEId: (.*)" replace="🔗 Conn: $1">mac-nr.ueid</param>
    <param match="HarqId: (.*)" replace="♻️ HARQ pid: $1">mac-nr.harqid</param>
    <param match="Radio Type: (.+) \(\d+\)" replace="📐 Duplex: $1">mac-nr.radio-type</param>
    <!-- Scheduler diagnostics: BSR LCG buffer indices (NR supports 8 LCGs
         in the Long BSR — TS 38.321 §6.1.3.1) and the full multi-LCID
         composition of each MAC PDU. Promoted into the base (2026-05-26)
         so steady-state UL-SCH / DL-SCH and PDCP frames surface the
         scheduling context without dropping to tshark, and so the same
         data appears regardless of which higher-priority template wins. -->
    <param match=".*BS = (\d+).*" replace="📊 BSR LCG0 idx: $1">mac-nr.control.bsr.bs-lcg0</param>
    <param match=".*BS = (\d+).*" replace="📊 BSR LCG1 idx: $1">mac-nr.control.bsr.bs-lcg1</param>
    <param match=".*BS = (\d+).*" replace="📊 BSR LCG2 idx: $1">mac-nr.control.bsr.bs-lcg2</param>
    <param match=".*BS = (\d+).*" replace="📊 BSR LCG3 idx: $1">mac-nr.control.bsr.bs-lcg3</param>
    <param match=".*BS = (\d+).*" replace="📊 BSR LCG4 idx: $1">mac-nr.control.bsr.bs-lcg4</param>
    <param match=".*BS = (\d+).*" replace="📊 BSR LCG5 idx: $1">mac-nr.control.bsr.bs-lcg5</param>
    <param match=".*BS = (\d+).*" replace="📊 BSR LCG6 idx: $1">mac-nr.control.bsr.bs-lcg6</param>
    <param match=".*BS = (\d+).*" replace="📊 BSR LCG7 idx: $1">mac-nr.control.bsr.bs-lcg7</param>
    <!-- Full LCID composition. Multi-LCID PDUs produce multiple field
         instances; these params emit one row per LCID so the diagram
         shows what's actually multiplexed in the MAC PDU. Direction-
         specific — UL frames populate ulsch.lcid, DL frames populate
         dlsch.lcid; the other is silently skipped. -->
    <param match=".*LCID: (.+) \((\d+)\).*" replace="📦 UL LCID: $2 ($1)">mac-nr.ulsch.lcid</param>
    <param match=".*LCID: (.+) \((\d+)\).*" replace="📦 DL LCID: $2 ($1)">mac-nr.dlsch.lcid</param>
  </message-base>

  <!-- =============================================================== -->
  <!-- NR-RRC procedure messages (priority 410-450)                    -->
  <!--                                                                 -->
  <!-- Opcode is anchored on nr-rrc.c1 (the CHOICE indicator) rather   -->
  <!-- than nr-rrc.<name>_element. The element field is emitted at two -->
  <!-- nested ASN.1 levels by Wireshark's NR-RRC dissector (CHOICE     -->
  <!-- wrapper + SEQUENCE), which would fire each template twice per   -->
  <!-- frame. nr-rrc.c1 appears exactly once and its showname carries  -->
  <!-- the RRC message name (e.g. "c1: rrcSetupRequest (0)"), so a     -->
  <!-- single match isolates the message type without the        -->
  <!-- double-fire.                                                    -->
  <!-- =============================================================== -->

  <!-- Three-tier address fallback for the pre-security RRC procedures.
       These three opcodes (Setup Request / Setup / Setup Complete) are
       the only nr-rrc.c1 values that surface visibly in bare-RLC /
       bare-PDCP captures (srsRAN's `rlc-nr-pcap` / `pdcp-nr-pcap`
       outputs) — once Security Mode Complete activates RRC ciphering,
       c1 stops being dissected, so duplicating the post-SMC templates
       would not help anyone.

       Three variants per opcode at decreasing priority:
         priority 510 — bare-PDCP: anchors on `pdcp-nr.direction`.
                        Fires on bare-PDCP captures where there is no
                        MAC or RLC layer. Note that CCCH (Setup Request
                        / Setup) doesn't go through PDCP, so those two
                        priority-510 templates effectively only fire
                        for SRB1 DCCH content (Setup Complete and
                        onward). They're still listed for symmetry.
         priority 500 — bare-RLC: anchors on `rlc-nr.direction`.
                        Fires on bare-RLC captures where the MAC layer
                        is absent. Also covers steady-state
                        mac-nr-pcap because rlc-nr.direction is set
                        there too (same value as mac-nr.direction).
         priority 450 — mac-nr-pcap RACH: original templates below,
                        anchored on `mac-nr.direction`. Fires on
                        CCCH frames carried over TM-mode RLC (no RLC
                        header → no rlc-nr.direction).

       The FXT generator evaluates higher priority first and falls
       through to the next when the source/destination address fails
       to resolve, so each capture style lands on the right tier. -->

  <!-- UL: RRC Setup Request (bare-PDCP variant) -->
  <message extends="mac-sched-context-nr" style="coral" bookmark="true" priority="510">
    <source><address match=".*Uplink.*" replace="📱 UE">pdcp-nr.direction</address></source>
    <destination><address match=".*Uplink.*" replace="📡 gNB">pdcp-nr.direction</address></destination>
    <opcode match=".*c1: rrcSetupRequest \(.*" replace="📡 RRC Setup Request">nr-rrc.c1</opcode>
    <param match="RNTI: (\S+).*" replace="🆔 RNTI: $1">mac-nr.rnti</param>
    <param match="randomValue: (\S+).*" replace="🎲 Random: $1">nr-rrc.randomValue</param>
    <param match="establishmentCause: (.+) \(\d+\)" replace="📋 Cause: $1">nr-rrc.establishmentCause</param>
    <remark match=".*Frame Number: (\d+)" replace="💡 [Frame $1] RRC Setup Request — Msg3 over CCCH; UE asks gNB to establish RRC connection">frame.number</remark>
  </message>

  <!-- DL: RRC Setup (bare-PDCP variant) -->
  <message extends="mac-sched-context-nr" style="forest-green" bookmark="true" priority="510">
    <source><address match=".*Downlink.*" replace="📡 gNB">pdcp-nr.direction</address></source>
    <destination><address match=".*Downlink.*" replace="📱 UE">pdcp-nr.direction</address></destination>
    <opcode match=".*c1: rrcSetup \(.*" replace="✅ RRC Setup">nr-rrc.c1</opcode>
    <param match="RNTI: (\S+).*" replace="🆔 RNTI: $1">mac-nr.rnti</param>
    <param match="rrc-TransactionIdentifier: (\d+)" replace="🔢 Trans ID: $1">nr-rrc.rrc_TransactionIdentifier</param>
    <param match="cellGroupId: (\d+)" replace="📐 CellGroup: $1">nr-rrc.cellGroupId</param>
    <param match="srb-Identity: (\d+)" replace="🛡️ SRB add: $1">nr-rrc.srb_Identity</param>
    <param match=".*" replace="🛠️ RLC mode: AM">nr-rrc.am_element</param>
    <param match=".*" replace="🛠️ RLC mode: UM-shortSN">nr-rrc.um_WithShortSN</param>
    <param match=".*" replace="🛠️ RLC mode: UM-longSN">nr-rrc.um_WithLongSN</param>
    <remark match=".*Frame Number: (\d+)" replace="💡 [Frame $1] RRC Setup — Msg4 payload carrying SRB1 config and master cell group; UE moves to RRC_CONNECTED on receipt">frame.number</remark>
  </message>

  <!-- UL: RRC Setup Complete (bare-PDCP variant) -->
  <message extends="mac-sched-context-nr" style="coral" bookmark="true" priority="510">
    <source><address match=".*Uplink.*" replace="📱 UE">pdcp-nr.direction</address></source>
    <destination><address match=".*Uplink.*" replace="📡 gNB">pdcp-nr.direction</address></destination>
    <opcode match=".*c1: rrcSetupComplete \(.*" replace="✅ RRC Setup Complete">nr-rrc.c1</opcode>
    <param match="RNTI: (\S+).*" replace="🆔 RNTI: $1">mac-nr.rnti</param>
    <param match="rrc-TransactionIdentifier: (\d+)" replace="🔢 Trans ID: $1">nr-rrc.rrc_TransactionIdentifier</param>
    <param match="selectedPLMN-Identity: (\d+)" replace="🌐 PLMN idx: $1">nr-rrc.selectedPLMN_Identity</param>
    <param match="Bearer Id: (\d+)" replace="🚌 SRB: $1">pdcp-nr.bearer-id</param>
    <param match="dedicatedNAS-Message: (.{16}).*" replace="📦 NAS: $1…">nr-rrc.dedicatedNAS_Message</param>
    <param match="Message type: (.+) \(0x[0-9a-fA-F]+\)" replace="📬 NAS msg: $1">nas-5gs.mm.message_type</param>
    <remark match=".*Frame Number: (\d+)" replace="💡 [Frame $1] RRC Setup Complete — UE acks SRB1 and piggybacks an initial NAS message to the AMF">frame.number</remark>
  </message>

  <!-- UL CCCH: RRC Setup Request (bare-RLC variant) -->
  <message extends="mac-sched-context-nr" style="coral" bookmark="true" priority="500">
    <source><address match=".*Uplink.*" replace="📱 UE">rlc-nr.direction</address></source>
    <destination><address match=".*Uplink.*" replace="📡 gNB">rlc-nr.direction</address></destination>
    <opcode match=".*c1: rrcSetupRequest \(.*" replace="📡 RRC Setup Request">nr-rrc.c1</opcode>
    <param match="RNTI: (\S+).*" replace="🆔 RNTI: $1">mac-nr.rnti</param>
    <param match="randomValue: (\S+).*" replace="🎲 Random: $1">nr-rrc.randomValue</param>
    <param match="establishmentCause: (.+) \(\d+\)" replace="📋 Cause: $1">nr-rrc.establishmentCause</param>
    <remark match=".*Frame Number: (\d+)" replace="💡 [Frame $1] RRC Setup Request — Msg3 over CCCH; UE asks gNB to establish RRC connection">frame.number</remark>
  </message>

  <!-- DL CCCH: RRC Setup (bare-RLC variant) -->
  <message extends="mac-sched-context-nr" style="forest-green" bookmark="true" priority="500">
    <source><address match=".*Downlink.*" replace="📡 gNB">rlc-nr.direction</address></source>
    <destination><address match=".*Downlink.*" replace="📱 UE">rlc-nr.direction</address></destination>
    <opcode match=".*c1: rrcSetup \(.*" replace="✅ RRC Setup">nr-rrc.c1</opcode>
    <param match="RNTI: (\S+).*" replace="🆔 RNTI: $1">mac-nr.rnti</param>
    <param match="rrc-TransactionIdentifier: (\d+)" replace="🔢 Trans ID: $1">nr-rrc.rrc_TransactionIdentifier</param>
    <param match="cellGroupId: (\d+)" replace="📐 CellGroup: $1">nr-rrc.cellGroupId</param>
    <param match="srb-Identity: (\d+)" replace="🛡️ SRB add: $1">nr-rrc.srb_Identity</param>
    <param match=".*" replace="🛠️ RLC mode: AM">nr-rrc.am_element</param>
    <param match=".*" replace="🛠️ RLC mode: UM-shortSN">nr-rrc.um_WithShortSN</param>
    <param match=".*" replace="🛠️ RLC mode: UM-longSN">nr-rrc.um_WithLongSN</param>
    <param match=".*t-PollRetransmit: (.+) \(\d+\)" replace="⏱️ T-PollRetx: $1">nr-rrc.t_PollRetransmit</param>
    <param match=".*maxRetxThreshold: (.+) \(\d+\)" replace="📦 maxRetx: $1">nr-rrc.maxRetxThreshold</param>
    <param match=".*t-Reassembly: (.+) \(\d+\)" replace="⏱️ T-Reassembly: $1">nr-rrc.t_Reassembly</param>
    <param match=".*t-StatusProhibit: (.+) \(\d+\)" replace="⏱️ T-StatusProhibit: $1">nr-rrc.t_StatusProhibit</param>
    <remark match=".*Frame Number: (\d+)" replace="💡 [Frame $1] RRC Setup — Msg4 payload carrying SRB1 config and master cell group; UE moves to RRC_CONNECTED on receipt">frame.number</remark>
  </message>

  <!-- UL DCCH: RRC Setup Complete (bare-RLC variant) -->
  <message extends="mac-sched-context-nr" style="coral" bookmark="true" priority="500">
    <source><address match=".*Uplink.*" replace="📱 UE">rlc-nr.direction</address></source>
    <destination><address match=".*Uplink.*" replace="📡 gNB">rlc-nr.direction</address></destination>
    <opcode match=".*c1: rrcSetupComplete \(.*" replace="✅ RRC Setup Complete">nr-rrc.c1</opcode>
    <param match="RNTI: (\S+).*" replace="🆔 RNTI: $1">mac-nr.rnti</param>
    <param match="rrc-TransactionIdentifier: (\d+)" replace="🔢 Trans ID: $1">nr-rrc.rrc_TransactionIdentifier</param>
    <param match="selectedPLMN-Identity: (\d+)" replace="🌐 PLMN idx: $1">nr-rrc.selectedPLMN_Identity</param>
    <param match="Bearer Id: (\d+)" replace="🚌 SRB: $1">pdcp-nr.bearer-id</param>
    <param match="dedicatedNAS-Message: (.{16}).*" replace="📦 NAS: $1…">nr-rrc.dedicatedNAS_Message</param>
    <param match="Message type: (.+) \(0x[0-9a-fA-F]+\)" replace="📬 NAS msg: $1">nas-5gs.mm.message_type</param>
    <remark match=".*Frame Number: (\d+)" replace="💡 [Frame $1] RRC Setup Complete — UE acks SRB1 and piggybacks an initial NAS message to the AMF">frame.number</remark>
  </message>

  <!-- UL CCCH: RRC Setup Request (UE→gNB, Msg3) -->
  <message extends="mac-sched-context-nr" style="coral" bookmark="true" priority="450">
    <source><address match=".*Uplink.*" replace="📱 UE">mac-nr.direction</address></source>
    <destination><address match=".*Uplink.*" replace="📡 gNB">mac-nr.direction</address></destination>
    <opcode match=".*c1: rrcSetupRequest \(.*" replace="📡 RRC Setup Request">nr-rrc.c1</opcode>
    <param match="RNTI: (\S+).*" replace="🆔 RNTI: $1">mac-nr.rnti</param>
    <param match="randomValue: (\S+).*" replace="🎲 Random: $1">nr-rrc.randomValue</param>
    <param match="establishmentCause: (.+) \(\d+\)" replace="📋 Cause: $1">nr-rrc.establishmentCause</param>
    <!-- LCID list inherited via mac-sched-context-nr. -->
    <remark match=".*Frame Number: (\d+)" replace="💡 [Frame $1] RRC Setup Request — Msg3 over CCCH; UE asks gNB to establish RRC connection">frame.number</remark>
  </message>

  <!-- DL CCCH: RRC Setup (gNB→UE, Msg4 payload) -->
  <message extends="mac-sched-context-nr" style="forest-green" bookmark="true" priority="450">
    <source><address match=".*Downlink.*" replace="📡 gNB">mac-nr.direction</address></source>
    <destination><address match=".*Downlink.*" replace="📱 UE">mac-nr.direction</address></destination>
    <opcode match=".*c1: rrcSetup \(.*" replace="✅ RRC Setup">nr-rrc.c1</opcode>
    <param match="RNTI: (\S+).*" replace="🆔 RNTI: $1">mac-nr.rnti</param>
    <param match="rrc-TransactionIdentifier: (\d+)" replace="🔢 Trans ID: $1">nr-rrc.rrc_TransactionIdentifier</param>
    <!-- LCID list inherited via mac-sched-context-nr. -->
    <param match="cellGroupId: (\d+)" replace="📐 CellGroup: $1">nr-rrc.cellGroupId</param>
    <param match="srb-Identity: (\d+)" replace="🛡️ SRB add: $1">nr-rrc.srb_Identity</param>
    <param match=".*" replace="🛠️ RLC mode: AM">nr-rrc.am_element</param>
    <param match=".*" replace="🛠️ RLC mode: UM-shortSN">nr-rrc.um_WithShortSN</param>
    <param match=".*" replace="🛠️ RLC mode: UM-longSN">nr-rrc.um_WithLongSN</param>
    <param match=".*t-PollRetransmit: (.+) \(\d+\)" replace="⏱️ T-PollRetx: $1">nr-rrc.t_PollRetransmit</param>
    <param match=".*maxRetxThreshold: (.+) \(\d+\)" replace="📦 maxRetx: $1">nr-rrc.maxRetxThreshold</param>
    <param match=".*t-Reassembly: (.+) \(\d+\)" replace="⏱️ T-Reassembly: $1">nr-rrc.t_Reassembly</param>
    <param match=".*t-StatusProhibit: (.+) \(\d+\)" replace="⏱️ T-StatusProhibit: $1">nr-rrc.t_StatusProhibit</param>
    <param match=".*periodicBSR-Timer: (.+) \(\d+\)" replace="⏰ periodicBSR: $1">nr-rrc.periodicBSR_Timer</param>
    <param match=".*retxBSR-Timer: (.+) \(\d+\)" replace="⏰ retxBSR: $1">nr-rrc.retxBSR_Timer</param>
    <param match=".*phr-PeriodicTimer: (.+) \(\d+\)" replace="📶 phr-Periodic: $1">nr-rrc.phr_PeriodicTimer</param>
    <param match=".*phr-ProhibitTimer: (.+) \(\d+\)" replace="📶 phr-Prohibit: $1">nr-rrc.phr_ProhibitTimer</param>
    <param match=".*phr-Tx-PowerFactorChange: (.+) \(\d+\)" replace="📶 phr-TxPwrFactor: $1">nr-rrc.phr_Tx_PowerFactorChange</param>
    <param match=".*phr-Type2OtherCell: (.+)" replace="📶 phr-Type2OtherCell: $1">nr-rrc.phr_Type2OtherCell</param>
    <param match=".*phr-ModeOtherCG: (.+) \(\d+\)" replace="📶 phr-ModeOtherCG: $1">nr-rrc.phr_ModeOtherCG</param>
    <param match=".*sr-TransMax: (.+) \(\d+\)" replace="📡 SR-TransMax: $1">nr-rrc.sr_TransMax</param>
    <param match=".*tag-Id: (\d+)" replace="🏷️ TAG: $1">nr-rrc.tag_Id</param>
    <param match=".*timeAlignmentTimer: (.+) \(\d+\)" replace="⏰ TAT: $1">nr-rrc.timeAlignmentTimer</param>
    <param match="controlResourceSetId: (\d+)" replace="📐 CORESET: $1">nr-rrc.controlResourceSetId</param>
    <remark match=".*Frame Number: (\d+)" replace="💡 [Frame $1] RRC Setup — Msg4 payload carrying SRB1 config and master cell group; UE moves to RRC_CONNECTED on receipt">frame.number</remark>
  </message>

  <!-- UL DCCH: RRC Setup Complete (UE→gNB, Msg5; piggybacks initial NAS) -->
  <message extends="mac-sched-context-nr" style="coral" bookmark="true" priority="450">
    <source><address match=".*Uplink.*" replace="📱 UE">mac-nr.direction</address></source>
    <destination><address match=".*Uplink.*" replace="📡 gNB">mac-nr.direction</address></destination>
    <opcode match=".*c1: rrcSetupComplete \(.*" replace="✅ RRC Setup Complete">nr-rrc.c1</opcode>
    <param match="RNTI: (\S+).*" replace="🆔 RNTI: $1">mac-nr.rnti</param>
    <param match="rrc-TransactionIdentifier: (\d+)" replace="🔢 Trans ID: $1">nr-rrc.rrc_TransactionIdentifier</param>
    <param match="selectedPLMN-Identity: (\d+)" replace="🌐 PLMN idx: $1">nr-rrc.selectedPLMN_Identity</param>
    <param match="Bearer Id: (\d+)" replace="🚌 SRB: $1">pdcp-nr.bearer-id</param>
    <param match="dedicatedNAS-Message: (.{16}).*" replace="📦 NAS: $1…">nr-rrc.dedicatedNAS_Message</param>
    <param match="Message type: (.+) \(0x[0-9a-fA-F]+\)" replace="📬 NAS msg: $1">nas-5gs.mm.message_type</param>
    <param match=".*5GS registration type: (.+) \(\d+\)" replace="📋 NAS Reg type: $1">nas-5gs.mm.5gs_reg_type</param>
    <param match=".*Type of identity: (.+) \(\d+\)" replace="🎫 NAS Mobile ID: $1">nas-5gs.mm.type_id</param>
    <param match="Mobile Country Code \(MCC\): (.+)" replace="🌍 NAS MCC: $1">e212.guami.mcc</param>
    <param match="Mobile Network Code \(MNC\): (.+)" replace="🌍 NAS MNC: $1">e212.guami.mnc</param>
    <param match="5G-TMSI: \d+ \((0x[0-9a-fA-F]+)\)" replace="🎟️ NAS 5G-TMSI: $1">nas-5gs.5g_tmsi</param>
    <remark match=".*Frame Number: (\d+)" replace="💡 [Frame $1] RRC Setup Complete — UE acks SRB1 and piggybacks an initial NAS message to the AMF">frame.number</remark>
  </message>

  <!-- DL DCCH: RRC Reconfiguration -->
  <message extends="mac-sched-context-nr" style="forest-green" bookmark="true" priority="440">
    <source><address match=".*Downlink.*" replace="📡 gNB">mac-nr.direction</address></source>
    <destination><address match=".*Downlink.*" replace="📱 UE">mac-nr.direction</address></destination>
    <opcode match=".*c1: rrcReconfiguration \(.*" replace="⚙️ RRC Reconfiguration">nr-rrc.c1</opcode>
    <param match="RNTI: (\S+).*" replace="🆔 RNTI: $1">mac-nr.rnti</param>
    <param match="rrc-TransactionIdentifier: (\d+)" replace="🔢 Trans ID: $1">nr-rrc.rrc_TransactionIdentifier</param>
    <param match="Bearer Id: (\d+)" replace="🚌 SRB: $1">pdcp-nr.bearer-id</param>
    <param match="srb-Identity: (\d+)" replace="🛡️ SRB add: $1">nr-rrc.srb_Identity</param>
    <param match="drb-Identity: (\d+)" replace="📊 DRB add: $1">nr-rrc.drb_Identity</param>
    <param match="pdu-Session: (\d+)" replace="🌐 PDU Session: $1">nr-rrc.pdu_Session</param>
    <param match="QFI: (\d+)" replace="🎯 QFI: $1">nr-rrc.QFI</param>
    <param match=".*defaultDRB: (.+)" replace="✅ Default DRB: $1">nr-rrc.defaultDRB</param>
    <param match="logicalChannelIdentity: (\d+)" replace="📛 LCID: $1">nr-rrc.logicalChannelIdentity</param>
    <param match="priority: (\d+)" replace="🚦 Priority: $1">nr-rrc.priority</param>
    <param match="prioritisedBitRate: (.+) \(\d+\)" replace="📏 PBR: $1">nr-rrc.prioritisedBitRate</param>
    <param match=".*" replace="🛠️ RLC mode: AM">nr-rrc.am_element</param>
    <param match=".*" replace="🛠️ RLC mode: UM-shortSN">nr-rrc.um_WithShortSN</param>
    <param match=".*" replace="🛠️ RLC mode: UM-longSN">nr-rrc.um_WithLongSN</param>
    <param match=".*t-PollRetransmit: (.+) \(\d+\)" replace="⏱️ T-PollRetx: $1">nr-rrc.t_PollRetransmit</param>
    <param match=".*pollPDU: (.+) \(\d+\)" replace="📦 pollPDU: $1">nr-rrc.pollPDU</param>
    <param match=".*pollByte: (.+) \(\d+\)" replace="📦 pollByte: $1">nr-rrc.pollByte</param>
    <param match=".*pdcp-SN-SizeDL: (.+) \(\d+\)" replace="🔢 PDCP-SN DL: $1">nr-rrc.pdcp_SN_SizeDL</param>
    <param match=".*pdcp-SN-SizeUL: (.+) \(\d+\)" replace="🔢 PDCP-SN UL: $1">nr-rrc.pdcp_SN_SizeUL</param>
    <param match=".*headerCompression: (.+) \(\d+\)" replace="🗜️ ROHC: $1">nr-rrc.headerCompression</param>
    <param match=".*sdap-HeaderDL: (.+) \(\d+\)" replace="🛣️ SDAP DL: $1">nr-rrc.sdap_HeaderDL</param>
    <param match=".*sdap-HeaderUL: (.+) \(\d+\)" replace="🛣️ SDAP UL: $1">nr-rrc.sdap_HeaderUL</param>
    <param match="p-NR-FR1: (.+)" replace="⚡ p-NR-FR1: $1">nr-rrc.p_NR_FR1</param>
    <param match=".*phr-PeriodicTimer: (.+) \(\d+\)" replace="📶 phr-Periodic: $1">nr-rrc.phr_PeriodicTimer</param>
    <param match=".*phr-ProhibitTimer: (.+) \(\d+\)" replace="📶 phr-Prohibit: $1">nr-rrc.phr_ProhibitTimer</param>
    <param match=".*phr-Tx-PowerFactorChange: (.+) \(\d+\)" replace="📶 phr-TxPwrFactor: $1">nr-rrc.phr_Tx_PowerFactorChange</param>
    <param match=".*phr-Type2OtherCell: (.+)" replace="📶 phr-Type2OtherCell: $1">nr-rrc.phr_Type2OtherCell</param>
    <param match=".*phr-ModeOtherCG: (.+) \(\d+\)" replace="📶 phr-ModeOtherCG: $1">nr-rrc.phr_ModeOtherCG</param>
    <param match="pdsch-HARQ-ACK-Codebook: (.+) \(\d+\)" replace="🔁 HARQ-ACK CB: $1">nr-rrc.pdsch_HARQ_ACK_Codebook</param>
    <param match="dedicatedNAS-Message: (.{16}).*" replace="📦 NAS: $1…">nr-rrc.dedicatedNAS_Message</param>
    <remark match=".*Frame Number: (\d+)" replace="💡 [Frame $1] RRC Reconfiguration — gNB pushes radio-bearer / measurement / PHY config; may carry a piggybacked NAS message">frame.number</remark>
  </message>

  <!-- UL DCCH: RRC Reconfiguration Complete -->
  <message extends="mac-sched-context-nr" style="coral" priority="440" bookmark="true">
    <source><address match=".*Uplink.*" replace="📱 UE">mac-nr.direction</address></source>
    <destination><address match=".*Uplink.*" replace="📡 gNB">mac-nr.direction</address></destination>
    <opcode match=".*c1: rrcReconfigurationComplete \(.*" replace="✅ RRC Reconfiguration Complete">nr-rrc.c1</opcode>
    <param match="RNTI: (\S+).*" replace="🆔 RNTI: $1">mac-nr.rnti</param>
    <param match="rrc-TransactionIdentifier: (\d+)" replace="🔢 Trans ID: $1">nr-rrc.rrc_TransactionIdentifier</param>
    <param match="Bearer Id: (\d+)" replace="🚌 SRB: $1">pdcp-nr.bearer-id</param>
    <remark match=".*Frame Number: (\d+)" replace="💡 [Frame $1] RRC Reconfiguration Complete — UE has applied the new bearer/PHY config">frame.number</remark>
  </message>

  <!-- DL DCCH: RRC Release -->
  <!--                                                                 -->
  <!-- RLC/PDCP identity fields (am.sn, am.p, pdcp seq-num, MAC-I) are -->
  <!-- surfaced so that a retransmission storm (same RLC SN with poll  -->
  <!-- bit set, repeating because the UE never acks) is visible in the -->
  <!-- diagram without needing to drop to tshark. The classic failure  -->
  <!-- mode here is a UE that has already autonomously moved to        -->
  <!-- RRC_IDLE after a terminal NAS reject — gNB's t-PollRetransmit   -->
  <!-- then fires every ~45 ms re-sending the same SN with P=1.        -->
  <message extends="mac-sched-context-nr" style="firebrick" bookmark="true" priority="440">
    <source><address match=".*Downlink.*" replace="📡 gNB">mac-nr.direction</address></source>
    <destination><address match=".*Downlink.*" replace="📱 UE">mac-nr.direction</address></destination>
    <opcode match=".*c1: rrcRelease \(.*" replace="🛑 RRC Release">nr-rrc.c1</opcode>
    <param match="RNTI: (\S+).*" replace="🆔 RNTI: $1">mac-nr.rnti</param>
    <param match="rrc-TransactionIdentifier: (\d+)" replace="🔢 Trans ID: $1">nr-rrc.rrc_TransactionIdentifier</param>
    <param match="Bearer Id: (\d+)" replace="🚌 SRB: $1">pdcp-nr.bearer-id</param>
    <param match="waitTime: (\d+)" replace="⏱️ Wait: $1s">nr-rrc.waitTime</param>
    <param match=".*Sequence Number: (\d+)" replace="📦 RLC SN=$1">rlc-nr.am.sn</param>
    <param match=".*Polling Bit: (.+)" replace="❓ RLC Poll: $1">rlc-nr.am.p</param>
    <param match=".*SN (\d+) Repeated.*" replace="🔁 RLC retx (dup PDCP SN $1)">pdcp-nr.sequence-analysis</param>
    <param match=".*Seq Num: (\d+)" replace="🔢 PDCP SN=$1">pdcp-nr.seq-num</param>
    <param match="COUNT: (\d+)" replace="🔢 PDCP COUNT: $1">pdcp-nr.security-config.count</param>
    <param match="MAC: (\S+)" replace="🛡️ MAC-I: $1">pdcp-nr.mac</param>
    <remark match=".*Frame Number: (\d+)" replace="💡 [Frame $1] RRC Release — gNB sends UE to RRC_IDLE (or RRC_INACTIVE if suspendConfig is present). Watch for repeated SN+Poll: that signals UE already gone (RLC poll-retx storm).">frame.number</remark>
  </message>

  <!-- DL DCCH: Security Mode Command -->
  <message extends="mac-sched-context-nr" style="amethyst" bookmark="true" priority="430">
    <source><address match=".*Downlink.*" replace="📡 gNB">mac-nr.direction</address></source>
    <destination><address match=".*Downlink.*" replace="📱 UE">mac-nr.direction</address></destination>
    <opcode match=".*c1: securityModeCommand \(.*" replace="🔐 Security Mode Command">nr-rrc.c1</opcode>
    <param match="RNTI: (\S+).*" replace="🆔 RNTI: $1">mac-nr.rnti</param>
    <param match="rrc-TransactionIdentifier: (\d+)" replace="🔢 Trans ID: $1">nr-rrc.rrc_TransactionIdentifier</param>
    <param match="cipheringAlgorithm: (.+) \(\d+\)" replace="🔒 Cipher: $1">nr-rrc.cipheringAlgorithm</param>
    <param match="integrityProtAlgorithm: (.+) \(\d+\)" replace="🛡️ Integrity: $1">nr-rrc.integrityProtAlgorithm</param>
    <param match="Bearer Id: (\d+)" replace="🚌 SRB: $1">pdcp-nr.bearer-id</param>
    <remark match=".*Frame Number: (\d+)" replace="💡 [Frame $1] Security Mode Command — gNB activates AS security; subsequent SRB/DRB traffic is ciphered and integrity-protected">frame.number</remark>
  </message>

  <!-- UL DCCH: Security Mode Complete -->
  <message extends="mac-sched-context-nr" style="amethyst" priority="430" bookmark="true">
    <source><address match=".*Uplink.*" replace="📱 UE">mac-nr.direction</address></source>
    <destination><address match=".*Uplink.*" replace="📡 gNB">mac-nr.direction</address></destination>
    <opcode match=".*c1: securityModeComplete \(.*" replace="🔐 Security Mode Complete">nr-rrc.c1</opcode>
    <param match="RNTI: (\S+).*" replace="🆔 RNTI: $1">mac-nr.rnti</param>
    <param match="rrc-TransactionIdentifier: (\d+)" replace="🔢 Trans ID: $1">nr-rrc.rrc_TransactionIdentifier</param>
    <param match="Bearer Id: (\d+)" replace="🚌 SRB: $1">pdcp-nr.bearer-id</param>
    <remark match=".*Frame Number: (\d+)" replace="💡 [Frame $1] Security Mode Complete — UE confirms AS security activation">frame.number</remark>
  </message>

  <!-- DL DCCH: UE Capability Enquiry -->
  <message extends="mac-sched-context-nr" style="royal-blue" priority="420" bookmark="true">
    <source><address match=".*Downlink.*" replace="📡 gNB">mac-nr.direction</address></source>
    <destination><address match=".*Downlink.*" replace="📱 UE">mac-nr.direction</address></destination>
    <opcode match=".*c1: ueCapabilityEnquiry \(.*" replace="❓ UE Capability Enquiry">nr-rrc.c1</opcode>
    <param match="RNTI: (\S+).*" replace="🆔 RNTI: $1">mac-nr.rnti</param>
    <param match="rrc-TransactionIdentifier: (\d+)" replace="🔢 Trans ID: $1">nr-rrc.rrc_TransactionIdentifier</param>
    <param match="rat-Type: (.+) \(\d+\)" replace="📡 RAT: $1">nr-rrc.rat_Type</param>
    <param match="Bearer Id: (\d+)" replace="🚌 SRB: $1">pdcp-nr.bearer-id</param>
    <remark match=".*Frame Number: (\d+)" replace="💡 [Frame $1] UE Capability Enquiry — gNB asks UE to report supported bands, MIMO layers, modulation, ROHC profiles">frame.number</remark>
  </message>

  <!-- UL DCCH: UE Capability Information -->
  <message extends="mac-sched-context-nr" style="royal-blue" priority="420" bookmark="true">
    <source><address match=".*Uplink.*" replace="📱 UE">mac-nr.direction</address></source>
    <destination><address match=".*Uplink.*" replace="📡 gNB">mac-nr.direction</address></destination>
    <opcode match=".*c1: ueCapabilityInformation \(.*" replace="📋 UE Capability Information">nr-rrc.c1</opcode>
    <param match="RNTI: (\S+).*" replace="🆔 RNTI: $1">mac-nr.rnti</param>
    <param match="rrc-TransactionIdentifier: (\d+)" replace="🔢 Trans ID: $1">nr-rrc.rrc_TransactionIdentifier</param>
    <param match="Bearer Id: (\d+)" replace="🚌 SRB: $1">pdcp-nr.bearer-id</param>
    <param match="powerClass-v1530: (.+) \(\d+\)" replace="⚡ PowerClass: $1">nr-rrc.powerClass_v1530</param>
    <param match="multiplePHR: (.+)" replace="📶 multiplePHR: $1">nr-rrc.multiplePHR</param>
    <remark match=".*Frame Number: (\d+)" replace="💡 [Frame $1] UE Capability Information — UE returns supported bands, MIMO, modulation, ROHC profiles">frame.number</remark>
  </message>

  <!-- DL DCCH: DL Information Transfer (transparent NAS down) -->
  <!--                                                                 -->
  <!-- When NEA0 ciphering is in use (typical srsRAN+Open5GS test rig) -->
  <!-- the inner NAS message is plaintext; surfacing                   -->
  <!-- nas-5gs.mm.message_type / nas-5gs.sm.message_type and any cause -->
  <!-- IE turns these otherwise opaque "NAS container" frames into     -->
  <!-- readable procedure steps (Auth Req, Reg Accept, Config Update   -->
  <!-- Cmd, Reg Reject + 5GMM cause, etc.). For ciphered captures the  -->
  <!-- enclosing tshark call should set nas-5gs.null_decipher:TRUE when -->
  <!-- the cipher is null (NEA0) so the dissector still parses bodies. -->
  <message extends="mac-sched-context-nr" style="royal-blue" priority="410">
    <source><address match=".*Downlink.*" replace="📡 gNB">mac-nr.direction</address></source>
    <destination><address match=".*Downlink.*" replace="📱 UE">mac-nr.direction</address></destination>
    <opcode match=".*c1: dlInformationTransfer \(.*" replace="⬇️ DL Information Transfer">nr-rrc.c1</opcode>
    <param match="RNTI: (\S+).*" replace="🆔 RNTI: $1">mac-nr.rnti</param>
    <param match="rrc-TransactionIdentifier: (\d+)" replace="🔢 Trans ID: $1">nr-rrc.rrc_TransactionIdentifier</param>
    <param match="Bearer Id: (\d+)" replace="🚌 SRB: $1">pdcp-nr.bearer-id</param>
    <param match="dedicatedNAS-Message: (.{16}).*" replace="📦 NAS: $1…">nr-rrc.dedicatedNAS_Message</param>
    <param match="Message type: (.+) \(0x[0-9a-fA-F]+\)" replace="📬 NAS 5GMM: $1">nas-5gs.mm.message_type</param>
    <param match="Message type: (.+) \(0x[0-9a-fA-F]+\)" replace="📬 NAS 5GSM: $1">nas-5gs.sm.message_type</param>
    <param match=".*5GMM cause: (.+) \(\d+\)" replace="🚨 5GMM cause: $1">nas-5gs.mm.5gmm_cause</param>
    <param match=".*5GSM cause: (.+) \(\d+\)" replace="🚨 5GSM cause: $1">nas-5gs.sm.5gsm_cause</param>
    <param match=".*5GS registration result: (.+) \(\d+\)" replace="📋 Reg result: $1">nas-5gs.mm.5gs_reg_result</param>
    <remark match=".*Frame Number: (\d+)" replace="💡 [Frame $1] DL Information Transfer — transparent NAS container from AMF/SMF to UE">frame.number</remark>
  </message>

  <!-- UL DCCH: UL Information Transfer (transparent NAS up) -->
  <message extends="mac-sched-context-nr" style="coral" priority="410">
    <source><address match=".*Uplink.*" replace="📱 UE">mac-nr.direction</address></source>
    <destination><address match=".*Uplink.*" replace="📡 gNB">mac-nr.direction</address></destination>
    <opcode match=".*c1: ulInformationTransfer \(.*" replace="⬆️ UL Information Transfer">nr-rrc.c1</opcode>
    <param match="RNTI: (\S+).*" replace="🆔 RNTI: $1">mac-nr.rnti</param>
    <param match="Bearer Id: (\d+)" replace="🚌 SRB: $1">pdcp-nr.bearer-id</param>
    <param match="dedicatedNAS-Message: (.{16}).*" replace="📦 NAS: $1…">nr-rrc.dedicatedNAS_Message</param>
    <param match="Message type: (.+) \(0x[0-9a-fA-F]+\)" replace="📬 NAS 5GMM: $1">nas-5gs.mm.message_type</param>
    <param match="Message type: (.+) \(0x[0-9a-fA-F]+\)" replace="📬 NAS 5GSM: $1">nas-5gs.sm.message_type</param>
    <param match=".*5GMM cause: (.+) \(\d+\)" replace="🚨 5GMM cause: $1">nas-5gs.mm.5gmm_cause</param>
    <param match=".*5GSM cause: (.+) \(\d+\)" replace="🚨 5GSM cause: $1">nas-5gs.sm.5gsm_cause</param>
    <remark match=".*Frame Number: (\d+)" replace="💡 [Frame $1] UL Information Transfer — transparent NAS container from UE to AMF/SMF">frame.number</remark>
  </message>

  <!-- =============================================================== -->
  <!-- RLC-NR AM Status PDU (priority 350) — ARQ feedback              -->
  <!-- =============================================================== -->

  <!-- =============================================================== -->
  <!-- DRB UPLINK STALL HIGHLIGHTERS (custom, Issue 1)                  -->
  <!--                                                                 -->
  <!-- This capture's DRB1 (LCID 4 / bearer 4) uplink stalls: the UE   -->
  <!-- retransmits the same RLC AM Data SN=0 ~18× across frames 40-62  -->
  <!-- and the gNB answers every poll with STATUS ACK_SN=0 (frames     -->
  <!-- 41-63), i.e. it never advances past 0. These two templates lift -->
  <!-- those PDUs out of the neutral-lavender ARQ stream and render    -->
  <!-- them in firebrick with explicit "never ACKed" / "no progress"   -->
  <!-- labels so the SN=0 / ACK_SN=0 loop is self-evident.             -->
  <!--                                                                 -->
  <!-- Capture-accurate filters (no false positives on this pcap):     -->
  <!--   • Stall STATUS: anchored on rlc-nr.am.ack-sn == 0. Every SRB  -->
  <!--     status PDU here reports ACK_SN >= 1, so matching "0" isolates-->
  <!--     exactly the 12 DRB status PDUs. Priority 355 > 350 so it     -->
  <!--     wins over the generic RLC Status template.                  -->
  <!--   • Stall DATA: anchored on rlc-nr.am.sn (a Data PDU, mutually  -->
  <!--     exclusive with am.cpt/Status). UL-only. Priority 348 sits    -->
  <!--     above both the generic RLC AM Data (340) and the RLC retx    -->
  <!--     (345) templates so the DRB SN=0 (re)transmissions always     -->
  <!--     carry the stall label. SRB RLC data is caught by the NR-RRC  -->
  <!--     templates (410+) and never falls to this tier.              -->
  <!--                                                                 -->
  <!-- Note: FXT has no cross-frame state, so a literal "retx #N"       -->
  <!-- counter is not expressible. The per-frame SN + Poll (data) and  -->
  <!-- ACK_SN (status) annotations + frame.number in each remark let    -->
  <!-- the reader count the loop directly off the diagram.             -->
  <!-- =============================================================== -->

  <!-- DL DRB STATUS — frozen ACK_SN=0 (gNB never advances) -->
  <message extends="mac-sched-context-nr" style="firebrick" bookmark="true" priority="355">
    <source><address match=".*Downlink.*" replace="📡 gNB">rlc-nr.direction</address></source>
    <destination><address match=".*Downlink.*" replace="📱 UE">rlc-nr.direction</address></destination>
    <opcode match=".*ACK Sequence Number: 0\b.*" replace="🛑📥 DRB STATUS — ACK_SN=0 (no progress)">rlc-nr.am.ack-sn</opcode>
    <param match=".*RNTI: (\S+).*" replace="🆔 RNTI: $1">mac-nr.rnti</param>
    <param match="Bearer Id: (\d+)" replace="🚌 DRB Bearer: $1">rlc-nr.bearer-id</param>
    <param match=".*ACK Sequence Number: (\d+)" replace="✅ ACK_SN: $1 (SN 0 NOT acknowledged)">rlc-nr.am.ack-sn</param>
    <param match="Number of NACKs: (\d+)" replace="🚫 NACKs: $1">rlc-nr.am.nacks</param>
    <param match=".*Extension bit 1: (.+)" replace="🔗 E1: $1">rlc-nr.am.e1</param>
    <remark match=".*Frame Number: (\d+)" replace="💡 [Frame $1] 🛑 DRB STALL — gNB STATUS reports ACK_SN=0: it has NOT acknowledged the UE's SN=0 SDU. RX_Next never advances ⇒ uplink user plane frozen. MAC delivered the PDU (this is a gNB trace) but RLC-AM RX does not deliver/ACK it.">frame.number</remark>
  </message>

  <!-- UL DRB DATA — SN=0 retransmitted, never ACKed -->
  <message extends="mac-sched-context-nr" style="firebrick" bookmark="true" priority="348">
    <source><address match=".*Uplink.*" replace="📱 UE">rlc-nr.direction</address></source>
    <destination><address match=".*Uplink.*" replace="📡 gNB">rlc-nr.direction</address></destination>
    <opcode match=".*Sequence Number: (\d+)" replace="🛑📤 DRB UL Data SN=$1 (never ACKed)">rlc-nr.am.sn</opcode>
    <param match=".*RNTI: (\S+).*" replace="🆔 RNTI: $1">mac-nr.rnti</param>
    <param match="Bearer Id: (\d+)" replace="🚌 DRB Bearer: $1">rlc-nr.bearer-id</param>
    <param match=".*Polling Bit: (.+)" replace="❓ Poll (forces STATUS): $1">rlc-nr.am.p</param>
    <param match=".*Segmentation Info: (.+) \(0x[0-9a-fA-F]+\)" replace="✂️ SI: $1">rlc-nr.am.si</param>
    <param match="\[PDU Length: (\d+)\]" replace="📏 Len: $1">rlc-nr.pdu-length</param>
    <remark match=".*Frame Number: (\d+)" replace="💡 [Frame $1] 🛑 DRB STALL — UE (re)sends RLC AM Data SN=0 on the DRB with Poll=1. SN never leaves 0 ⇒ a single uplink SDU retransmitted because the gNB never positively ACKs it. ~1s cadence = upper-layer retry; ~20-40ms bursts = t-PollRetransmit.">frame.number</remark>
  </message>

  <!-- DL RLC Status PDU (gNB→UE) -->
  <message extends="mac-sched-context-nr" style="lavender" priority="350">
    <source><address match=".*Downlink.*" replace="📡 gNB">rlc-nr.direction</address></source>
    <destination><address match=".*Downlink.*" replace="📱 UE">rlc-nr.direction</address></destination>
    <opcode match=".*" replace="🛂 RLC AM Status PDU">rlc-nr.am.cpt</opcode>
    <param match=".*RNTI: (\S+).*" replace="🆔 RNTI: $1">mac-nr.rnti</param>
    <param match="RLC Mode: (\S+) Mode \(\d+\)" replace="🛠️ RLC mode: $1">rlc-nr.mode</param>
    <param match="Sequence Number length: (\d+)" replace="🔢 RLC SN size: $1-bit">rlc-nr.seqnum-length</param>
    <param match=".*ACK Sequence Number: (\d+)" replace="✅ ACK SN: $1">rlc-nr.am.ack-sn</param>
    <param match="Number of NACKs: (\d+)" replace="🚫 NACKs: $1">rlc-nr.am.nacks</param>
    <param match=".*NACK Sequence Number: (\d+)" replace="🚫 NACK SN: $1">rlc-nr.am.nack-sn</param>
    <param match="NACK range: (\d+)" replace="📐 NACK range: $1">rlc-nr.am.nack-range</param>
    <param match="SO start: (\d+)" replace="✂️ SO start: $1">rlc-nr.am.so-start</param>
    <param match="SO end: (\d+)" replace="✂️ SO end: $1">rlc-nr.am.so-end</param>
    <param match=".*Extension bit 1: (.+)" replace="🔗 E1: $1">rlc-nr.am.e1</param>
    <param match="Bearer Id: (\d+)" replace="🚌 Bearer: $1">rlc-nr.bearer-id</param>
    <remark match=".*Frame Number: (\d+)" replace="💡 [Frame $1] RLC AM Status PDU — receiver reports cumulative ACK_SN and any NACK_SN list; drives RLC retransmissions on the AM bearer">frame.number</remark>
  </message>

  <!-- UL RLC Status PDU (UE→gNB) -->
  <message extends="mac-sched-context-nr" style="lavender" priority="350">
    <source><address match=".*Uplink.*" replace="📱 UE">rlc-nr.direction</address></source>
    <destination><address match=".*Uplink.*" replace="📡 gNB">rlc-nr.direction</address></destination>
    <opcode match=".*" replace="🛂 RLC AM Status PDU">rlc-nr.am.cpt</opcode>
    <param match=".*RNTI: (\S+).*" replace="🆔 RNTI: $1">mac-nr.rnti</param>
    <param match="RLC Mode: (\S+) Mode \(\d+\)" replace="🛠️ RLC mode: $1">rlc-nr.mode</param>
    <param match="Sequence Number length: (\d+)" replace="🔢 RLC SN size: $1-bit">rlc-nr.seqnum-length</param>
    <param match=".*ACK Sequence Number: (\d+)" replace="✅ ACK SN: $1">rlc-nr.am.ack-sn</param>
    <param match="Number of NACKs: (\d+)" replace="🚫 NACKs: $1">rlc-nr.am.nacks</param>
    <param match=".*NACK Sequence Number: (\d+)" replace="🚫 NACK SN: $1">rlc-nr.am.nack-sn</param>
    <param match="NACK range: (\d+)" replace="📐 NACK range: $1">rlc-nr.am.nack-range</param>
    <param match="SO start: (\d+)" replace="✂️ SO start: $1">rlc-nr.am.so-start</param>
    <param match="SO end: (\d+)" replace="✂️ SO end: $1">rlc-nr.am.so-end</param>
    <param match=".*Extension bit 1: (.+)" replace="🔗 E1: $1">rlc-nr.am.e1</param>
    <param match="Bearer Id: (\d+)" replace="🚌 Bearer: $1">rlc-nr.bearer-id</param>
    <remark match=".*Frame Number: (\d+)" replace="💡 [Frame $1] RLC AM Status PDU — receiver reports cumulative ACK_SN and any NACK_SN list; drives RLC retransmissions on the AM bearer">frame.number</remark>
  </message>

  <!-- =============================================================== -->
  <!-- RLC AM retransmission (priority 345) — bookmarked retx event    -->
  <!--                                                                 -->
  <!-- A PDU whose PDCP SDU PDCP flags as a duplicate delivery, i.e.   -->
  <!-- RLC re-sent it. Sits above the generic RLC AM Data (340) so the -->
  <!-- retransmitted PDU is labelled and bookmarked distinctly; the    -->
  <!-- original transmission and every non-duplicate PDU fall through  -->
  <!-- to 340. Fires only on the duplicate frame — pdcp-nr.sequence-   -->
  <!-- analysis carries the "SN N Repeated" variant there and "OK"     -->
  <!-- everywhere else (which never matches the opcode regex). With 0  -->
  <!-- NACKs in a capture these are t-PollRetransmit-driven (e.g. the  -->
  <!-- gNB resending an RRC Release the UE never acked). Where a       -->
  <!-- higher-priority template also matches the frame (RRC Release at -->
  <!-- 440 for the SRB1 release retx) that label wins in the single-   -->
  <!-- winner explore view; this template still flags DRB / non-RRC    -->
  <!-- retransmissions and bookmarks them.                             -->
  <!-- =============================================================== -->

  <!-- DL RLC AM retransmission (gNB→UE) -->
  <message extends="mac-sched-context-nr" style="firebrick" bookmark="true" priority="345">
    <source><address match=".*Downlink.*" replace="📡 gNB">rlc-nr.direction</address></source>
    <destination><address match=".*Downlink.*" replace="📱 UE">rlc-nr.direction</address></destination>
    <opcode match=".*SN (\d+) Repeated.*" replace="🔁 RLC retx (dup PDCP SN $1)">pdcp-nr.sequence-analysis</opcode>
    <param match=".*Sequence Number: (\d+)" replace="📦 RLC SN=$1">rlc-nr.am.sn</param>
    <param match=".*RNTI: (\S+).*" replace="🆔 RNTI: $1">mac-nr.rnti</param>
    <param match="Bearer Id: (\d+)" replace="🚌 Bearer: $1">rlc-nr.bearer-id</param>
    <param match=".*Polling Bit: (.+)" replace="❓ P: $1">rlc-nr.am.p</param>
    <remark match=".*Frame Number: (\d+)" replace="💡 [Frame $1] RLC AM retransmission — PDCP flagged this PDCP SN as a duplicate delivery, so RLC re-sent the PDU. 0 NACKs in this capture ⇒ poll-retransmit (t-PollRetransmit) driven, typically a release the UE never acked.">frame.number</remark>
  </message>

  <!-- UL RLC AM retransmission (UE→gNB) -->
  <message extends="mac-sched-context-nr" style="firebrick" bookmark="true" priority="345">
    <source><address match=".*Uplink.*" replace="📱 UE">rlc-nr.direction</address></source>
    <destination><address match=".*Uplink.*" replace="📡 gNB">rlc-nr.direction</address></destination>
    <opcode match=".*SN (\d+) Repeated.*" replace="🔁 RLC retx (dup PDCP SN $1)">pdcp-nr.sequence-analysis</opcode>
    <param match=".*Sequence Number: (\d+)" replace="📦 RLC SN=$1">rlc-nr.am.sn</param>
    <param match=".*RNTI: (\S+).*" replace="🆔 RNTI: $1">mac-nr.rnti</param>
    <param match="Bearer Id: (\d+)" replace="🚌 Bearer: $1">rlc-nr.bearer-id</param>
    <param match=".*Polling Bit: (.+)" replace="❓ P: $1">rlc-nr.am.p</param>
    <remark match=".*Frame Number: (\d+)" replace="💡 [Frame $1] RLC AM retransmission — PDCP flagged this PDCP SN as a duplicate delivery, so RLC re-sent the PDU. 0 NACKs in this capture ⇒ poll-retransmit (t-PollRetransmit) driven, typically a release the UE never acked.">frame.number</remark>
  </message>

  <!-- =============================================================== -->
  <!-- RLC-NR AM Data PDU (priority 340) — sequenced SDU segments      -->
  <!--                                                                 -->
  <!-- Status PDUs (priority 350 above) anchor on `rlc-nr.am.cpt`;     -->
  <!-- Data PDUs anchor on `rlc-nr.am.sn`. These two fields are        -->
  <!-- mutually exclusive per packet (a single RLC PDU is either a     -->
  <!-- Status or a Data PDU), so the priority ordering between them    -->
  <!-- is for diagram-layer consistency rather than disambiguation.    -->
  <!--                                                                 -->
  <!-- These templates use `rlc-nr.direction` for source/destination,  -->
  <!-- so they fire on both MAC-NR-pcap captures (the original target  -->
  <!-- of this sample) and on bare-RLC-pcap captures (srsRAN's         -->
  <!-- `rlc-nr-pcap` output), where the MAC layer is absent. The       -->
  <!-- inherited `mac-sched-context-nr` base contributes mac-nr.*      -->
  <!-- params silently — when the field is absent the param skips.    -->
  <!-- =============================================================== -->

  <!-- DL RLC AM Data PDU (gNB→UE) -->
  <message extends="mac-sched-context-nr" style="lavender dashed" priority="340">
    <source><address match=".*Downlink.*" replace="📡 gNB">rlc-nr.direction</address></source>
    <destination><address match=".*Downlink.*" replace="📱 UE">rlc-nr.direction</address></destination>
    <opcode match=".*Sequence Number: (\d+)" replace="📦 RLC AM Data SN=$1">rlc-nr.am.sn</opcode>
    <param match=".*RNTI: (\S+).*" replace="🆔 RNTI: $1">mac-nr.rnti</param>
    <param match="Bearer Id: (\d+)" replace="🚌 Bearer: $1">rlc-nr.bearer-id</param>
    <param match="RLC Mode: (\S+) Mode \(\d+\)" replace="🛠️ RLC mode: $1">rlc-nr.mode</param>
    <param match="Sequence Number length: (\d+)" replace="🔢 RLC SN size: $1-bit">rlc-nr.seqnum-length</param>
    <param match=".*Segmentation Info: (.+) \(0x[0-9a-fA-F]+\)" replace="✂️ SI: $1">rlc-nr.am.si</param>
    <param match="Segment Offset: (\d+)" replace="✂️ SO: $1">rlc-nr.am.so</param>
    <param match=".*Polling Bit: (.+)" replace="❓ P: $1">rlc-nr.am.p</param>
    <param match="\[PDU Length: (\d+)\]" replace="📏 Len: $1">rlc-nr.pdu-length</param>
    <param match="Fragment count: (\d+)" replace="🧩 SDU fragments: $1">rlc-nr.fragment.count</param>
    <remark match=".*Frame Number: (\d+)" replace="💡 [Frame $1] RLC AM Data PDU — sequenced PDCP SDU segment on the AM bearer. The Poll bit forces an immediate Status PDU back">frame.number</remark>
  </message>

  <!-- UL RLC AM Data PDU (UE→gNB) -->
  <message extends="mac-sched-context-nr" style="lavender dashed" priority="340">
    <source><address match=".*Uplink.*" replace="📱 UE">rlc-nr.direction</address></source>
    <destination><address match=".*Uplink.*" replace="📡 gNB">rlc-nr.direction</address></destination>
    <opcode match=".*Sequence Number: (\d+)" replace="📦 RLC AM Data SN=$1">rlc-nr.am.sn</opcode>
    <param match=".*RNTI: (\S+).*" replace="🆔 RNTI: $1">mac-nr.rnti</param>
    <param match="Bearer Id: (\d+)" replace="🚌 Bearer: $1">rlc-nr.bearer-id</param>
    <param match="RLC Mode: (\S+) Mode \(\d+\)" replace="🛠️ RLC mode: $1">rlc-nr.mode</param>
    <param match="Sequence Number length: (\d+)" replace="🔢 RLC SN size: $1-bit">rlc-nr.seqnum-length</param>
    <param match=".*Segmentation Info: (.+) \(0x[0-9a-fA-F]+\)" replace="✂️ SI: $1">rlc-nr.am.si</param>
    <param match="Segment Offset: (\d+)" replace="✂️ SO: $1">rlc-nr.am.so</param>
    <param match=".*Polling Bit: (.+)" replace="❓ P: $1">rlc-nr.am.p</param>
    <param match="\[PDU Length: (\d+)\]" replace="📏 Len: $1">rlc-nr.pdu-length</param>
    <param match="Fragment count: (\d+)" replace="🧩 SDU fragments: $1">rlc-nr.fragment.count</param>
    <remark match=".*Frame Number: (\d+)" replace="💡 [Frame $1] RLC AM Data PDU — sequenced PDCP SDU segment on the AM bearer. The Poll bit forces an immediate Status PDU back">frame.number</remark>
  </message>

  <!-- =============================================================== -->
  <!-- SDAP user-plane PDUs (priority 360)                             -->
  <!--                                                                 -->
  <!-- SDAP (TS 37.324) is the top user-plane layer on a DRB: a 1-byte -->
  <!-- header carrying the QFI (QoS Flow Identifier) the IP payload    -->
  <!-- maps to, plus RDI/RQI (DL) or D/C (UL). It only dissects when   -->
  <!-- the DRB is configured with an SDAP header (from the RRC         -->
  <!-- Reconfiguration's sdap-Config) AND the PDCP user plane is       -->
  <!-- decipherable (null cipher, or UE keys supplied). On the ciphered-->
  <!-- bundled sample pcaps sdap.qfi never appears, so these templates -->
  <!-- stay inert and the frame falls through to the RLC AM Data       -->
  <!-- template (priority 340) as before — no behaviour change.        -->
  <!--                                                                 -->
  <!-- Priority 360 places SDAP ABOVE the RLC Status (350) / RLC Data  -->
  <!-- (340) templates so that, when it IS decipherable, a user-plane  -->
  <!-- DRB PDU surfaces by its QoS flow ("🛣️ UL SDAP → QFI 2") rather  -->
  <!-- than as a bare "RLC AM Data SN=N". Signalling (SRB) frames carry -->
  <!-- no SDAP header, so the higher-priority NR-RRC templates (410+)  -->
  <!-- still win there. Validated against the Wireshark work-item-19757 -->
  <!-- capture (r16_mdt_nr_mac.pcap) with the UE keys from that issue — -->
  <!-- see samples/5g-nr-radio/README.md and the ready-to-run          -->
  <!-- r16-mdt-19757.visualether.toml.                                 -->
  <!-- =============================================================== -->

  <!-- DL SDAP Data PDU (gNB→UE) -->
  <message extends="mac-sched-context-nr" style="turquoise" priority="360">
    <source><address match=".*Downlink.*" replace="📡 gNB">pdcp-nr.direction</address></source>
    <destination><address match=".*Downlink.*" replace="📱 UE">pdcp-nr.direction</address></destination>
    <opcode match=".*QFI: (\d+)" replace="🛣️ DL SDAP → QFI $1">sdap.qfi</opcode>
    <param match="RNTI: (\S+).*" replace="🆔 RNTI: $1">mac-nr.rnti</param>
    <param match="Bearer Id: (\d+)" replace="🚌 DRB: $1">pdcp-nr.bearer-id</param>
    <param match=".*PDU Type: (.+)" replace="📦 SDAP PDU: $1">sdap.pdu-type</param>
    <param match=".*RDI: (.+)" replace="🔁 RDI: $1">sdap.rdi</param>
    <param match=".*RQI: (.+)" replace="🔔 RQI: $1">sdap.rqi</param>
    <param match=".*Seq Num: (\d+)" replace="🔢 PDCP SN=$1">pdcp-nr.seq-num</param>
    <param match=".*Sequence Number: (\d+)" replace="📦 RLC SN=$1">rlc-nr.am.sn</param>
    <remark match=".*Frame Number: (\d+)" replace="💡 [Frame $1] DL SDAP Data PDU — user-plane on a DRB. The 1-byte SDAP header maps the IP payload to a QoS Flow (QFI) per TS 37.324; RDI/RQI drive reflective QoS. Requires deciphered PDCP (UE keys) to surface.">frame.number</remark>
  </message>

  <!-- UL SDAP Data PDU (UE→gNB) -->
  <message extends="mac-sched-context-nr" style="turquoise" priority="360">
    <source><address match=".*Uplink.*" replace="📱 UE">pdcp-nr.direction</address></source>
    <destination><address match=".*Uplink.*" replace="📡 gNB">pdcp-nr.direction</address></destination>
    <opcode match=".*QFI: (\d+)" replace="🔼 UL SDAP → QFI $1">sdap.qfi</opcode>
    <param match="RNTI: (\S+).*" replace="🆔 RNTI: $1">mac-nr.rnti</param>
    <param match="Bearer Id: (\d+)" replace="🚌 DRB: $1">pdcp-nr.bearer-id</param>
    <param match=".*PDU Type: (.+)" replace="📦 SDAP PDU: $1">sdap.pdu-type</param>
    <param match=".*Seq Num: (\d+)" replace="🔢 PDCP SN=$1">pdcp-nr.seq-num</param>
    <param match=".*Sequence Number: (\d+)" replace="📦 RLC SN=$1">rlc-nr.am.sn</param>
    <remark match=".*Frame Number: (\d+)" replace="💡 [Frame $1] UL SDAP Data PDU — user-plane on a DRB. The UL SDAP header carries D/C + the QFI the UE tagged the IP payload with (TS 37.324). Requires deciphered PDCP (UE keys) to surface.">frame.number</remark>
  </message>

  <!-- =============================================================== -->
  <!-- MAC-NR control elements (priority 200-250)                      -->
  <!-- =============================================================== -->

  <!-- Random Access Response (gNB→UE, Msg2 of 4-step RACH).           -->
  <!--                                                                 -->
  <!-- NR RAR carries a richer UL grant than LTE: separate frequency   -->
  <!-- (FRA) and time (TSA) resource allocations, MCS, TPC command,    -->
  <!-- frequency-hopping flag, and a CSI request bit. The UE uses      -->
  <!-- this grant for Msg3 (RRC Setup Request). Backoff Indicator (BI) -->
  <!-- subheaders signal RACH overload and tell the UE to back off     -->
  <!-- before retrying; surfaced separately when the dissector emits   -->
  <!-- mac-nr.rar.bi. Note: the 5g-nr-radio sample pcaps are all       -->
  <!-- UE-side captures recorded after RACH completion, so no RAR      -->
  <!-- frames are present here — this template fires on gNB-side or    -->
  <!-- pre-RACH captures (e.g. srsRAN_Project gNB mac-nr-pcap).        -->
  <message extends="mac-sched-context-nr" style="gold" bookmark="true" priority="250">
    <source><address match=".*" replace="📡 gNB">mac-nr.rar.subheader</address></source>
    <destination><address match=".*" replace="📱 UE">mac-nr.rar.subheader</address></destination>
    <opcode match=".*" replace="📨 RAR">mac-nr.rar.subheader</opcode>
    <param match="RNTI: (\S+).*" replace="🆔 RA-RNTI: $1">mac-nr.rnti</param>
    <param match=".*RAPID: (\d+)" replace="🎲 RAPID: $1">mac-nr.rar.rapid</param>
    <param match=".*Timing Advance: (\d+)" replace="⏱️ TA: $1">mac-nr.rar.ta</param>
    <param match="Temporary C-RNTI: (\S+).*" replace="🆕 Temp C-RNTI: $1">mac-nr.rar.temp_crnti</param>
    <param match=".*Frequency hopping flag: (\d+)" replace="🦘 Hop: $1">mac-nr.rar.grant.hopping</param>
    <param match=".*Msg3 PUSCH frequency resource allocation: (\d+)" replace="📦 FRA: $1">mac-nr.rar.grant.fra</param>
    <param match=".*Msg3 PUSCH time resource allocation: (\d+)" replace="📅 TSA: $1">mac-nr.rar.grant.tsa</param>
    <param match=".*MCS: (\d+)" replace="📈 MCS: $1">mac-nr.rar.grant.mcs</param>
    <param match=".*TPC command for Msg3 PUSCH: (.+)" replace="🔋 TPC: $1">mac-nr.rar.grant.tcsp</param>
    <param match=".*CSI request: (\d+)" replace="📊 CSI-req: $1">mac-nr.rar.grant.csi</param>
    <param match=".*Backoff Indicator: (\d+)" replace="🛑 BI: $1">mac-nr.rar.bi</param>
    <remark match=".*Frame Number: (\d+)" replace="💡 [Frame $1] Random Access Response (Msg2) — gNB replies to the PRACH preamble. Carries Temporary C-RNTI, Timing Advance, and the UL grant the UE will use for Msg3 (FRA = frequency PRBs, TSA = PUSCH time-domain slot, MCS = modulation/coding index). A Backoff Indicator subheader (no RAPID) tells the UE to back off before retrying PRACH.">frame.number</remark>
  </message>

  <!-- UE Contention Resolution Identity (gNB→UE, MAC CE in Msg4) -->
  <message extends="mac-sched-context-nr" style="forest-green" bookmark="true" priority="250">
    <source><address match=".*" replace="📡 gNB">mac-nr.control.ue-contention-resolution.identity</address></source>
    <destination><address match=".*" replace="📱 UE">mac-nr.control.ue-contention-resolution.identity</address></destination>
    <opcode match=".*" replace="🤝 Contention Resolution">mac-nr.control.ue-contention-resolution.identity</opcode>
    <param match="RNTI: (\S+).*" replace="🆔 RNTI: $1">mac-nr.rnti</param>
    <param match="UE Contention Resolution Identity: (.*)" replace="🔑 CRI: $1">mac-nr.control.ue-contention-resolution.identity</param>
    <remark match=".*Frame Number: (\d+)" replace="💡 [Frame $1] Msg4 UE Contention Resolution — gNB echoes the UE's Msg3 CCCH SDU; the matching UE wins RACH contention and promotes Temporary C-RNTI to C-RNTI">frame.number</remark>
  </message>

  <!-- Short BSR (UE→gNB) -->
  <message extends="mac-sched-context-nr" style="amethyst" priority="200">
    <source><address match=".*" replace="📱 UE">mac-nr.control.bsr.short.lcg</address></source>
    <destination><address match=".*" replace="📡 gNB">mac-nr.control.bsr.short.lcg</address></destination>
    <opcode match=".*" replace="📊 Short BSR">mac-nr.control.bsr.short.lcg</opcode>
    <param match="RNTI: (\S+).*" replace="🆔 RNTI: $1">mac-nr.rnti</param>
    <param match=".*LCG: (\d+)" replace="🪣 LCG: $1">mac-nr.control.bsr.short.lcg</param>
    <!-- BS index inherited via mac-sched-context-nr (📊 BSR LCG0 idx). -->
    <remark match=".*Frame Number: (\d+)" replace="💡 [Frame $1] Short Buffer Status Report — UE reports queued UL data for one Logical Channel Group">frame.number</remark>
  </message>

  <!-- Long / Long Truncated BSR (UE→gNB). LCID 60 = Long Truncated, LCID 62 = Long. -->
  <message extends="mac-sched-context-nr" style="amethyst" priority="200">
    <source><address match=".*" replace="📱 UE">mac-nr.control.bsr.long.lcg0</address></source>
    <destination><address match=".*" replace="📡 gNB">mac-nr.control.bsr.long.lcg0</address></destination>
    <opcode match=".*LCID: (.+) \(\d+\)" replace="📊 $1">mac-nr.ulsch.lcid</opcode>
    <param match="RNTI: (\S+).*" replace="🆔 RNTI: $1">mac-nr.rnti</param>
    <param match=".*LCG0: (.+)" replace="🪣 LCG0: $1">mac-nr.control.bsr.long.lcg0</param>
    <param match=".*LCG1: (.+)" replace="🪣 LCG1: $1">mac-nr.control.bsr.long.lcg1</param>
    <param match=".*LCG2: (.+)" replace="🪣 LCG2: $1">mac-nr.control.bsr.long.lcg2</param>
    <param match=".*LCG3: (.+)" replace="🪣 LCG3: $1">mac-nr.control.bsr.long.lcg3</param>
    <param match=".*LCG4: (.+)" replace="🪣 LCG4: $1">mac-nr.control.bsr.long.lcg4</param>
    <param match=".*LCG5: (.+)" replace="🪣 LCG5: $1">mac-nr.control.bsr.long.lcg5</param>
    <param match=".*LCG6: (.+)" replace="🪣 LCG6: $1">mac-nr.control.bsr.long.lcg6</param>
    <param match=".*LCG7: (.+)" replace="🪣 LCG7: $1">mac-nr.control.bsr.long.lcg7</param>
    <!-- Per-LCG BS indices inherited via mac-sched-context-nr (LCG0..7). -->
    <remark match=".*Frame Number: (\d+)" replace="💡 [Frame $1] Long (Truncated) Buffer Status Report — UE reports per-LCG buffer occupancy across up to 8 Logical Channel Groups">frame.number</remark>
  </message>

  <!-- Single-Entry PHR (UE→gNB) -->
  <message extends="mac-sched-context-nr" style="amethyst" priority="200">
    <source><address match=".*" replace="📱 UE">mac-nr.control.se-phr.ph</address></source>
    <destination><address match=".*" replace="📡 gNB">mac-nr.control.se-phr.ph</address></destination>
    <opcode match=".*" replace="📶 Single-Entry PHR">mac-nr.control.se-phr.ph</opcode>
    <param match="RNTI: (\S+).*" replace="🆔 RNTI: $1">mac-nr.rnti</param>
    <param match=".*Power Headroom: (.+) \((\d+)\)" replace="📶 PH: $1 [idx $2]">mac-nr.control.se-phr.ph</param>
    <param match=".*Pcmax,c,f: (.+) \((\d+)\)" replace="🔋 Pcmax: $1 [idx $2]">mac-nr.control.se-phr.pcmax_f_c</param>
    <param match="PHR Type2 other cell PHR: (.*)" replace="📡 Type2 other-cell: $1">mac-nr.type2-other-cell</param>
    <remark match=".*Frame Number: (\d+)" replace="💡 [Frame $1] Power Headroom Report — UE reports transmit power margin remaining below Pcmax">frame.number</remark>
  </message>

  <!-- =============================================================== -->
  <!-- PDCP-NR PDUs (priority 280) — fire above MAC CE (200) so frames -->
  <!-- carrying an SDU + piggybacked BSR/PHR are labeled as PDCP, with -->
  <!-- the MAC CE info attached as optional params.                    -->
  <!-- =============================================================== -->

  <!-- DL PDCP PDU (gNB→UE) -->
  <message extends="mac-sched-context-nr" style="steel-blue" priority="280">
    <source><address match=".*Downlink.*" replace="📡 gNB">pdcp-nr.direction</address></source>
    <destination><address match=".*Downlink.*" replace="📱 UE">pdcp-nr.direction</address></destination>
    <opcode match=".*Seq Num: (\d+)" replace="🔽 DL PDCP SN=$1">pdcp-nr.seq-num</opcode>
    <param match="RNTI: (\S+).*" replace="🆔 RNTI: $1">mac-nr.rnti</param>
    <param match="Bearer Id: (\d+)" replace="🚌 Bearer: $1">pdcp-nr.bearer-id</param>
    <param match="Plane: (.+) \(\d+\)" replace="🛣️ Plane: $1">pdcp-nr.plane</param>
    <param match="Seqnum length: (\d+)" replace="🔢 PDCP SN size: $1-bit">pdcp-nr.seqnum_length</param>
    <param match="MAC: (\S+)" replace="🛡️ MAC-I: $1">pdcp-nr.mac</param>
    <param match=".*calculated (\S+) but found (\S+)" replace="❌ MAC-I check: wrong (calc=$1, got=$2)">pdcp-nr.maci-wrong</param>
    <param match="UE Security \(ciphering=(\S+) \(\S+\), integrity=(\S+) \(\S+\)\)" replace="🔐 Sec: $1/$2">pdcp-nr.security-config</param>
    <param match="COUNT: (\d+)" replace="🔢 PDCP COUNT: $1">pdcp-nr.security-config.count</param>
    <param match=".*SN (\d+) Repeated.*" replace="🔁 PDCP retx: SN $1">pdcp-nr.sequence-analysis</param>
    <param match="Previous frame for Bearer: (\d+)" replace="↪️ Prev frame: $1">pdcp-nr.sequence-analysis.previous-frame</param>
    <param match=".*Sequence Number: (\d+)" replace="📦 RLC SN=$1">rlc-nr.am.sn</param>
    <param match=".*Polling Bit: (.+)" replace="❓ RLC Poll: $1">rlc-nr.am.p</param>
    <param match="Segment Offset: (\d+)" replace="✂️ RLC SO: $1">rlc-nr.am.so</param>
    <param match="Fragment count: (\d+)" replace="🧩 SDU fragments: $1">rlc-nr.fragment.count</param>
    <!-- BSR LCG0..7 indices inherited via mac-sched-context-nr. -->
    <param match=".*Power Headroom: (.+) \((\d+)\)" replace="📶 PHR: $1 [idx $2]">mac-nr.control.se-phr.ph</param>
    <param match=".*Pcmax,c,f: (.+) \((\d+)\)" replace="🔋 Pcmax: $1 [idx $2]">mac-nr.control.se-phr.pcmax_f_c</param>
    <param match="PHR Type2 other cell PHR: (.*)" replace="📡 PHR Type2 other-cell: $1">mac-nr.type2-other-cell</param>
    <remark match=".*Frame Number: (\d+)" replace="💡 [Frame $1] DL PDCP PDU — gNB→UE on a configured radio bearer. After AS security activates, the inner SDU is ciphered and integrity-protected">frame.number</remark>
  </message>

  <!-- UL PDCP PDU (UE→gNB) -->
  <message extends="mac-sched-context-nr" style="steel-blue" priority="280">
    <source><address match=".*Uplink.*" replace="📱 UE">pdcp-nr.direction</address></source>
    <destination><address match=".*Uplink.*" replace="📡 gNB">pdcp-nr.direction</address></destination>
    <opcode match=".*Seq Num: (\d+)" replace="🔼 UL PDCP SN=$1">pdcp-nr.seq-num</opcode>
    <param match="RNTI: (\S+).*" replace="🆔 RNTI: $1">mac-nr.rnti</param>
    <param match="Bearer Id: (\d+)" replace="🚌 Bearer: $1">pdcp-nr.bearer-id</param>
    <param match="Plane: (.+) \(\d+\)" replace="🛣️ Plane: $1">pdcp-nr.plane</param>
    <param match="Seqnum length: (\d+)" replace="🔢 PDCP SN size: $1-bit">pdcp-nr.seqnum_length</param>
    <param match="MAC: (\S+)" replace="🛡️ MAC-I: $1">pdcp-nr.mac</param>
    <param match=".*calculated (\S+) but found (\S+)" replace="❌ MAC-I check: wrong (calc=$1, got=$2)">pdcp-nr.maci-wrong</param>
    <param match="UE Security \(ciphering=(\S+) \(\S+\), integrity=(\S+) \(\S+\)\)" replace="🔐 Sec: $1/$2">pdcp-nr.security-config</param>
    <param match="COUNT: (\d+)" replace="🔢 PDCP COUNT: $1">pdcp-nr.security-config.count</param>
    <param match=".*SN (\d+) Repeated.*" replace="🔁 PDCP retx: SN $1">pdcp-nr.sequence-analysis</param>
    <param match="Previous frame for Bearer: (\d+)" replace="↪️ Prev frame: $1">pdcp-nr.sequence-analysis.previous-frame</param>
    <param match=".*Sequence Number: (\d+)" replace="📦 RLC SN=$1">rlc-nr.am.sn</param>
    <param match=".*Polling Bit: (.+)" replace="❓ RLC Poll: $1">rlc-nr.am.p</param>
    <param match="Segment Offset: (\d+)" replace="✂️ RLC SO: $1">rlc-nr.am.so</param>
    <param match="Fragment count: (\d+)" replace="🧩 SDU fragments: $1">rlc-nr.fragment.count</param>
    <!-- BSR LCG0..7 indices inherited via mac-sched-context-nr. -->
    <param match=".*Power Headroom: (.+) \((\d+)\)" replace="📶 PHR: $1 [idx $2]">mac-nr.control.se-phr.ph</param>
    <param match=".*Pcmax,c,f: (.+) \((\d+)\)" replace="🔋 Pcmax: $1 [idx $2]">mac-nr.control.se-phr.pcmax_f_c</param>
    <param match="PHR Type2 other cell PHR: (.*)" replace="📡 PHR Type2 other-cell: $1">mac-nr.type2-other-cell</param>
    <remark match=".*Frame Number: (\d+)" replace="💡 [Frame $1] UL PDCP PDU — UE→gNB on a configured radio bearer. After AS security activates, the inner SDU is ciphered and integrity-protected">frame.number</remark>
  </message>

  <!-- =============================================================== -->
  <!-- Generic MAC PDUs (priority 100) — fallback for unmatched LCIDs  -->
  <!-- =============================================================== -->

  <!-- Downlink (gNB→UE) — generic DL-SCH MAC PDU -->
  <message extends="mac-sched-context-nr" style="royal-blue" priority="100">
    <source><address match=".*Downlink.*" replace="📡 gNB">mac-nr.direction</address></source>
    <destination><address match=".*Downlink.*" replace="📱 UE">mac-nr.direction</address></destination>
    <opcode match=".*LCID: (.+) \((\d+)\)" replace="📥 DL LCID $2 ($1)">mac-nr.dlsch.lcid</opcode>
    <param match="RNTI: (\S+).*" replace="🆔 RNTI: $1">mac-nr.rnti</param>
    <param match="Subheader: \((.*)\)" replace="📦 Subheader: $1">mac-nr.subheader</param>
    <param match="SDU Length: (.*)" replace="📏 SDU len: $1">mac-nr.subheader.sdu-length</param>
    <remark match=".*Frame Number: (\d+)" replace="💡 [Frame $1] DL-SCH MAC PDU — gNB→UE on PDSCH (generic fallback for LCIDs not matched by a higher-priority template)">frame.number</remark>
  </message>

  <!-- Uplink (UE→gNB) — generic UL-SCH MAC PDU -->
  <message extends="mac-sched-context-nr" style="coral" priority="100">
    <source><address match=".*Uplink.*" replace="📱 UE">mac-nr.direction</address></source>
    <destination><address match=".*Uplink.*" replace="📡 gNB">mac-nr.direction</address></destination>
    <opcode match=".*LCID: (.+) \((\d+)\)" replace="📤 UL LCID $2 ($1)">mac-nr.ulsch.lcid</opcode>
    <param match="RNTI: (\S+).*" replace="🆔 RNTI: $1">mac-nr.rnti</param>
    <param match="Subheader: \((.*)\)" replace="📦 Subheader: $1">mac-nr.subheader</param>
    <param match="SDU Length: (.*)" replace="📏 SDU len: $1">mac-nr.subheader.sdu-length</param>
    <remark match=".*Frame Number: (\d+)" replace="💡 [Frame $1] UL-SCH MAC PDU — UE→gNB on PUSCH (generic fallback for LCIDs not matched by a higher-priority template)">frame.number</remark>
  </message>

</FXT>
