# Batch-C Integration Notes

C++ firmware ports of three PDL-platform Python modules.
Generated 2026-04-24 alongside `src/BATCH_C_INTEGRATION_NOTES.md`.

---

## Files delivered

| File | Size (approx.) | SRAM cost |
|------|---------------|-----------|
| `src/fhss_hop.h` / `.cpp` | 6 KB source | ~5 KB SRAM (ring 384 B + ngram table 4352 B) |
| `src/threat_vqc.h` / `.cpp` | 5 KB source | 256 B (32-element Cx statevector) |
| `src/attack_graph_local.h` / `.cpp` | 5 KB source | ~30 B (6 floats + decay timer) |
| `test/test_batch_c/test_batch_c.cpp` | 8 KB source | — (native only) |

Total new SRAM: **≈5.4 KB** (well inside the ESP32-S3 8 MB PSRAM budget; fits in base SRAM if psram is unavailable).

---

## Module 1 — `fhss_hop.{h,cpp}`

### Source
`pdl-engine/pdl_platform/pdl_quantum/integrations/fhss_hop_predictor.py`

### Algorithm summary
- **Stage 1 (3-gram Markov):** frequencies are hash-bucketed into 16 bins (5 MHz each, 2.4 GHz linear; 5 GHz fold via 4-bit XOR). A 64-slot LRU cache holds `{ key: uint16_t, counts[16]: uint16_t }` for the most-recently-seen 3-grams. On predict, falls back to 2-gram → unigram if the 3-gram key is absent.
- **Stage 2 (dwell estimation):** rolling window of 32 inter-hop intervals (µs); median via insertion sort. Only intervals in `[1 ms, 20 ms]` (OcuSync 2/3 spec) are admitted.

### Main-loop integration (after `capture_rf_spectrum`)
```cpp
#include "fhss_hop.h"

// In setup():
fhss_init();

// After finding strongest WiFi-channel proxy (freq_mhz, micros()):
fhss_observe(strongest_channel_freq, micros());

// Optionally gate per-channel dwell:
uint16_t next_freq; uint32_t dwell_us;
if (fhss_predict_next(&next_freq, &dwell_us)) {
    // Spend extra dwell time on next_freq channel
    tune_and_capture(next_freq, dwell_us);
}
```

---

## Module 2 — `threat_vqc.{h,cpp}`

### Source
`pdl-engine/pdl_platform/pdl_quantum/integrations/threat_network.py`
(`QuantumBayesianThreatNetwork` forward pass — no QPU, classically simulated)

### Algorithm summary
5-qubit VQC statevector (32 complex<float>):
1. Evidence encoding: `Ry(π * feat[i])` on each qubit
2. CNOT chain entangler: q0→q1→q2→q3→q4
3. Variational layer A: `Ry(θ[0..4])`
4. Variational layer B: `Ry(θ[5..9])`
5. Output: `P(popcount(basis_state) ≥ 3)` — majority-detection quantum Bayesian score

The θ vector is a `static const float s_theta[10]` in `threat_vqc.cpp`. Replace with `best_params[0..9]` from the IBM Quantum artifact `phase2_burn4_bayesian_threat.json` after re-training.

### Feature vector mapping
```
feats[0] = markov_nll_normalised       (0=benign, 1=max; scale markov_current_nll()/8.0)
feats[1] = wfx_channel_occupancy       (from wfx_features: occupancy_ratio field)
feats[2] = ffps_corroborator_count_norm (corroborator_count / MAX_CORROBORATORS)
feats[3] = channel_rssi_norm           (e.g. (rssi_dbm + 100.0) / 100.0)
feats[4] = threat_correlator_active    (0.0 or 1.0)
```

### Main-loop integration
```cpp
#include "threat_vqc.h"
#include "config.h"

// In setup():
threat_vqc_init();

// In detection loop:
float feats[5] = {
    markov_current_nll() / 8.0f,
    wfx_get_occupancy(),
    (float)ffps_corroborator_count() / 8.0f,
    (rssi_dbm + 100.0f) / 100.0f,
    threat_correlator_is_active() ? 1.0f : 0.0f
};
float threat_score = threat_vqc_score(feats);
if (threat_score > 0.7f) {
    // Set composite-threat bit in next mesh publish packet
    composite_flags |= COMPOSITE_FLAG_VQC_THREAT;
}
```

