---
phase: 16
slug: vrchat-osc-bridge
status: planned
nyquist_compliant: true
wave_0_complete: false
created: 2026-04-19
updated: 2026-04-19
---

# Phase 16 — Validation Strategy

> Per-phase validation contract for feedback sampling during execution.

---

## Test Infrastructure

| Property | Value |
|----------|-------|
| **Framework** | None — driver project uses manual SMOKE UAT (continues 14-SMOKE / 15-SMOKE pattern) |
| **Config file** | None — Wave 0 (Plan 16-00) installs `16-SMOKE.md` + `tests/osc_fixture/send_backglow.py` |
| **Quick run command** | `"C:/Program Files/Microsoft Visual Studio/2022/Community/Common7/IDE/CommonExtensions/Microsoft/CMake/CMake/bin/cmake.exe" --build build --config Release` |
| **Full suite command** | Deploy + SteamVR + 16-SMOKE.md UAT: `powershell -File scripts/deploy-backglow-dev.ps1; "C:/Program Files (x86)/Steam/steamapps/common/SteamVR/bin/win64/vrstartup.exe"` then run Python fixture + walk SMOKE rows |
| **Estimated runtime** | ~30 s compile; full SMOKE UAT ~10 min (hardware) |

---

## Sampling Rate

- **After every task commit:** Run `cmake --build build --config Release` (catches link/macro errors)
- **After every plan wave:** Deploy + launch SteamVR; tail `vrserver.txt` for `Backglow: daemon spawned` + `Backglow: online`
- **Before `/gsd-verify-work`:** Full `16-SMOKE.md` green (hardware + optional real VRChat)
- **Max feedback latency:** ~30 s per-commit compile; ~2 min per-wave deploy smoke

---

## Per-Task Verification Map

| Task ID | Plan | Wave | Requirement | Threat Ref | Secure Behavior | Test Type | Automated Command | File Exists | Status |
|---------|------|------|-------------|------------|-----------------|-----------|-------------------|-------------|--------|
| 16-00-01 | 00 | 0 | VRCH-01, VRCH-02 | — | — | wave0 scaffold | `test -f .planning/phases/16-vrchat-osc-bridge/16-SMOKE.md && test -f tests/osc_fixture/send_backglow.py` | ✅ Plan 00 | ⬜ pending |
| 16-00-02 | 00 | 0 | VRCH-01, VRCH-02 | T-16-00-01 (driver-DLL cross-contamination) | daemon target separate from driver DLL | compile | `cmake --build build --config Release` exits 0; `test -f build/driver/BeyondProximity/bin/win64/beyond_backglow_ctl.exe` | ✅ Plan 00 | ⬜ pending |
| 16-01-01 | 01 | 1 | VRCH-02 | T-16-01-04 (UDP flood OOM) | fixed 2 KB buffer, atomic u8 slots | compile | `test -f extern/oscpp/include/oscpp/server.hpp && grep 'PIPE_READMODE_MESSAGE' src/backglow_ctl/pipe_client.cpp` | ✅ Plan 00 | ⬜ pending |
| 16-01-02 | 01 | 1 | VRCH-02 | T-16-01-03 (malformed OSC crash) | try/catch around oscpp parse | compile | `grep -c 'catch' src/backglow_ctl/osc_server.cpp` ≥ 2; `grep 'isBundle()' src/backglow_ctl/osc_server.cpp` | ✅ Plan 00 | ⬜ pending |
| 16-01-03 | 01 | 1 | VRCH-02 | T-16-01-02 (LAN OSC inject), T-16-01-05 (DLL hijack) | loopback bind + SetDefaultDllDirectories | smoke+camera | Daemon runs; `netstat -ano \| findstr 127.0.0.1:9001 \| findstr LISTENING`; `py tests/osc_fixture/send_backglow.py --led 0 --r 1.0` → LED 0 red within ~50 ms | ❌ needs 16-02 | ⬜ pending |
| 16-02-01 | 02 | 2 | VRCH-01 | — | 90 Hz retune | compile | `grep 'milliseconds(11)' src/led/led_controller.cpp` = 1 match; `cmake --build build --config Release` exits 0 | ✅ Plan 00 | ⬜ pending |
| 16-02-02 | 02 | 2 | VRCH-01 | T-16-02-01 (DLL hijack), T-16-02-02 (orphan daemon), T-16-02-04 (nested job) | absolute-path + JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE + CREATE_BREAKAWAY_FROM_JOB | compile | `grep JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE src/driver/device_provider.cpp`; `grep CREATE_BREAKAWAY_FROM_JOB src/driver/device_provider.cpp`; build exits 0 | ✅ Plan 00 | ⬜ pending |
| 16-02-03 | 02 | 2 | VRCH-01 | T-16-02-02 (orphan) | Job Object kill-on-close | smoke | After SteamVR launch: `Get-Process beyond_backglow_ctl` returns 1 row; `vrserver.txt` contains `Backglow: daemon spawned (pid=`; then `Stop-Process vrserver -Force` → within 2s `Get-Process beyond_backglow_ctl -ErrorAction SilentlyContinue` returns null | ❌ needs 16-02 | ⬜ pending |
| 16-03-01 | 03 | 3 | VRCH-01 | — | vendored mDNS at pinned commit | compile | `test -f extern/mdns/mdns.h && test -f extern/mdns/VERSION.txt`; `grep Content-Type src/backglow_ctl/oscquery_http.cpp` | ✅ Plan 00 | ⬜ pending |
| 16-03-02 | 03 | 3 | VRCH-01 | T-16-03-01 (LAN mDNS), T-16-03-02 (malformed HTTP) | loopback mDNS bind + strict HTTP parser | compile | `grep '_oscjson._tcp' src/backglow_ctl/mdns_advertise.cpp`; `grep -c INADDR_ANY src/backglow_ctl/` = 0 | ✅ Plan 00 | ⬜ pending |
| 16-03-03 | 03 | 3 | VRCH-01 | — | OSCQuery primary path OR D-19 fallback | automated | Daemon log contains `OSCQuery primary path:` OR `fallback path: listening on 127.0.0.1:9001`; `curl http://127.0.0.1:M/` (primary) returns JSON with `/avatar` | ❌ needs 16-02 + hardware | ⬜ pending |

