# RT-4D Standby / Main (Home) Screen — Render Map & Rewrite Recipe

**Key:** `main-screen` · **Target:** `stock-fw/rt4d_stock_v3.25_abs_0x08002800.bin` (ARM Cortex‑M4F Thumb, load/vaddr base `0x08002800`). All disassembly via capstone `CS_ARCH_ARM + CS_MODE_THUMB`.

This document reverse‑engineers the **standby / operating (VFO or channel) home screen** — the first UI redesign target. It ties into the three prior docs: geometry & draw primitives in **`display.md`**, the state machine & hook points in **`main-loop.md`**, and keys in **`input.md`**. Read those for the low‑level draw API and the superloop hook; this doc covers *what the home screen paints, from which data, at which coordinates, and how to replace it.*

---

## 0. TL;DR

- The home screen is **screen id 0** (`g_screen @0x200008B3 == 0`). Its **draw handler is `home_draw @0x08013BD8`** (dispatched from `ui_draw_dispatch @0x0801E1BC`'s `tbb` table). An **alternate home / dual‑display variant is id 7 → `0x08013804`**.
- Home render is **two‑stage and data‑driven**, not hard‑coded:
  1. **`format_area_display(area, hi, shift) @0x08011414`** reads the live **channel cache** at `0x20002DEA` (48‑byte records) + the per‑area **mode table** at `0x20002DCF`, formats frequency / channel‑name / mode‑tag ASCII into the **display struct `g_disp @0x200009C3`**, and sets **dirty flags** in `g_dirty @0x200024EB`.
  2. **`home_draw @0x08013BD8`** consumes `g_disp` + `g_dirty` and calls the `display.md` primitives (`draw_string`, `draw_number_row`, `draw_str_spaced`, `draw_marker5`) to paint each *changed* element.
- **Redraw is incremental / dirty‑flag driven.** An element repaints only when its byte in `g_dirty @0x200024EB` is non‑zero; setting a flag (e.g. after a channel/area change, RX event, or key) triggers repaint on the next UI tick. No full‑frame clear.
- **The single struct you must understand is `g_disp @0x200009C3`.** Every on‑screen string/flag is a field of it (see §3). To replace the home screen, either (a) keep `format_area_display` and re‑lay‑out in your own `home_draw`, or (b) ignore both and render straight from the cache `0x20002DEA` + radio getters. Both keep the SPI codeplug format and CPS serial protocol **untouched** (they only *read* cache RAM and call display primitives).

---

## 1. Where the home screen is dispatched from

Chain (from `main-loop.md`, confirmed): superloop `app_main @0x0802136C` → normal UI tick `ui_tick_normal @0x080207DC` → **`bl ui_draw_dispatch @0x0801E1BC` at call‑site `0x080207E6`**. `ui_draw_dispatch` `tbb`‑jumps on `g_screen @0x200008B3`; **index 0 → `home_draw @0x08013BD8`**, index 7 → `0x08013804` (alt/dual home). The mirror input `tbb` (`ui_process_key @0x0801E6EC`) routes id 0 → key handler `0x08017F60`.

| screen id | draw handler | content struct | dirty‑flag array | role |
|---:|---|---|---|---|
| **0** | **`home_draw 0x08013BD8`** | **`g_disp 0x200009C3`** | **`g_dirty 0x200024EB`** | **primary standby (VFO/channel) home** |
| 7 | `0x08013804` | `0x2000096A` | `0x200009BD` | alt home / dual‑display variant |
| 2 | `0x08013980` | `0x200008D0` | `0x200008F5` | standby‑key / list overlay |

> **Hook (recommended, from `main-loop.md` Option A):** redirect the `bl` at `0x080207E6` to `my_draw_router`; render your own home there. Everything else (RF, DMR RX, scan, battery, and the entire serial/CPS + SPI region‑write path) is preserved. 2‑instruction cut.

---

## 2. The render pipeline (data → struct → pixels)

```
 SPI codeplug channels (0x004000, 48B recs)   ← format on flash, DO NOT CHANGE
        │  loaded/cached by channel-load code (writes 0x20002DEA)
        ▼
 g_chcache @0x20002DEA   : live channel record cache, 48-byte (0x30) stride, [area]
 g_chmode  @0x20002DCF   : per-area display-mode bytes  (= g_chcache − 0x1B)
        │
        ▼  format_area_display(area, hi, shift)  @0x08011414   (4 call sites)
        │     reads freq/tones/power/mode from cache, formats ASCII
        ▼
 g_disp @0x200009C3      : DISPLAY STRUCT (freq/name strings, mode flags, TG, markers)
        │     + sets element dirty flags in g_dirty @0x200024EB
        ▼  home_draw @0x08013BD8   (dispatched every UI tick for screen 0)
        │     paints only elements whose g_dirty byte != 0
        ▼
 draw_string / draw_number_row / draw_str_spaced / draw_marker5  (display.md prims)
        ▼  lcd_set_pos + lcd_write_col  →  direct-to-GDDRAM (no framebuffer)
```

### 2.1 `format_area_display(u8 area, u8 hi, u8 shift) @0x08011414` — the formatter

Prototype (AAPCS): `void format_area_display(u8 area /*r0*/, u8 hi /*r1→r5*/, u8 shift /*r2→r8*/)`.
- `area` (r4) = which VFO/area (0 = A, 1 = B) — indexes the cache `0x20002DEA + area*0x30`.
- `hi` (r5) = highlight/selection state (0,1,2,3) → stored to `g_disp+0x1D`; `2` selects an alt copy path.
- `shift` (r8) = TX‑shift display mode: `0`=simplex/RX shown, `1`=+shift, `2`=−shift/reverse (Talkaround/Reverse). Selects which of RX(`+5`)/TX(`+9`) freq words is shown.

Reads (per 48‑byte cache record at `0x20002DEA + area*0x30`):

| cache off | field | used for |
|---:|---|---|
| `[+0]` | flags; **`>>6` = mode** (1 ⇒ DMR/digital, else analog) | "DMR"/"ANA" tag, "A-"/"D-" prefix |
| `[+5]` | u32 **RX frequency** (`MHz×100000`, `FREQ_MULTIPLIER`) | main freq digits |
| `[+9]` | u32 **TX frequency** | shown when `shift`≠0 / reverse |
| `[+0xD]` | u12 (`ubfx #0,#0xC`) **RX tone** (CTCSS/DCS) | tone field |
| `[+0xE]>>4` | nibble | power/flag |
| `[+0xF]` | u12 **TX tone** | tone field |
| `[+0x10]>>4` | nibble | flag |
| `[+0x20]` | 16‑byte **channel name** (ASCII) | name display (mode==2) |

Also reads globals: `g_power @0x20000B76` → `g_disp+0x1C` (the "HD"/status flag byte); `g_chmode @0x20002DCF + area` → `g_disp+0x1E` (element mode), `[+2+area]` → `g_disp+0x1F` (sub‑mode).

Writes into `g_disp @0x200009C3` (see §3) and builds ASCII via helpers:
- **`num_to_ascii(uint val, int ndigits) @0x08018530`** — right‑aligns `ndigits` decimal ASCII into scratch **`0x200024D0`**. Used to render the frequency (`freq`, 8 digits) and channel number.
- **`str_insert_char(buf, ch, pos, len) @0x080135A8`** — inserts `ch` at `pos` (shifting right). Used to splice the **"." decimal point** into the frequency string (`ch=0x2E` at pos 3) → `"438.80000"`.
- **`memcpy_off(dst, src, dstoff, len) @0x080062EC`** — `dst[dstoff+i]=src[i]`. The workhorse for copying templates / name / formatted number into `g_disp` fields.

Inline ASCII templates embedded in the formatter (decoded): `"CH MODE"`, `"VFO MODE"`, `"VFO MODE-A"`, `"VFO MODE-B"`, `"CH-"`, `"A-"` (analog), `"D-"` (digital), `"ANA"`, `"DMR"`. These are the literal strings the stock home screen shows in the mode/status line.

Called from 4 sites (`0x080033D2`, `0x08004F60`, `0x08008F28`, `0x0800951C`) — always after a state change (area switch, channel change, mode toggle). Example call: `0x0800951C` → `format_area_display(area=[0x20000B5F], hi=2, shift=[0x200029BB+0x67])`.

### 2.2 `home_draw @0x08013BD8` — the painter

Consumes `g_disp @0x200009C3` and per‑element dirty flags `g_dirty @0x200024EB`. For each element: `if (g_dirty[k]) draw…`. Ends `pop {…pc}` at `0x08013E50`. Row constants used: `r4=1` (top status page pair), `r5=2` (Area A page pair), `r6=5` (Area B page pair) — but these are re‑derived from `g_disp+0x42` (active area) at entry so the *selected* area gets the larger/highlighted rows. Uses `mode` arg to `draw_string` = normal(0)/inverse(1) for highlight.

---

## 3. `g_disp @0x200009C3` — the display struct (field map)

Two parallel area sub‑records of stride **0x43** (Area A at `+0x00`, Area B at `+0x43`), plus shared/status fields. Offsets below are from `0x200009C3` (Area A); add `0x43` for Area B. All confirmed from `format_area_display` writes + `home_draw` reads.

| off | type | name | meaning / source | drawn by (element) |
|---:|---|---|---|---|
| `+0x00` | char[16] | `area_line0` | Area A top text (name/tag line) | `draw_str_spaced` (flag+0) |
| `+0x15` | char[7] | `line_prefix` | `"CH-"`+num or `"A-"/"D-"` mode tag | `draw_str_spaced`, x=0x68 (flag+6) |
| `+0x1C` | u8 | `status_flag` | ← `g_power @0x20000B76`; drives "HD"/"  " top‑right indicator | `draw_str_spaced` x=0x74, template `"HD"`/`"  "` (flag+2) |
| `+0x1D` | u8 | `hiliteA` | highlight state (r5) for Area A frame | `draw_frame_corners` (0x08008224) |
| `+0x1E` | u8 | `modeA` | **element mode**: 0 = blank, 1 = **frequency**, 2 = **channel name** | selects freq vs name path |
| `+0x1F` | u8 | `submodeA` | sub‑mode: 0/1 name width, 2 = big‑digit freq | picks big_char vs string |
| `+0x20` | char[16] | `contentA` | **the main Area A string** (formatted freq `"438.80000"` OR channel name OR `"CH MODE"`/`"VFO MODE"`) | `draw_number_row`(big digits) / `draw_string` on row r5 (flag+7) |
| `+0x30` | u8 | `modeB` | Area B element mode (0/1/2) | (flag+5) |
| `+0x31` | u8 | `submodeB` | Area B sub‑mode | |
| `+0x32` | char[16] | `contentB` | Area B main string | `draw_string` row r6 (flag+5) |
| `+0x42` | u8 | `active_area` | 0 = A selected, else B — chooses which rows get big/highlight | branches at handler entry |
| `+0x43…` | — | Area B record | mirror of `+0x00…` at stride 0x43 | (rendered by sibling fn `0x08013E64`) |
| `+0x63` | u8 | `ab_marker` | 0/1/2 A/B right‑edge marker state ← `g_active_area 0x20000B5F` | `draw_marker5` pages 2 & 5 (flag+7 preamble) |
| `+0x64` | u8 | `batt_marker` | 0/1 → battery/dual marker layout | `draw_marker5` pages 1/3/6 (flag+8) |

Additional companion buffer **`g_disp2 @0x20000A28`** holds the **mode‑tag / secondary line** ("ANA"/"DMR", "A-"/"D-") built alongside `contentA` — drawn on the status row.

> **`draw_marker5(page, on) @0x0800870C`** (aka the "batt/arrow icon" in `display.md`): draws a 5‑column glyph at columns **123–127** (`0x7B+i`). Pattern bytes `30 78 FC 30 30` = a right‑pointing arrowhead → the **A/B active‑area selector arrows** on the far right edge. `draw_icon_batt @0x08008874` is the structurally‑identical sibling used for the battery/dual markers.

---

## 4. On‑screen element → data source → draw call (the master table)

Coordinates: `page` = 8‑px row band (0=top…7), `x` = pixel column (0…127). Rows: top status ≈ page 1, Area A ≈ pages 2–3, Area B ≈ pages 5–6 (exact page passed in `r4/r5/r6`, re‑ordered by `active_area`).

| # | Element (what you see) | Data source | Draw call (page,x) | Dirty flag |
|---|---|---|---|---|
| 1 | **Area A main line** — frequency `"438.80000"` (big digits) *or* channel name *or* `"CH MODE"/"VFO MODE"` | `g_disp+0x20` ← `format_area_display` from cache `0x20002DEA[area]` (`+5` freq / `+0x20` name) & `modeA=g_disp+0x1E` | `draw_number_row(r5,0x14,…,8|7)` big digits **or** `draw_string(r5,0x64,…,2)` | `g_dirty[7]` |
| 2 | **Area B main line** (dual‑watch second area) | `g_disp+0x32` ← same formatter, `area=1` | `draw_string(r6,0x0A/0x27/0x1C,…)` per `modeB=g_disp+0x30` | `g_dirty[5]` |
| 3 | **Top name/tag line** (radio name / area label) | `g_disp+0x00` | `draw_str_spaced(r4,0x00,…,16)` | `g_dirty[0]` |
| 4 | **Channel # / A‑/D‑ prefix** (`"CH-001"`, `"A-"`, `"D-"`) | `g_disp+0x15` ← templates `"CH-"/"A-"/"D-"` + `num_to_ascii` | `draw_str_spaced(r4,0x68,…,4)` | `g_dirty[6]` |
| 5 | **Mode tag** `"ANA"` / `"DMR"` (FM/AM/SSB vs digital) | `g_disp2 0x20000A28` ← `cache[+0]>>6` (mode bits) | `memcpy_off`+`draw_string` on status row | (with #4) |
| 6 | **HD / status indicator** (top‑right) | `g_disp+0x1C` ← `g_power @0x20000B76`; template `"HD"`/`"  "` | `draw_str_spaced(r4,0x74,…,2)` | `g_dirty[2]` |
| 7 | **A/B selector arrows** (right edge, col 123) | `g_disp+0x63` ← `g_active_area 0x20000B5F`; `g_disp+0x64` | `draw_marker5(2,on)` & `draw_marker5(5,on)`; batt markers pages 1/3/6 | `g_dirty[7]`,`[8]` |
| 8 | **Area A highlight frame** | `g_disp+0x1D` (hilite) & `+0x42` | `draw_frame_corners(page,x) 0x08008224` | `g_dirty[3]` |
| 9 | **CTCSS/DCS tone, power nibble** | cache `[+0xD]`/`[+0xF]` (12‑bit tone), `[+0xE]`/`[+0x10]` (nibble) | folded into `contentA`/`g_disp2` via formatter | (with #1) |
| 10 | **DMR TG / Contact / Color‑Code (RX)** | DMR call ctx `g_call @0x2000A6C5` (`+1`=call type, `+2`=ID, `+0x38`=name), fed from cache & FM100B | built by the call‑info path near `0x08006754`; painted on Area line as name/ID | RX‑event driven |
| 11 | **Battery / signal icons** | see `display.md` §draw_icon_batt/signal (cols 21 & 123); battery volts from ADC1 `0x08010960` | `draw_icon_batt`/`draw_icon_signal` | period‑gated task |

Notes:
- **FM / AM / SSB vs DMR**: the coarse analog/digital split is `cache[+0]>>6` → `"ANA"`/`"DMR"`. The fine analog demod (FM/AM/SSB) is a per‑channel field (`RX Demod (A)` menu) stored in the channel record and shown via the same status‑line path; it is *not* a separate draw primitive.
- **RX/TX arrow**: TX vs RX display is driven by the `shift` arg (Talkaround/Reverse) selecting cache `[+5]` (RX) vs `[+9]` (TX); the on‑air TX indicator is a status‑flag element, not a distinct routine.
- **Dual‑watch A/B**: `active_area (g_disp+0x42)` and `ab_marker (+0x63)` decide which area is highlighted and where the arrows point; `g_active_area @0x20000B5F` is the master.

---

## 5. Key RAM state variables (home screen)

| addr | width | name | role |
|---|---|---|---|
| `0x200009C3` | struct | `g_disp` | **home display struct** — all home strings/flags (§3). Area A `+0`, Area B `+0x43`. |
| `0x200024EB` | u8[9+] | `g_dirty` | per‑element **dirty flags**; element k paints iff `g_dirty[k]!=0`. Indices used: 0,2,3,4,5,6,7,8. |
| `0x20002DEA` | rec[N] | `g_chcache` | **live channel cache**, 48‑byte (`0x30`) stride; `[+5]`RXfreq `[+9]`TXfreq `[+0xD/0xF]`tones `[+0x20]`name. Mirrors codeplug channel record — **read‑only for UI**. |
| `0x20002DCF` | u8[] | `g_chmode` | per‑area display mode/sub‑mode bytes (`= g_chcache − 0x1B`). |
| `0x20000B5F` | u8 | `g_active_area` | selected VFO/area index (0=A). |
| `0x20000B76` | u8 | `g_power`/status | drives `g_disp+0x1C` (HD indicator). |
| `0x200024D0` | u8[16] | `g_numbuf` | scratch for `num_to_ascii` (freq/number ASCII build). |
| `0x2000A6C5` | struct | `g_call` | DMR RX call ctx: `+1` call type, `+2` ID (BCD‑decoded via `0x080112B8`), `+0x38` name. |
| `0x200029BB` | struct | `g_uictx2` | secondary UI context (`+0x63`,`+0x67`,`+0x197` read by formatter callers). |
| `0x200008B3` | u8 | `g_screen` | screen id; 0 = this home screen. |

---

## 6. Redraw cadence & triggers

- **Cadence:** `home_draw` is called **every UI tick** (unthrottled) from `ui_tick_normal @0x080207DC` via `ui_draw_dispatch @0x0801E1BC` at `0x080207E6`, but each element is gated by its `g_dirty` byte, so a steady screen costs ~nothing.
- **Triggers (what sets `g_dirty`):**
  - **Key / channel / area change** — the standby‑key handler path (e.g. `0x080094F8`) calls `format_area_display`, then sets `g_dirty[7]=1` (`0x080095DA`) and `g_disp+0x63 = g_active_area`, forcing Area A repaint next tick. Other setters at `0x08005DA0`, `0x0800975E`.
  - **Screen entry** — `ui_draw_dispatch` preamble `clear_dirty_flags @0x080136E4` walks a companion dirty row `0x20002512` and force‑marks fields dirty on entry (full repaint on screen switch).
  - **RX / DMR event** — updates `g_call @0x2000A6C5` and re‑runs the formatter → dirties the affected area.
  - **Periodic** — battery/signal icons and RSSI are repainted by the slower period‑gated tasks in `ui_tick_normal` (counters `0x20000BF5/BF6/BF8`), independent of `g_dirty`.
- **Model:** direct‑to‑GDDRAM (no framebuffer, `display.md` §1). Partial repaint is safe because elements occupy fixed, non‑overlapping regions.

---

## 7. Recipe to REPLACE the home screen

### 7.1 Hook point
Redirect the draw dispatch (`main-loop.md` Option A): patch `bl 0x0801E1BC` at **`0x080207E6`** → `bl my_draw_router`. In `my_draw_router`, `if (g_screen==0) my_home(); else <call stock 0x0801E1BC for other screens>`. (Symmetrically patch key dispatch `0x08020816` if you want custom home navigation — see `input.md`.)

### 7.2 Two build strategies

**Strategy A — reuse stock formatter, re‑lay‑out only (least work).**
Keep calling `format_area_display(area, hi, shift) @0x08011414` (it does the codeplug→ASCII math for you), then read the ready‑made strings from `g_disp @0x200009C3` and paint them wherever you like with `draw_string`. You get frequency/name/mode formatting for free and never touch the cache or codeplug.

```c
// stock entry points (thumb; set bit0 when taking a function pointer)
void format_area_display(u8 area,u8 hi,u8 shift);   // 0x08011414 — fills g_disp
void draw_string(u8 pg,u8 x,const char*s,u8 n,u8 mode); // 0x08008A50
void draw_number_row(u8 pg,u8 x,const u8*d,u16 n,u8 mode);// 0x08008B90 (big freq digits)
#define G_DISP ((volatile u8*)0x200009C3)

void my_home(void){
    format_area_display(g_active_area,2,0);          // stock builds strings into g_disp
    for(u8 pg=0;pg<8;pg++) blit_cols(pg,128,0,0);    // clear (display.md §2.1)
    draw_string(0, 2, (char*)&G_DISP[0x00], 16, 0);  // top line
    draw_string(2, 4, (char*)&G_DISP[0x20], 16, 0);  // Area A main (freq/name)
    draw_string(5, 4, (char*)&G_DISP[0x32], 16, 0);  // Area B main
    // add your own layout, icons, TG/CC from g_call@0x2000A6C5, battery, etc.
}
```

**Strategy B — full custom, read straight from the cache (max control).**
Skip `g_disp` entirely. Read the live channel cache and radio getters yourself:

```c
#define CH(area)  ((volatile u8*)(0x20002DEA + (area)*0x30))
u32 rx = *(u32*)(CH(area)+5);        // RX freq, MHz*100000
u32 tx = *(u32*)(CH(area)+9);        // TX freq
u16 rtone = *(u16*)(CH(area)+0xD) & 0x0FFF;   // RX CTCSS/DCS
u8  isDMR = CH(area)[0] >> 6;        // 1 => digital
char*name = (char*)(CH(area)+0x20);  // 16-byte channel name
// DMR RX overlay:
#define CALL ((volatile u8*)0x2000A6C5)  // +1 type, +2 id, +0x38 name
// battery volts: call 0x08010960 (raw*4/0x42 -> tenths of a volt)
```
Format freq with `num_to_ascii(rx,8) @0x08018530` (→ `0x200024D0`) then `str_insert_char(buf,'.',3,len) @0x080135A8`, or roll your own. Paint with the `display.md` primitives.

### 7.3 Data getters + draw primitives your home screen will call

| purpose | vaddr | prototype |
|---|---|---|
| build all home strings (opt.) | `0x08011414` | `format_area_display(u8 area,u8 hi,u8 shift)` |
| decimal ASCII | `0x08018530` | `num_to_ascii(uint val,int ndigits)` → `0x200024D0` |
| insert char (decimal pt) | `0x080135A8` | `str_insert_char(char*buf,char ch,int pos,int len)` |
| byte copy w/ offset | `0x080062EC` | `memcpy_off(void*dst,const void*src,int dstoff,int len)` |
| text | `0x08008A50` | `draw_string(pg,x,s,n,mode)` |
| big freq digits | `0x08008B90` | `draw_number_row(pg,x,digits,n,mode)` |
| spaced small text | `0x08008BC6` | `draw_str_spaced(pg,x,s,n,mode)` |
| A/B arrow / batt marker | `0x0800870C` / `0x08008874` | `draw_marker5(page,on)` (col 123) |
| highlight frame corners | `0x08008224` | `draw_frame_corners(x,page)` |
| battery/signal icons | `0x08008874`/`0x08008A04` | (see `display.md`) |
| battery volts (ADC) | `0x08010960` | `u8 batt_read(void)` (tenths V) |
| clear region | `0x08008D24` | `blit_cols(pg,count,NULL,0)` |

### 7.4 Boundary compliance (hard constraint)
Everything above **reads** RAM caches (`0x20002DEA`, `0x2000A6C5`, `0x200009C3`) and calls **display + ADC** primitives. None of it writes SPI codeplug regions or touches the USART6 CPS framer / SPI region‑write engine (`main-loop.md` §5). The channel cache `0x20002DEA` mirrors the on‑flash 48‑byte channel record but is a *separate RAM copy* — reading it cannot change the codeplug format. **CPS + codeplug compatibility is unaffected.**

---

## 8. Confidence

- **HIGH** — home screen = id 0, draw handler `0x08013BD8`; the formatter `format_area_display @0x08011414`; the display struct `g_disp @0x200009C3` field map; the channel cache `0x20002DEA` (48‑byte stride, RX@+5/TX@+9/tones@+0xD,0xF/name@+0x20); helpers `num_to_ascii 0x08018530`, `str_insert_char 0x080135A8`, `memcpy_off 0x080062EC`; the dirty‑flag model (`g_dirty 0x200024EB`) and the `0x080095DA` trigger. All directly disassembled, literal pools resolved, inline templates decoded (`"CH MODE"/"VFO MODE"/"CH-"/"A-"/"D-"/"ANA"/"DMR"`).
- **MEDIUM‑HIGH** — exact page (row) numbers per element: the `r4/r5/r6` row registers are re‑ordered by `active_area` at handler entry, so the absolute page of Area A vs B swaps with selection; the column x‑values are exact (from the draw calls). Battery/signal icon columns per `display.md`.
- **MEDIUM** — the DMR TG/Color‑Code overlay path (`g_call 0x2000A6C5`, near `0x08006754`) is identified and its fields typed, but the full CC/slot rendering sequence on the standby line is only partially traced (it interleaves with FM100B RX events); the analog FM/AM/SSB sub‑tag is inferred from the `RX Demod (A)` channel field rather than a distinct draw routine. Confirm on‑device when redesigning the DMR overlay.