---

## Module 3 — `attack_graph_local.{h,cpp}`

### Source
`pdl-engine/pdl_platform/redcell/attack_graph.py` (local self-diagnosis subset)

### Algorithm summary
6-node directed graph (BOOTLOADER, FIRMWARE, MODELS, MESH_KEY, BLUE_FORCE_LIST, AUTH_PUBKEY).
- Per-node `compromise_probability ∈ [0,1]`
- **Update rule:** `new_prob = clamp(old_prob + s_event_weight[evt][node] * caller_weight, 0, 1)`
- **Propagation:** parent node propagates 10% of its delta to adjacent child nodes (mirrors reference graph chain topology)
- **Decay:** `prob *= exp(elapsed_ms * -0.0001667)` ≈ 1% per 60 000 ms; applied in `attackgraph_decay(elapsed_ms)`
- **Failsafe:** any node > 0.85 → `attackgraph_should_failsafe() = true`

### Main-loop integration
```cpp
#include "attack_graph_local.h"
#include "mesh_publish.h"
#include "command_handler.h"

// In setup():
attackgraph_init();
static uint32_t last_decay_ms = 0;

// In main loop (called ~1 Hz):
uint32_t now = millis();
attackgraph_decay(now - last_decay_ms);
last_decay_ms = now;

// On signature verification failure:
attackgraph_observe(NODE_FIRMWARE, EVT_SIG_FAIL, 1.0f);

// On OTA reject:
attackgraph_observe(NODE_BOOTLOADER, EVT_OTA_REJECT, 1.0f);
attackgraph_observe(NODE_FIRMWARE,   EVT_OTA_REJECT, 0.6f);

// On PEC deviation:
attackgraph_observe(NODE_MODELS, EVT_PEC_DEVIATION, pec_deviation_magnitude);

// On auth failure:
attackgraph_observe(NODE_AUTH_PUBKEY, EVT_AUTH_FAIL, 1.0f);
attackgraph_observe(NODE_MESH_KEY,    EVT_AUTH_FAIL, 0.5f);

// Check failsafe:
if (attackgraph_should_failsafe()) {
    // Publish high-risk behavioral NLL
    markov_publish_nll(sensor_id, 0, 0.0f);  // reason byte = high

    // Extreme case: AUTH_PUBKEY > 0.95 → wipe NVS + reboot
    if (attackgraph_node_risk(NODE_AUTH_PUBKEY) > 0.95f) {
        nvs_flash_erase();
        esp_restart();
    }
}
```

---

## Running the tests
```bash
# All Batch-C tests
pio test -e native -f test_batch_c

# All native tests (including existing suites)
pio test -e native
```

14 Unity test cases total: 3 FHSS + 5 VQC + 6 attack-graph.

---

## θ vector update procedure
1. Run IBM Quantum validation job, retrieve `best_params` array (15 values; indices 0–9 are used).
2. In `src/threat_vqc.cpp`, replace `s_theta[]` with the actual values.
3. Re-run `pio test -e native -f test_batch_c` — `test_threat_vqc_score_in_range` must still pass.
4. Flash firmware; the new parameters take effect on next boot.

---

## Memory accounting
| Region | Bytes | Notes |
|--------|-------|-------|
| `s_ring[64]` (FhssObs) | 384 | 64 × (uint16 + uint32) = 6 B each |
| `s_ngram[64]` (NgramSlot) | 2240 | 64 × (2 + 32 + 4) = 38 B each |
| `s_dwell_buf[32]` | 128 | 32 × uint32 |
| `s_sv[32]` (Cx) | 256 | 32 × 2 × float |
| `s_risk[6]` + timer | 28 | 6 × float + uint32 |
| **Total** | **≈3 KB** | Fits in base SRAM without PSRAM |
