The full-helix run and its three-panel figure + reduction tables live
in the pipeline report
(logs/logs_Jun07_26/full_control_run/.../run_PM0827/:
figures/.../inspection_summary.png,
reports/.../report.tex+.pdf). This note is the
story, not the numbers.
desired_speed 0.35 →
0.90 m/s (the knee — past it, tracking degrades and force climbs for
little gain). Retuned the loops that chase the faster reference: arm
velocity feedforward desired_twist.v_max 0.5 → 0.9, base
attitude stiffness gains.K 1.0 → 2.0, and raised
com.v_max so the commanded COM velocity stops clipping.
Effort stayed gentle.product to exponential with all weights at 1.0
— a byte-identical switch that turns the per-term weights from dead
knobs into live exponents. Then retuned reselect cadence for the faster
orbit (update_period/min_hold 125 → 32).# Clean: annotations, emotional comments),
added a ScoringMode enum + default to kill the magic-string
dispatch, promoted one generic lambda to utils.geometry,
and wrote GNC/CLAUDE_README.md +
de_cruft.md.Every step held the line: the 5 pinned baselines reproduce at
0.000e+00 throughout.
Coverage is set by the trajectory, not by guidance tuning. The single biggest lever on inspection duration was orbit speed, because coverage is orbit-traversal-limited. The camera marks whatever its FOV sweeps as the base orbits; which target the scorer picks barely changes the swept area. This reframes future work: to cover more, move the platform, don’t re-weight.
Change the reference speed, re-check every loop that tracks it. The faster orbit left the base attitude controller underdamped — it wandered without settling at the old gain. The fix was more stiffness, and the tell was that torque sat at ~12% of cap: the loop was bandwidth- limited, not effort-limited. Always check headroom before assuming a gain is “right.”
Two clean dead ends, worth remembering. (1) Tuning scoring weights to improve coverage — conservative changes, and even 5×/0× extremes, moved coverage by noise. (2) The tidy arc-per-reselect prediction said cadence ≈ 48; the data said 48 was the worst and 32 won in the late-helix tail. The principle pointed the right direction (smaller), the number came from the run.
A behavior gate makes cleanup fearless. The
byte-identical baseline check let me delete dead code and swap helpers
confidently — anything that changed behavior showed up instantly. The
one swap I held back (angle_between) was exactly where the
gate would have tripped: the helper re-normalizes its inputs (a ~1e-15
perturbation) and adds cost to a hot loop. Checking the helper before
swapping beat discovering it through a broken baseline.
Trust the loader, not the raw YAML. A gain sweep looked completely inert until I traced it: the scalar gains are snapshotted into built matrices at load, so overriding the scalar after load does nothing. Sweeps must touch the built path; committed changes edit the YAML scalar.
simulation.time.duration is coupled to the
orbit, not just run length. The helix is parameterized as
theta = 2*pi*n_rev*(t/duration) over
N = duration/dt samples, so it is the orbit’s nominal
period — the COM must traverse the full arclength within it. To run the
full helix at 0.90 m/s I had to raise duration 500 -> 700 (≈ the ~690
s traversal time); that re-samples the orbit and shifts the trajectory
at the ~0.1 level, so all 5 baselines were re-pinned. The new trajectory
is healthy (full-helix effort matched the tuning-run effort), so this is
a real config change with a re-pin, not a regression. Lesson: a
“run-length” knob can quietly be a trajectory knob — check what a
parameter actually feeds before assuming it’s inert.
Nominal guidance and control are solid at the faster operating point: full coverage over the helix, gentle effort, acceptable tracking, clean code, reproducible baselines. The weights are now live exponents — the natural hook for the uncertainty / risk-aware CVaR scoring that comes next.