*Status: ⬜ pending · ✅ green · ❌ red · ⚠️ flaky.*

---

## Wave 0 Requirements

- [x] `.planning/phases/16-vrchat-osc-bridge/16-SMOKE.md` — UAT template (Plan 16-00 Task 1)
- [x] `tests/osc_fixture/send_backglow.py` — python-osc fixture (Plan 16-00 Task 1)
- [x] `CMakeLists.txt` — `beyond_backglow_ctl` executable target wrapped in ENABLE_BACKGLOW (Plan 16-00 Task 2)
- [x] `scripts/deploy-backglow-dev.ps1` — Copy-Item for daemon exe (Plan 16-00 Task 2)
- [x] `extern/oscpp/` + `extern/mdns/` — placeholder directories for Wave 1/3 vendoring (Plan 16-00 Task 1)
- [x] No test-framework install — project has no unit-test harness; Phase 16 does not introduce one

---

## Manual-Only Verifications

| Behavior | Requirement | Why Manual | Test Instructions |
|----------|-------------|------------|-------------------|
| Startup white-fade visible on first spawn | VRCH-01e | Visual-only; LED output not queryable via API | Launch SteamVR with Backglow enabled; observe camera stream for ~4s white fade on headset LEDs |
| Float param → LED color within 1 frame | VRCH-02a | Timing verified via camera (hardware latency) | Run fixture `py tests/osc_fixture/send_backglow.py --led 0 --r 1.0`; capture camera frame; visually confirm LED 0 turns red |
| Silence fade after 3s OSC idle | VRCH-02c | Visual-only on hardware | Fixture sends then stops; ~3s later observe LEDs ramp off over ~500 ms |
| Startup animation visible (D-16) | VRCH-01e | Visual-only | Camera stream at SteamVR launch; white fill → 1 s ramp up to 128 → hold 250 ms → 3 s ramp down → off |

---

## Validation Sign-Off

- [x] Every task references a row above OR depends on Wave 0 scaffold
- [x] Sampling continuity: no 3 consecutive tasks without compile check (every task's verify includes `cmake --build`)
- [x] Wave 0 covers all scaffold references
- [x] No watch-mode flags
- [x] Feedback latency < 120 s per wave
- [x] `nyquist_compliant: true` — planner filled exact task rows per plan

**Approval:** approved 2026-04-19 (planner)
