Generated 2026-06-06 (TASK_2). Report only — no code modified.
Inspected: GNC/BreveController.py, GNC/base_guidance.py, and (for context only) GNC/com_controller.py, GNC/guidance/ee_guidance.py, GNC/guidance/guidance_classes.py, analysis/runner.py.
BaseController + BaseGuidance need nothing
from the camera/EE guidance — its EE error terms are clean
zero-placeholders. So yes, we stop at the base (step-1
question) and nail it down the way COM was nailed down, before touching
the arm.EEController re-implements the control skeleton
instead of overriding small hooks. Collapse the duplicated
RHS34b / x_tilde /
v_breve_damping_error into the shared parent;
EEController keeps only two hooks plus its EE error
owners.base and arm configs (one script, identical
config, monkeypatch only the thing under test — per the COM validation
pattern).GuidanceMode.INITIAL
(deterministic, low-error, no target_finder) to exercise
the EE control path end-to-end. The full targeting state machine comes
after.Two parallel inheritance chains, each with COM at the root:
| Layer | Controller | Guidance |
|---|---|---|
| COM (proven) | CC_Controller (com_controller.py:22) |
COMGuidance |
| + base attitude | BaseController (BreveController.py:31) |
BaseGuidance (base_guidance.py:36) |
| + end effector | EEController (BreveController.py:224) |
EEGuidance (GuidanceMode state machine +
target_finder) |
CC_Controller owns the control loop, integration, and
COM force terms. It exposes zero-placeholder task forces —
compute_tau_b_oplus → 0 and
compute_omega_e_oplus → 0 (com_controller.py:163-167) — that
subclasses override. This is the “perfect COM” root we keep
untouched.BaseController adds the base-attitude error/torque path
and a RHS34b / v_breve_dot. Its EE terms are
deliberate zero-placeholders (BreveController.py:91-98), so the
base controller is self-contained: it pairs with
BaseGuidance and never imports the EE guidance.EEController fills the EE terms in with real
error/Jacobian/twist logic.make_controller selects the controller via
match on controller.name
("com"/"base"/"arm").| Duplicated method | Base copy | EE copy | What actually differs in the EE copy |
|---|---|---|---|
RHS34b |
:120 | :449 | arm_scale on the arm D/K
block + an accel feedforward term |
x_tilde |
:84 | :296 | folds real x_e_tilde + integral + axis-only
projection |
v_breve_damping_error |
:97 | :327 | real EE twist error + roll-damping scale |
reconstruct_generalized_velocity |
com_controller.py:207 | BreveController.py:206 | Tikhonov-regularized solve vs plain lstsq |
| integral update | _update_com_integral_state com_controller.py:272 |
update_x_e_integral BreveController.py:263 |
same gate/leak/clamp shape, different names |
The RHS34b block is the big one: ~30 lines of
C_breve/D_breve/K_breve/
COM_coupling/posture assembly is copied
verbatim, and only the two EE additions are genuinely new.
Step-1 — “Can you stop here, or do you need the rest of the
guidance?” Stop here. Nothing in the base path
reaches into ee_guidance.py; the EE error terms are already
zeroed cleanly. The base is the easy win and is largely implemented — it
just needs the streamline + a validation pin, exactly as COM did.
Step-2 — “How much EE guidance should we pull in?”
Recommend Option 1 (minimum): take
GuidanceMode.INITIAL as the EE baseline. It is
deterministic and low-error, needs no target_finder, and
exercises the full EE control path so we can trust it before adding the
targeting state machine. Option 2 (keep separate) perpetuates the
duplication you want gone; Option 3 (import everything) risks overload
before the base is nailed. Defer this to the EE phase — Task 4
below.
Hoist the shared skeleton up to BaseController and
reduce EEController to small overrides. Two new hooks
default to no-ops in the base, so the base path is unchanged:
# BaseController — defaults make the base path identical to today.
def arm_gain_scale(self): # EE overrides with gamma conditioning
return 1.0
def rhs_feedforward(self, st, des): # EE overrides with accel feedforward
return 0.0
def RHS34b(self, st, des):
# ... single copy of the C_breve / D_breve / K_breve / COM_coupling / posture
# assembly, using self.arm_gain_scale() on the arm D/K block ...
return sanitize_column(rhs_total) + self.rhs_feedforward(st, des)
x_tilde / v_breve_damping_error already
dispatch through x_e_tilde / J_xe (zero in the
base), so a single parent version works once the EE-specific axis-only
projection and integral folding move into
x_e_tilde — its natural owner. Keep
reconstruct_generalized_velocity’s regularized override
as-is (it is a real behavior difference, gated by config), but drop the
duplicated logic from the EE layer where it only re-derives the base
version.
This respects the COM-first layering, removes the copy-paste, and stays behavior-preserving — each step validated by a saved-log A/B.
RHS34b via no-op hooksGoal. Remove the duplicated RHS34b. One
copy lives in BaseController, parameterized by
arm_gain_scale() and rhs_feedforward() hooks
that default to 1.0 / 0.0 so the base path is
byte-identical and EEController overrides only the two
hooks.
Modify - GNC/BreveController.py — add the two
hooks to BaseController; collapse
EEController.RHS34b (:449) into the parent.
Validation - Saved-log A/B: run the
base and arm configs before and after; assert
the logged forces/state match to numerical tolerance (one script,
identical config, monkeypatch only RHS34b).
py_compile the module.
x_tilde and
v_breve_damping_errorGoal. Collapse the duplicated error/damping assembly
to single parent methods driven by the x_e_tilde /
J_xe hooks. Move the EE-only axis-only projection and
integral folding into x_e_tilde so the
parent assembly is task-agnostic.
Modify - GNC/BreveController.py —
x_tilde (:84 / :296),
v_breve_damping_error (:97 / :327), and
EEController.x_e_tilde.
Validation - Same saved-log A/B for
base + arm; confirm x_tilde /
damping vectors match pre-refactor. py_compile.
Goal. Give the streamline tasks a fixed yardstick: a
short, saved reference log for the base controller at a
matched config, plus a tiny A/B comparator, so behavior-preservation is
checkable in seconds without a full sim.
Modify - A focused validation script under the existing test/validation location (reuse the COM A/B pattern; do not add a new framework).
Validation - The comparator reports max abs diff ≈ 0 against the pinned baseline on an unmodified tree.
GuidanceMode.INITIAL
onlyGoal. After the base is solid, exercise the EE
control path end-to-end using only GuidanceMode.INITIAL —
the deterministic, low-error baseline pose — with the
target_finder / targeting state machine left out.
Modify - GNC/guidance/ee_guidance.py / GNC/BreveController.py — wire
EEController to drive only the INITIAL pose path; gate the
targeting machinery off.
Validation - Short smoke: EE pose tracks the INITIAL baseline with low error and bounded control effort over a few seconds of sim.