Two corrections to the previous commit, both to my own reading.
1. RETRACTED: 'EIG is an overconfidence-seeking criterion'. I read my own
diagnostic backwards. err/var ratio is 1.0 when calibrated, so EIG's
0.96 is the WELL-calibrated end and random's 0.73 means the variance
head assigns falsely HIGH variance to the sets random picks -- sets
that are actually accurate. EIG is not hunting unearned confidence; it
avoids sets the head is over-cautious about, which happen to be the
accurate ones.
2. That two-number comparison was suggestive, not a direct test: it uses
absolute predicted variance on selected sets, while EIG selects on
entropy reduction from a baseline. So I ran the direct test.
DIRECT MEASUREMENT, superseding both readings: across all 27 single-probe
candidates per sample, correlating predicted entropy reduction with
realised squared-error reduction gives mean per-sample Pearson
r = +0.039, with 54.3% of samples positive (chance 50%).
EIG's predicted gain is essentially UNCORRELATED with realised benefit on
this problem. That is why it performs like random selection. Measured:
the criterion is uninformative here. INFERRED, not measured: that
variance-head miscalibration is why. The clean test is post-hoc
temperature scaling on val followed by re-measuring this correlation.
Also records that rung 4 is single-seed, so items 11-12 are one-seed
measurements -- the lesson rung 3 taught.
Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01JJnDvpYYk2rTvjkudQUoCk
Rung 4 gives the variance head sight of revealed values and adds an
observation head so EIG can be estimated by Monte Carlo (peek-free:
candidate values are sampled from the model, never read from the
future). Pre-registered pass condition: beat no-probe AND beat random,
both by >2 SE.
Result: DID NOT CLOSE THE GAP, and it went backwards. MC-EIG is worse
than random (t=-4.13 at K=9) and worse than no-probe (t=-9.07), closing
-40% of the oracle gap. The target-peeking bound is unchanged (+2.9 pts,
t=+22.4), so the information is still demonstrably there.
THE MECHANISM, now measured rather than hypothesised. At K=9 MC-EIG
selects probe sets with LOWER predicted variance (2.91e-4 vs random's
3.45e-4) but HIGHER actual squared error (2.78e-4 vs 2.52e-4).
Error/variance ratio, 1.0 if calibrated: 0.96 for EIG vs 0.73 for random.
Expected-information-gain selection under a miscalibrated predictive is
an OVERCONFIDENCE-SEEKING criterion. EIG hunts the sets the model claims
will shrink its entropy most; when the variance head is miscalibrated
those are disproportionately the sets where confidence is unearned.
This explains the whole arc: every rung failed its calibration criterion,
and EIG's failure is downstream of that. Calibration is a PRECONDITION
for information-gain probing, not a nice-to-have. The next step is a
calibrated predictive (low-rank covariance, heavier tails, or post-hoc
recalibration), with EIG re-tested only once coverage passes -- not a
better EIG estimator.
Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01JJnDvpYYk2rTvjkudQUoCk
Rung 4 removes the exact limitation the rung-3 bound diagnosed: the
variance head will SEE revealed values, and a new observation head
supplies p(z_t+1[S] | z_t, revealed) so EIG can be estimated by Monte
Carlo instead of a closed form that cannot represent value-dependence.
Still peek-free -- candidate values are sampled from the model, never
read from the future.
Pass condition fixed in advance: MC-EIG-greedy must beat no-probe by
>2 SE at some budget AND beat random by >2 SE with a consistent sign
across >=3 seeds. Anything less is 'did not close the gap', with the
fraction of the oracle gap closed stated as a measurement.
Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01JJnDvpYYk2rTvjkudQUoCk
Runs the discriminating experiment: does an oracle that selects probes
using the TRUE y_t+1 beat not probing at all, at rung 3?
Answer: YES, decisively, and against the intuition.
K=3 oracle 0.9620 vs no-probe 0.9416 (+0.0204, t=+19.5)
K=9 oracle 0.9705 vs no-probe 0.9416 (+0.0290, t=+22.3)
So the marginal information in z_t+1 bands is NOT absent. Persistence
R2 0.871 suggested it might be; that inference was wrong. Probing can be
worth ~+2.9 pts at K=9. EIG-driven selection cannot find it -- it loses
0.4 pts instead of gaining 2.9, a ~3.3 pt gap to the bound.
Mechanism, and it is a design choice made here: the variance head is
deliberately blind to revealed VALUES (it sees only z_t and the mask),
which is what kept EIG closed-form and peek-free. The bound shows probe
value is value-dependent, not merely mask-dependent -- the oracle
exploits which values actually came back, and a mask-only variance head
structurally cannot represent that.
The fix is the observation head the original design constraint called
for and which was traded away for tractability: sample candidate
z_t+1[S] from p(z_t+1[S] | z_t, revealed) and average the resulting pose
entropies (Monte-Carlo EIG). That is the next experiment.
Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01JJnDvpYYk2rTvjkudQUoCk
Seeding Burn's parameter RNG (LinearConfig::init draws from the global
RNG, which the LCG never covered) and rerunning across three seeds
retracts the headline rung-3 claim from the previous commit.
RETRACTED: 'EIG-greedy beats random by 3-5 SE at every budget'. That came
from one unseeded init. Across seeds 2026/7/101 the sign is not stable
(K=3: -0.56 / -1.47 / +1.53; K=9: -1.67 / -2.92 / +0.39). Criterion 3
does not reproduce, so rung 3 fails ALL THREE pre-registered criteria.
ROBUST, and the real finding: probing never helps. Paired against not
probing at all, EIG-greedy scores t = -5.0, -8.5, -10.2 (seed 2026),
-5.0, -8.7, -9.8 (seed 7), -7.2, -12.7, -15.4 (seed 101) at K=3,6,9.
any_budget_where_eig_greedy_beats_no_probe_by_2se is false on every seed,
and it degrades monotonically with budget. Revealing time-averaged
z_t+1 bands adds no information about y_t+1 beyond z_t, in either model
class -- it actively costs accuracy.
Point accuracy is robust: no-probe PCK@20 0.9424/0.9375/0.9452 vs bar
0.7557. The +18.7 pts is entirely the nonlinear mean head.
The NLL caveat is now MEASURED rather than speculated: diagonalising
rung 2's pose posterior (like-for-like against rung 3's diagonal head)
gives -81.92 vs rung 3's -94.9..-102.0. Heteroscedasticity does pay on
marginals; rung 2 won joint NLL via its full 30x30 covariance. Criterion
1 still FAILS as written and is not being rewritten.
Rung-3 runs are now seeded via RUNG3_SEED (default 2026) and
reproducible; per-seed results in results/rung3_seed*.json.
Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01JJnDvpYYk2rTvjkudQUoCk
M4's conclusion differs by model class, so the milestone line now states
both: rung 2 is an honest negative (underpowered-proof at n=20,000),
rung 3 shows EIG-greedy beating random by 3-5 SE at every budget. The
rung-2 negative was a property of the model class, not of EIG probing.
Adds a TL;DR so a resuming session gets the state in one screen,
including the two standing prohibitions: no calibrated-uncertainty claim
from rung 3, and no comparison to the 96.09% full-CSI number.
Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01JJnDvpYYk2rTvjkudQUoCk
Verdict by the pre-registered conjunction: RUNG 3 DID NOT HELP. 2 of 3
criteria missed, and that verdict stands. But the criteria conflated two
questions and rung 3 answers them oppositely:
1. beats rung-2 pose NLL FAIL (-102.83 vs -113.29)
2. coverage within +/-3pp all lvls FAIL (+7.46pp @50%, +3.20pp @80%)
3. EIG-greedy beats random >2 SE PASS decisively
THE CENTRAL FINDING: the M4 negative at rung 2 was a property of the
model class, not of information-gain probing. Once Sigma depends on z_t
so the probe set can vary per sample, EIG-driven selection beats random
at every budget (t = +3.11, +4.80, +5.04, +3.91 at K = 1, 3, 6, 9).
Adaptivity is real: 19,653 distinct probe sets across 20,000 samples at
K=9, where rung 2 had exactly 1 by construction.
Point accuracy also jumps: PCK@20 0.9425 vs bar 0.7557 (+18.7 pts),
against rung 2's ~0.8208 on the same pairs -- a nonlinear mean head is
worth ~+12 pts. MPJPE 0.01073. Degeneracy guard passes (pred std 0.0208).
Caveats recorded rather than hidden behind:
- The NLL comparison is not like-for-like and that is this design's
fault: rung 2 carries a full 30x30 pose covariance, rung 3 a diagonal
one, and joint NLL strongly rewards cross-keypoint correlation. Still
reported as a FAIL against the criterion as written; the fix is a
low-rank+diagonal head, not abandoning heteroscedasticity.
- Calibration got WORSE, not better (+7.46pp vs rung 2's +3.48pp at the
50% level), same direction: over-covered at the centre. Rung 3's
uncertainty is less trustworthy than rung 2's and no calibrated-
uncertainty claim may be made from it.
- Rung 3's 0.9425 exceeding rung 2's 0.8568 target-peeking bound is not
a contradiction: that bound held the linear-Gaussian predictor fixed
and bounded probe policies within that model class only.
Methodology change recorded: training length went from a fixed 12 epochs
to early stopping on val NLL (patience 40, cap 400) after the first run
was obviously undertrained -- 12 epochs took 16s with the loss still
descending steeply. A convergence fix, not a change to any pre-registered
criterion. Both runs kept; results/rung3_undertrained_12ep.json preserved.
M1/M2 guards re-verified after all representation changes: 12/12 tests
pass, validate still exits PASS on all 6 checks.
Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01JJnDvpYYk2rTvjkudQUoCk
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>