# WaveForge Expose — T1 Integration Notes

These are the **only** edits needed in existing files to wire `wfx_features`
into the live T1 firmware.  Do NOT modify `wfx_features.h` or
`wfx_features.cpp` — add the four touch-points below.

---

## 1.  `src/config.h` — add the new message type

Find the block of `#define MSG_TYPE_*` constants (around line 167-178) and
append immediately after `MSG_TYPE_MESH_DISCOVER`:

```c
#define MSG_TYPE_WFX_FEATURES  0x10  // WaveForge Expose feature vector (T1→T2)
```

The next free slot after `0x0F` (MESH_DISCOVER) is `0x10`.  Nothing else
currently uses it.

---

## 2.  `src/main.cpp` — include the header

In the block of `#include` statements at the top of `main.cpp` add:

```cpp
#include "wfx_features.h"
```

---

## 3.  `src/main.cpp` — call `wfx_features_init()` in `setup()`

Place this call after `ffps_init()` is called (around line 242):

```cpp
wfx_features_init();
```

---

## 4.  `src/main.cpp` — call `wfx_features_observe_rssi_packet()` after each `mesh_receive()`

**Location:** inside the `if (rx_len > 0)` block in `loop()`, immediately
after `health_update_rssi((int16_t)rx_rssi);` (around line 277):

```cpp
if (rx_len > 0) {
    health_update_rssi((int16_t)rx_rssi);

    // --- WaveForge: feed LoRa observation into the feature rolling buffer ---
    wfx_features_observe_rssi_packet(
        (int16_t)rx_rssi,
        (int16_t)rx_snr,
        millis(),
        /*band_id=*/1);   // 1 = 915 MHz LoRa band
    // -----------------------------------------------------------------------

    uint8_t msg_type = rx_buf[0];
    switch (msg_type) {
        // ... existing cases unchanged ...
    }
```

This feeds every successfully received LoRa packet into the jitter / RSSI /
SNR rolling window so the feature vector has real observations.

For the WiFi 2.4 GHz band, `capture_rf_spectrum()` already calls
`wifi_sniffer_cb()` which accumulates per-channel RSSI into `s_channel_rssi`.
The normalised spectrum it produces is fed directly into
`wfx_features_compute()` below — no additional call needed there.

---

## 5.  `src/main.cpp` — compute and publish the feature vector after `capture_rf_spectrum()`

**Location:** immediately after the `capture_rf_spectrum(s_spectrum);` call
at step 3 of `loop()` (around line 373).  Gate to every N=10 cycles to avoid
LoRa flooding (WFX packet = 64 bytes; at SF10/125 kHz one packet ≈ 370 ms
air time; once every 10 s at 1 Hz scan rate is well within the 1% duty cycle
budget):

```cpp
// ----------------------------------------------------------------
// 3. Capture RF spectrum (WiFi 2.4 GHz promiscuous scan)
// ----------------------------------------------------------------
capture_rf_spectrum(s_spectrum);

// ----------------------------------------------------------------
// 3a. WaveForge Expose: emit L0/L1 feature vector every 10 cycles
// ----------------------------------------------------------------
if (s_loop_count % 10 == 0) {
    WfxFeatureVec wfx_v;
    if (wfx_features_compute(s_spectrum, SPECTRUM_BINS, &wfx_v)) {
        wfx_v.timestamp_ms = millis();
        wfx_v.band_id      = 0;   // 2.4 GHz WiFi spectrum proxy
        wfx_features_publish(wfx_v);
        mesh_start_receive();  // re-arm RX after TX
    }
}
// ----------------------------------------------------------------
```

`s_loop_count` is already incremented at the bottom of `loop()` so it is
available here.

---

## Summary of changes

| File | Action | Where |
|------|--------|-------|
| `src/config.h` | `#define MSG_TYPE_WFX_FEATURES 0x10` | after line 178 |
| `src/main.cpp` | `#include "wfx_features.h"` | include block (top) |
| `src/main.cpp` | `wfx_features_init();` | `setup()`, after `ffps_init()` |
| `src/main.cpp` | `wfx_features_observe_rssi_packet(...)` | `loop()` step 1, after `health_update_rssi` |
| `src/main.cpp` | `wfx_features_compute(...); wfx_features_publish(...);` | `loop()` step 3a, after `capture_rf_spectrum`, gated `% 10` |

No other files need changes.  The `switch(msg_type)` in the receive path does
not need a `case MSG_TYPE_WFX_FEATURES` because T1 does not receive its own
feature packets — they are consumed upstream by the T2 gateway's
`l1_estimator.py`.
