Fixes the architecture, the three-part pass condition (beats rung-2 NLL
AND coverage within +/-3pp at all four levels AND EIG-greedy beats random
by >2 SE), and the hard time-box, before any neural code exists.
The variance head deliberately does not see revealed values, so EIG stays
closed-form and peek-free while sigma depends on z_t -- which is what
makes the probe set vary per sample, the one thing rung 2 could not do.
Backend is burn-ndarray on CPU by choice: the model is tiny, and this
removes the Blackwell sm_120 CUDA build risk. Burn 0.21 + ndarray
verified to build in 18.5s.
Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01JJnDvpYYk2rTvjkudQUoCk
The 2k-pair M4 run had SE ~0.0016 against ~0.001 policy gaps, so the
null could have been a power problem. Rerun at 20k pairs (SE ~0.0002)
with paired per-sample tests: the null holds, and at K=6/K=9 EIG-greedy
is slightly but significantly WORSE than random (t -3.15, -2.09;
magnitude <=0.0009 PCK@20).
Mechanism: EIG maximises the logdet of the 30-dim pose posterior --
total joint entropy reduction -- while PCK@20 is a thresholded
per-keypoint criterion. Different utilities, so maximising the first can
slightly hurt the second. The machinery is right; the objective does not
match the metric.
Adaptivity with no information is worth nothing, as the homoscedastic
account predicts: a fresh random probe set per sample matches a fixed
set (no consistent sign across budgets).
Correction: the oracle selects bands using the TRUE y_t+1, i.e. the
target itself -- so it upper-bounds every policy including a perfect
observation-adaptive one, and is not achievable. Renamed
target_peeking_upper_bound.
Also records that M3 forecasting (0.8195) and diag_repr sensing (0.8132)
are NOT like-for-like (different subsets, train sizes, lambda, bars), so
no comparison between them is licensed.
Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01JJnDvpYYk2rTvjkudQUoCk
M3 (rung 2, closed-form joint Gaussian over [z_t, z_t+1, y_t+1]):
PCK@20 0.8195 vs mean-pose bar 0.7561 -- beats the bar by +6.33 pts.
Persistence R2 0.871, so one-step dynamics is not trivial.
Calibration MARGINALLY FAILS the pre-registered criterion: coverage
error +3.48pp at the 50% level against a +/-3pp tolerance (80/90/95
all pass). Reported as a fail; the tolerance is not widened post hoc.
M4: EIG-greedy beats fixed and random on EIG nats at every budget and
the oracle bracket holds -- but the advantage does NOT convert into
pose accuracy. Greedy/fixed/random differ by ~0.001 PCK@20, inside the
random policy's own seed spread. Honest negative.
The oracle (peeks at true z_t+1) reaches 0.8596 vs 0.8230 at K=9,
so +3.7 pts of headroom exists for a per-sample adaptive policy.
Confirms the pre-registered caveat: a homoscedastic Sigma makes the
EIG-optimal probe set identical at every timestep, so this is
information-theoretic set selection, not adaptive sensing.
DEVIATION FROM PRE-REGISTRATION, with the measurement that forced it:
the pre-registered 27-bands-x-20-frames representation (chosen on the
reasoning that collapsing time would destroy motion signal) was
measured to be backwards, and was the worst of seven variants -- the
only one failing to beat the bar (0.7495 vs 0.7523).
Pose signal lives in fine channel structure; averaging 20 adjacent
channels destroys it while averaging over 20 frames costs ~nothing.
Revised to 270 bands of 2 channels averaged over time, with action
granularity (27 groups of 20 channels) decoupled from feature
granularity. Full sweep in results/repr_diagnostic.txt.
EIG machinery independently unit-tested: 7/7 including non-negativity,
monotonicity, greedy-vs-brute-force, and analytic-vs-Monte-Carlo.
Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01JJnDvpYYk2rTvjkudQUoCk
Representation, model ladder, metrics, extended guards, and the M4
policy-comparison protocol are all fixed in advance -- including the
bracketing check (oracle upper-bounds, random lower-bounds, EIG must sit
between) and the EIG unit tests.
Records two honesty constraints agreed before seeing any result:
a homoscedastic Sigma makes EIG a FIXED policy, not adaptive sensing;
and pose accuracy from 27 band means is not comparable to the 96%
full-CSI number, so it is reported against the 75.4% mean-pose bar only.
Also dumps the remaining M3 fixtures so no Rust code touches the pickle.
Verified globally that within-file consecutive window indices are
consecutive in time: 359,500 valid (t, t+1) pairs = 360,000 - 500 file
boundaries. Full cleaned GT cross-checks exactly against the validated
M2 test fixture.
Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01JJnDvpYYk2rTvjkudQUoCk
M1 - Rust evaluation harness (harness/), guards before capability:
- metrics.rs: upstream-exact torso-normalized PCK + MPJPE, plus
absolute_pck retained deliberately as a foil for the broken protocol
- guards.rs: constant-pose detector, mean-pose honesty bar, corruption
masks, finite checks, upstream zero-cleaning reimplemented
- npy.rs: mmap .npy reader (the CSI array is 15.5 GB)
- 5/5 tests pass, including a reproduction of the retracted 92.9% failure:
absolute-0.2 scores >0.99 on a constant predictor where torso-normalized
scores <0.60 and the degeneracy guard trips
M2 - reproduces the honest baseline, all 6 checks pass:
full PCK@20 0.9608877 vs recorded 0.9608815 (delta 6.1e-6)
clean PCK@20 0.9661492 vs recorded 0.9661454 (delta 3.8e-6)
Rust f64 is bit-identical to an independent Python f64 computation.
GT independently rebuilt from all_keypoints.npy: 0 mismatches after
cleaning, 8098 before, so the clean path is genuinely exercised.
Measured a reproducibility floor rather than assuming one: TF32 on/off
alone moves PCK@20 by 1.36e-5, and the recorded 2026-06-10 value lies
between the two. No improvement below ~1.4e-5 PCK@20 is real.
Answers the previously-unknown honesty bar: a mean-pose predictor scores
75.37% torso-PCK@20 on the WiFlow test split (oracle 75.46%). The model's
96.09% beats it by 20.7 points -- unlike the ESP32 set, where the bar was
95.9% and nothing beat it.
Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01JJnDvpYYk2rTvjkudQUoCk
Establishes the action-conditioned dynamics objective, the verified
upstream protocol (split/PCK/GT semantics read from source), the
retraction this work must not repeat, and the milestone order
(guards before capability).
Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01JJnDvpYYk2rTvjkudQUoCk
Path-scoped guard for v2/crates/ruview-swarm/** (ADR-148). Complements the
main ci.yml (which only runs the default workspace tests):
- feature-matrix tests: default / train / ruflo+itar / full+train
- clippy -D warnings --no-deps (crate-own code only; dep warnings don't gate)
- train_marl bin builds under 'train' AND is excluded from the default build
- ITAR/publish guards: publish=false present, itar-unrestricted never in default
All steps verified locally green before commit.
Co-Authored-By: claude-flow <ruv@ruv.net>
Adds an offline, dependency-free visualization for the drone training system:
a top-down swarm replay synced with training-metric curves, fed by a JSONL
telemetry log the trainer emits. No server, no build step, no CDN.
## Telemetry recorder (integration/telemetry.rs, always compiled, no new deps)
- TelemetryRecorder writes newline-delimited JSON: one `meta` (profile, area,
ground-truth victims), many `step` (per-tick drone x/y/heading/battery/detection
+ coverage%), and per-episode `episode` (mean_return, policy_loss, value_loss).
- Written by hand (no serde_json) so it stays in the default build; 2 tests.
## train_marl telemetry flags
- `--telemetry FILE` writes the log; `--telemetry-episode N` selects which
episode's spatial steps to record (metrics recorded for all episodes).
## Visualizer (viz/swarm_viz.html — single file, vanilla JS + canvas)
- LEFT: top-down replay — heading-oriented drone triangles (cyan/lime on
detection), victim markers, growing coverage heatmap, detection pulse rings,
play/pause/scrub/speed controls + live coverage/detection readout.
- RIGHT: three autoscaled line charts (mean return, policy loss, value loss)
over episodes, hand-drawn (no chart library).
- Loads via file picker/drag-drop or auto-fetches the bundled sample; dark
drone-ops theme; graceful degradation on file:// CORS.
- viz/sample_telemetry.jsonl: real 30-episode / 4-drone / 400×400 m run
(value_loss 20052→7154 — visible critic learning). Parses 1 meta / 60 step / 30 episode.
## Usage
cargo run --release -p ruview-swarm --features train,cuda --bin train_marl -- \
--episodes 5000 --telemetry run.jsonl
open v2/crates/ruview-swarm/viz/swarm_viz.html # load run.jsonl
Tests unchanged (91 default / 96 train / 104 ruflo+itar); telemetry adds 2.
Co-Authored-By: claude-flow <ruv@ruv.net>
Replaces the finite-difference PPO placeholder with a real GPU-capable Candle
0.9 autodiff trainer, adds A-MAPPO heterogeneous-role attention, a runnable
training binary, and right-sized GCP/local launch scripts. This is the unlock
that makes "GPU long training cycles" actually mean something — the previous
ppo_update did no gradient descent.
## Real autodiff PPO (feature `train`, optional `cuda`)
- candle_ppo.rs: CandleActorCritic (64→128→64 MLP + action/value heads +
learnable log_std), CandlePpoConfig, CandleTrainer with GAE and a genuine
optimizer.backward_step over the network. select_device() picks CUDA when
built --features cuda and a GPU is present, else CPU.
- Verified: 5-episode CPU smoke run shows value_loss 12643→12375 (critic
actually learning); safetensors checkpoint saved. Placeholder never moved weights.
## A-MAPPO heterogeneous-role attention (role_attention.rs, always compiled)
Addresses the four sensor-vs-relay edge cases:
- relay attention floor (prevents collapse — relays produce no CSI)
- role-segmented sensor/relay attention pools (variable neighbor cardinality)
- sensor-gated triangulation-geometry penalty (protects 3-view fusion baseline,
ADR-148 §4.2 — relays not dragged into triangulation geometry)
- one-hot role embeddings for keys
## Training binary
- src/bin/train_marl.rs (required-features=["train"], excluded from default build)
- CLI: --episodes --drones --profile --steps --checkpoint-dir --checkpoint-every
- Wires CandleTrainer to the SwarmOrchestrator rollout loop; GAE + PPO update
per episode; periodic safetensors checkpoints
## Right-sized launch (scripts/gcp/)
- provision_marl.sh: g2-standard-16 (1× L4, 16 vCPU, ~$1.40/hr) — NOT the
$29/hr A100×8 box. MARL is rollout-bound not matmul-bound; ~21× cheaper.
- run_marl_train.sh: GCP rsync + train + checkpoint pull
- run_marl_train_local.sh: local RTX 5080, $0
- A100×8 provision_training.sh left for OccWorld (which saturates the GPUs)
## Tests
- --no-default-features: 91/91 (87 + 4 role_attention)
- --features train: 96/96 (+ 5 candle_ppo, incl. real-autodiff verification)
- --features ruflo,itar-unrestricted: 104/104
- default build stays light: train_marl excluded via required-features
Co-Authored-By: claude-flow <ruv@ruv.net>
- bench_ppo_update: PPO update over 64-transition buffer — 244 µs median
- fix: bench imports referenced stale `wifi_densepose_swarm` (pre-rename),
corrected to `ruview_swarm` so the bench target compiles
M6 benchmark suite now 5/5 compiling and running. Tests unchanged: 87/100.
Co-Authored-By: claude-flow <ruv@ruv.net>
Follow-up to 13b08927 which committed an intermediate M7 state with one
failing test. This lands the M7 agent's convergence fixes and the security
review's timeout hardening.
## Fixes
- swarm_sim.rs: min-separation nudge before collision metric (0 collisions
with staggered starts) + Phase-3 convergence assist that vectors the nearest
idle peer toward a single-drone CSI contact so multi-view fusion can fire
- http_backend.rs: add 5s request timeout to reqwest client (security review
Medium finding — a dead daemon would otherwise hang the swarm step loop)
## Security review verdict (HttpRufloBackend)
Safe to merge. No credentials in requests, serde_json prevents injection,
fail-open on daemon-down is documented and appropriate for SAR missions,
MAVLink passed as structured text (not raw bytes). Timeout fix applied.
## Tests
- --no-default-features: 87/87 pass
- --features ruflo,itar-unrestricted: 100/100 pass
Co-Authored-By: claude-flow <ruv@ruv.net>
The swarm control system is a RuView-level capability (drone coordination,
Raft consensus, MARL) that operates above the wifi-densepose sensing layer
rather than being a sub-component of it. Rename aligns with the project
identity and separates coordination infrastructure from sensing modules.
Co-Authored-By: claude-flow <ruv@ruv.net>
- User guide: full retrain workflow (record → vqvae → transformer → serve)
with checkpoint path usage
- README: note fine-tune capability in world model capability row
Co-Authored-By: claude-flow <ruv@ruv.net>
Drives a real SemanticBus: raw snapshot (fall_detected, past warmup) ->
FallRisk primitive -> SemanticStateRecord (provenance) -> single-signal rule
fires / multi-signal agreement rule does NOT (no false escalation) -> expired
record rejected. Proves the ADR-140 credibility path end to end.
Co-Authored-By: claude-flow <ruv@ruv.net>
Weaves the three framing points into every ADR in the series:
- skeleton/scaffolding (data contracts + trust/privacy/audit machinery +
algorithms; real, tested, compiling) that existing sensing code plugs into
- Built (tested building block) vs Integration glue (not yet on the live 20 Hz
path) — per-ADR, with commit + issue references
- trust throughline (traceable evidence, sensor agreement, calibration
provenance, auditable privacy)
ADR-136 §8 carries the full series framing; 137-146 carry per-ADR status.
Co-Authored-By: claude-flow <ruv@ruv.net>
Adds a `--min-frames N` flag to `wifi-densepose calibrate` that overrides
the ADR-135 tier minimum (default 600 frames at 20 Hz for HT20).
Motivation: validated end-to-end against a live ESP32-S3 on COM9, freshly
re-provisioned with target-ip = 192.168.1.50 (this host). The firmware
emits CSI at roughly 0.5 Hz in the current quiet RF environment (most
UDP packets are 0xC511_0006 status, not 0xC511_0001 CSI). Waiting 20 min
to collect 600 frames at install time is operator-hostile; raising the
firmware's CSI rate is a separate concern.
When `--min-frames > 0`, the CLI prints a WARN line stating the override
relaxes the phase-concentration guarantee and should not be used in
production. ADR-135 defaults are preserved unchanged.
Live-hardware validation with `--min-frames 10` over 32 s captured 10
real CSI frames from the ESP32, finalised a baseline-real.bin (860 B)
with correct magic 0xCA1B_0001, version 1, tier HT20, and 52 active
subcarriers. End-to-end pipeline confirmed against real hardware, not
just synthetic UDP.
Co-Authored-By: claude-flow <ruv@ruv.net>
Operator-initiated calibration that records 30 s of stationary CSI,
emits a per-subcarrier baseline (amplitude mean+variance via Welford,
phase via circular sin/cos sums with von Mises dispersion), and gates
downstream stages on a deviation z-score. Plugs into multistatic
coherence gating, motion/presence detection, and the new ADR-134 CIR
estimator as a reference-subtracted input.
API surface (under wifi_densepose_signal):
CalibrationConfig::{ht20, ht40, he20, he40}
CalibrationRecorder { record(), finalize(), frames_recorded() }
BaselineCalibration {
subcarriers: Vec<SubcarrierBaseline>,
deviation(&CsiFrame), subtract_in_place(&mut CsiFrame),
to_bytes(), from_bytes()
}
CalibrationDeviationScore { amplitude_z_median, amplitude_z_max,
phase_drift_median, motion_flagged }
CalibrationError { SubcarrierMismatch, TierMismatch,
InsufficientFrames, VersionMismatch, TruncatedBuffer }
Binary baseline format: magic 0xCA1B_0001 + u8 version=1 + u8 tier +
captured_at_unix_s (i64) + frame_count (u64) + num_subcarriers (u32) +
[SubcarrierBaseline; N] as 16 bytes each (amp_mean, amp_variance,
phase_mean, phase_dispersion as f32 LE). Hand-written serialisation so
the format is stable across Rust toolchain versions without serde drift.
CLI: new `wifi-densepose calibrate` subcommand binds a UDP listener
(0xC511_0001 frames), streams them through CalibrationRecorder, prints
a real-time z-score banner per ADR-135 §risk 1 (operator-may-be-moving),
aborts on sustained high deviation, and writes the binary baseline to
disk. Local UDP packet parser duplicated from sensing-server (per ADR
discussion — avoids cross-crate API churn).
Witness: cross-platform-deterministic SHA-256 over the per-subcarrier
quantised baseline profile (u16 LE at 1e-2/1e-4/1e-3, no sort) using
the lesson learnt from the CIR PR #837 libm-jitter fix. Hash:
d6bce07ecb1648e6936561df44bf4a3bfc17bb0ba5f692646b2301d105b52f67
CI guard: new "ADR-135 calibration witness proof (determinism guard)"
step under the Rust Workspace Tests job, adjacent to the existing
ADR-134 CIR guard. Regressions are unambiguously attributable.
Hardware-in-loop validation: full 600-frame capture exercised via the
new scripts/synth-csi-udp.py emitter targeting 127.0.0.1:5005. The CLI
binary received 600 frames at 20 Hz, z_med stable at ~0.7, motion
correctly NOT flagged, finalised baseline written to baseline.bin (860
bytes) with correct magic + version + timestamp in the header. Live
ESP32 capture from COM9 is operator follow-up — requires provisioning
the firmware's UDP target IP to match the host running the CLI.
Test results (cargo test -p wifi-densepose-signal --no-default-features):
lib: 382 pass / 0 fail / 1 ignored
calibration_synthetic: 17 pass / 0 fail
calibration_drift: 5 pass / 0 fail
calibration_roundtrip: 10 pass / 0 fail
cir_*: 9 pass + 6 documented P2 ignores
doctest: 10 pass
Bench: 20 Criterion combinations registered
(recorder_record / recorder_finalize / deviation / record_600 /
to_bytes across HT20/HT40/HE20/HE40 tiers).
Witness: bash scripts/verify-calibration-proof.sh → VERDICT: PASS
Co-Authored-By: claude-flow <ruv@ruv.net>