Doctoral Research · Space Robotics Inspection with a Free-Flying Space Manipulator
A Doctoral Research Journal Aerospace Engineering

Controller twin extraction — BreveCoreController

Date: 2026-06-22 · Status: DONE, byte-identical (5/5 baselines 0.000e+00)

What prompted this

The structural-clone detector (validation/ast_duplication.py) flagged GNC/base_controller.py (BaseController) and GNC/breve_controller.py (BreveController) as carrying ~12 byte-identical method bodies (Jaccard J=1.00 — compute_tau_b_oplus shared 125 subtrees, all_control_terms 85, _apply_step_state_update 94, J_xb, x_b_tilde, …). Two controllers maintained in lock-step by hand is where a fix lands in one and silently not the other. The remedy: hoist the shared bodies into one place.

Both classes already inherit the same root, CC_Controller (GNC/com_controller.py). So the duplication has a clean home: a new intermediate base both classes inherit.

The investigation (verified at the source, not from the detector’s labels)

Reading all three files method-by-method partitioned the surface into three groups:

  1. Byte-identical twins (13) → hoist to the shared base. _loop_sync_uses_update_views, _build_base_desired_for_step (+ the base-path build_desired_for_step), x_b_tilde, x_b_tilde_dot, J_xb, x_tilde, J_x, v_breve_damping_error, conditioning_scale, compute_tau_b_oplus, all_control_terms, _apply_step_state_update. Identical computations in both classes.

  2. Genuinely forked leaves → leave in place. The hoisted methods call self.x_e_rhs(), self.J_xe(), self.omega_b_damping_error(), self.nu_e_damping_error(), self.arm_breve_gains(), self.rhs_feedforward(), self.v_breve_dot(), self.RHS34b(), self.calc_posture(). These differ between the two controllers (Base zeroes the EE/arm path; Breve runs the full coordinated law with implicit damping, velocity feed-forward, CHAIN-5 posture, null-space reconstruction). Dynamic dispatch resolves each call to the subclass’s own version, so the one shared template produces two behaviours. This is the Template Method pattern — and it is why hoisting the identical methods is safe without touching the forks.

  3. Redundant parent-overrides → delete. BaseController._sync_loop_state and ._update_breve_views were byte-identical to CC_Controller’s own versions (verified against com_controller.py:316-326). Pure dead overrides; deleted so Base inherits the parent’s identical implementations.

What changed

Why it is correct (the proof, not an assertion)

A pure relocation of byte-identical method bodies along the existing dispatch graph cannot change the closed-loop trajectory. The certificate is the five method-level baselines, checked, not re-pinned (per .claude/rules/GNC.md): an unchanged closed loop must reproduce max_abs_diff = 0.000e+00 at ATOL 1e-9.

baseline controller regime max_abs_diff
validate_base_baseline BaseController base-only 0.000e+00
validate_ee_initial_baseline BreveController INITIAL 0.000e+00
validate_ee_targeting_baseline BreveController TARGETING 0.000e+00
validate_ee_coverage_baseline BreveController COVERAGE 0.000e+00
validate_ee_tracking_baseline BreveController TRACKING (40 s) 0.000e+00

The detector re-run on GNC/ confirms the elimination: 0 pairs ≥ 0.85. The highest remaining Base↔︎Breve pairs are the deliberately forked methods — calc_posture (0.83), reconstruct_generalized_velocity (0.82), RHS34b (0.75) — which score below threshold precisely because they genuinely differ. The lock-step copy-paste is gone; the real differences between the diagnostic and the full controller now read as intentional.

Limitation / honest scope

This removes Type-1/2 duplication (identical bodies). It does not attempt to unify the forked methods (RHS34b, calc_posture, v_breve_dot, reconstruct) — those are real behavioural differences and a further Template-Method refactor of them is a separate design decision, not a byte-identical move.