Repository navigation
feat(g1): Unitree G1 support, scope A (right arm + Dex1-1) [draft, sim gate pending] - #601
Draft
ZhangHanDong wants to merge 64 commits into
Draft
ZhangHanDong wants to merge 64 commits into
ZhangHanDong wants to merge 64 commits into
Conversation
Add the g1_right wire order (7 right-arm joints, then the two prismatic Dex1-1 fingers of the official g1_29dof_mode_15_with_dex1_1 URDF) as the shared contract both G1 lanes build on. Frozen file: env_hash.json regenerated. Draft only (feat/g1-support, not for main until owner review). Requirement IDs: TC-5 (draft amendment), CON-7 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pin upstream unitree_ros commit 5994d4f: the URDF, all 39 referenced meshes (relative layout unchanged), upstream BSD-3-Clause LICENSE, and the g1_description README as UPSTREAM_README.md. PROVENANCE.toml pins repo/commit/path/license/retrieval-date. Files are verbatim copies; no URDF edits (mesh-path concerns are handled in loading code later). Requirement IDs: TC-5 (draft amendment), CON-7 (frozen-set asset add, authorized verbatim by outer-loop item moxin-org#2)
FROZEN_DIRS gains assets/g1; env_hash.json regenerated (n_files 130). New test mirrors test_so101_asset_change_changes_hash: URDF or mesh tampering must invalidate the hash. Requirement IDs: CON-7 (frozen-set widening authorized verbatim by outer-loop item moxin-org#2), CON-5
Mirror test_so101_contract.py for g1_right: PROVENANCE pins unitree_ros commit 5994d4f / robots/g1_description path / BSD-3-Clause, LICENSE and all 39 vendored meshes verified, right-arm 7 joints match G1_RIGHT_ARM_JOINTS order, Dex1-1 fingers prismatic [-0.02, 0.0245], total actuated DOF 33 (29 body + 4 fingers; floating base excluded), robot name g1_29dof_mode_15 / mode_machine 15. Requirement IDs: TC-5 (draft amendment), SCN-4, CON-5
g1_right wires 9 of the G1's 33 DOFs; the rest are held at home. profile_held_joint_names / held_dof_indices join the contract: a profile with held_joint_names must partition the robot's DOFs (wire ∪ held == all, disjoint), and profile_dof_indices returns wire indices only. from_wire_joint_order gains base= for held-at-home native vectors (copy of base, wire positions overwritten); base=None keeps legacy bit-identical behavior. franka/so101/mobile profiles without held_joint_names are untouched (legacy coverage rule still enforced). Unit tests use a fake 33-DOF Robot in the so101 style: shuffled parser order, overlap/gap rejection, held positions preserved, round-trip, legacy non-regression. Frozen file: env_hash.json regenerated. Requirement IDs: TC-5 (draft amendment), CON-7 (authorized verbatim by outer-loop item moxin-org#2)
Record why scope A wires only the right 7+2 (recoverable, same wire shape as franka), why g1_29dof_mode_15_with_dex1_1 (up-to-date 5010 wrist machine matching the lane robot, only official Dex1-1 description), and why held_joint_names is an explicit list rather than hold_unlisted_joints (fail-loud partition vs silent asset drift). mode_machine must stay 15; frozen-set/TC-5 changes await owner review under CON-14/CON-7; draft stays off main until then (ADR-66 ordering). Requirement IDs: CON-15 (ambiguity resolved by ADR), CON-7, TC-5 (draft)
Third embodiment profile: g1_right n_dof=7+2 with identical bridge semantics; every G1 DOF outside the wire is held at home via held_joint_names, and wire ∪ held MUST partition the robot's DOFs (ADR-g1-embodiment, PROPOSED — draft amendment, not for main until owner review under CON-14). Requirement IDs: TC-5
The frozen-set widening in 16f0589 added assets/g1 to FROZEN_DIRS but left harness/CLAUDE.research.md's frozen-path list (and the generated docs inventory) describing the old set — the drift-proof tests (test_frozen_set_rule_names_every_frozen_path, test_committed_inventory_matches_this_tree) caught both. Line-count neutral edit to the contract; inventory regenerated via tools/docs_inventory.py --write. Requirement IDs: CON-7, HAR-5
40c0a0c's one-token edit to harness/CLAUDE.research.md made the committed mon-treatment-table rendering stale (the table anchors the contract's bytes). Regenerated via harness monolith table --write; table_report ok=true, rows=14, errors=[]. Requirement IDs: MON-1, MON-14
The Unitree G1 hardware bridge behind the so101-driver topic surface: wire 7+2 on the frozen g1_right order, injected ArmBus/GripperBus with loopback doubles (loopback never imports unitree_sdk2py/cyclonedds), gradual arm_sdk weight takeover/release, lease watchdog (AISLE_G1_LEASE_S, no silent re-arm; the reset refusal is the explicit re-arm), mode_machine 15 gate, normalized 0..1 gripper round-trip with URDF-travel finger mapping, and hardware_monotonic stamps (HWP-7 wording). The three SO-101 gaps (no lease, radians-in-joint-slot gripper, unnormalized state) are closed by construction. Real-bus facts cited file:line@commit: unitree_sdk2py@814556d1 (rt/arm_sdk LowCmd_ slots 22-28, weight slot 29, kp60/kd1.5, CRC, example g1_arm7_sdk_dds_example.py); dex1_1_service@ced3216 is a C++ serial<->DDS bridge with no confirmed Python interface, so Dex1GripperBus raises HardwarePending rather than guessing a protocol. - adapters: run_scenario(dof=..., default SO101_DOF, rows==12 pin unchanged) + G1-only mode_mismatch scenario outside the SO-101 SCENARIOS tuple - registry: g1-driver manifest, g1_right arm enum (schema + 7 fixture copies), curated_core, EXPECTED_IDS, reserved_dists mirror/stub, confinement DENIED_IMPORTS + fault-injector FROZEN_ASSETS symmetric with so101 - tools/g1_runtime.py: out-of-lock SDK2 install/verify/receipt pinned to 814556d1 + cyclonedds==0.10.2, gitignored receipt (ADR-67 pattern) - ADR-g1-driver (PROPOSED): interface choice, lease semantics, finger mapping, Dex1-1 status, out-of-lock rationale - treatment-table regenerated (registry change made it stale) Requirement IDs: TC-2, TC-5, TC-6, HWP-5, HWP-6, HWP-7, HWP-8 Known-red (expected per outer-loop addendum): test_freeze_registry.py::test_committed_registrations_check_clean_with_withheld_seeds (successor registration is G-I's, not self-generated)
…1-D-r1) Outer-loop review moxin-org#3 fixes, tests written first citing HWP-6/HWP-8/TC-5: 1. Release is now GRADUAL (safety): when not following, the driver writes the ramped self.weight at the current pose q7 instead of a hard 0 — weight monotonically non-increasing over >= ceil(takeover_s/dt) beats (test_release_is_gradual_monotone_after_lease_expiry). 2. Gripper semantics match the CONTRACT (env/CONTRACT.md:19,41: 0.0 = open ... 1.0 = closed; env/limits.toml:24-26). Which finger position is open is a geometry fact: unitree_ros@5994d4f g1_29dof_mode_15_with_dex1_1.urdf:1113-1119/:1142-1147 finger axes (0,-1,0)/(0,1,0) diverge with +q, so upper limit +0.0245 m = open, lower -0.02 m = closed. finger = 0.0245 - grip*0.0445; exact inverse. 3. Startup no longer misjudges the machine: mode_machine None (no rt/lowstate yet) -> reason awaiting_state, not following, NOT latched; an integer that is present and != 15 keeps the latched refusal. 4. Gripper freezes at its current position on lease expiry instead of chasing the stale target (write(read()) while not following). 5. Inventory: ADR heading made parseable (# ADR-g1-driver — ...) with a literal Status: line; docs/generated/project-inventory.md regenerated. Requirement IDs: HWP-6, HWP-8, TC-5 (also TC-2, TC-6, HWP-5/7 unchanged)
Generated files (monolithic treatment table, project inventory) conflicted between task/g1-a and task/g1-d; resolved by regenerating with harness monolith table --write and tools/docs_inventory.py --write. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
De-hardcode the harness and the embodiment-agnostic nodes; kinematics- bearing nodes keep refusing by name until G1-F. - cli.py: rollout --embodiment adds g1_right (rollout ONLY — the research-bound exposure/monolith tables at :245/:299/:320/:325 stay) - validate.py: EMBODIMENT_ARM maps g1_right -> g1_right - manifests opened to g1_right (source audit: zero embodiment branches): oracle-pose, rollout-client, turn-barrier (+ reserved_dists mirrors); excluded with reasons (source branches on franka/so101 or reads physics[embodiment]): dora-genesis, arm-driver-sim, gripper-driver-sim, camera-source (all share dora_genesis.py so101_latch/profile), reset (hardcoded franka home_qpos), verifier-oracle + task-state-machine (resolve_layout) — listed in the ACK per the entry - ik_trajectory: _require_supported_embodiment guards fk_tcp / _pose_error / _ik_once / ik_solve; grasp_topdown: require_supported_embodiment wired into main() — unknown kinds raise ValueError instead of silently solving the Franka DH chain - tests/unit/test_g1_embodiment_gating.py: 14 tests (parser accept/ refuse, open nodes pass under g1_right, pinned trio keeps EMBODIMENT_MISMATCH, franka/so101 no regression, mirrors in sync) Requirement IDs: VAL-2, HAR-1 (MOB-4 mapping pinned unchanged)
…zation (G1-G)
- docs/hardware/g1/: the seven SPEC-520 template artifacts adapted to G1 —
station manifest (mode_machine=15, Dex1-1 version/serial, PC2/Jetson,
192.168.123.x network, remote controller + e-stop), safety case (balance/
fall authority stays with the motion controller per HWP-12 wording; hoist,
damping mode, weight-ramp release hazard), calibration (joint zeros from
rt/lowstate radians not feetech counts; Dex1 travel pinned to the URDF
limits; head + fixed overhead camera extrinsics; dual clock-domain time
mapping), execution ladder (torque-off telemetry -> hoist no-load ->
loaded; weight-ramp/lease drills), protocol/reset/scorer templates — all
hardware_pending
- hardware_phase.py: execution_checklist/hardware_report take embodiment;
the so101 blocker wording stays byte-identical (pinned by
test_hardware_phase.py:280), g1_right names the G1 station
- cli.py: harness hardware dry-run/report gain --embodiment
{so101,g1_right} (default so101, existing output unchanged); g1_right
runs the 15 SPEC-520 scenarios + mode_mismatch at dof=9
- tests/unit/test_g1_hardware_phase.py: 8 tests (templates validate,
unsigned safety case blocks, 16 scenarios reproducible at dof=9, so101
default byte-stable with/without --embodiment, blocker parameterized)
Requirement IDs: HWP-8, HWP-18, HWP-20 (G1H-1..8 cited in docstrings)
Status: PROPOSED, HUMAN-REVIEW GATED, under the ADR-66 freeze discipline. Adapts SPEC 520 to g1_right; only G1 differences are clauses G1H-1..8: station identity includes mode_machine=15 and the SDK2 runtime receipt (G1H-1); Dex1-1 interface stays hardware_pending behind the driver's HardwarePending refusal (G1H-2); gradual weight-ramp takeover/release, lease expiry freeze + explicit re-arm (G1H-3); balance/fall authority stays with the motion controller per HWP-12 wording (G1H-4); joint zeros from rt/lowstate radians and Dex1 travel pinned to the URDF limits (G1H-5); dual clock-domain time mapping (G1H-6); behavioral-only reset with SPEC 520 scoring consequences verbatim (G1H-7); torque-off -> hoist no-load -> loaded ladder with weight/lease drills (G1H-8). G1H-1/3/4/5/7/8 are cited by tests/unit/test_g1_hardware_phase.py docstrings; G1H-2/G1H-6 are waived in tools/trace_waivers.toml until their hardware-adjacent artifacts exist. Specs are frozen pending human review (CON-14): this draft adds a file, it does not amend any accepted spec.
…-E, G1-G) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
G1-E edited sources the monolithic treatment table hashes; regenerated with harness monolith table --write (MON-1/MON-14 evidence current). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
[embodiment.g1_right] physics profile (ADR-g1-embodiment scope A): wire = right arm 7 + right Dex1-1 2 (TC-5 draft); 24 held DOFs at standing home (all zeros, FK-verified soles flat); pelvis fixed at 0.7872 (FK sole probe, collision spheres r=0.005 at ankle z=-0.03); Dex1-1 stops from the URDF ([-0.02, 0.0245], +q separates => open); CoACD full decomposition threshold 0.0 (addendum-2 ruling); grasp center x=0.0978 (finger AABB mid-x), TCP rpy [0, pi/2, 0] (palm +x -> tool +z, top-down); waist-height shelf/tray/slot lattice inside the measured shoulder sphere (worst |pregrasp-shoulder| 0.373 <= 0.42 chain); trajectory/ik keys at so101 scale marked 待 G1-F 调. pharmacy.py: G1_URDF constant; g1_urdf_options (fixed pelvis at FK height); robot_morph() replaces the silent fall-through — unknown embodiments now raise ValueError before importing genesis (SCN-4, CON-12); native_home_qpos() fills held DOFs from held_home_qpos and overwrites only wire positions (from_wire_joint_order base=), legacy profiles keep the exact np.empty semantics; held-DOF kp/kv from held_kp/kv with the held home as the PD target (gravity droop, SCN-4); both home construction sites (build + _assert_reachable) routed through it. nexus/rapier dev backends reject g1_right explicitly (Genesis only). Tests: test_scene_cfg gains g1_right in all four embodiment loops plus profile key/length/URDF-partition checks, g1_urdf_options pinning, and the no-sim unknown-embodiment rejection. Frozen set authorized by item moxin-org#3; env_hash.json regenerated. Requirement IDs: TC-5 (draft), SCN-2, SCN-3, SCN-4, CON-12
… gains
Three fixes from the first sim run, each with its evidence:
- Genesis collapses the massless fixed right_dex1_base_link (like SO-101's
gripper_frame_link): ee_link moves to the surviving right_wrist_yaw_link
with the composed offset 0.0415 + 0.0978 = 0.1393 along link +x; pitch
+pi/2 faces the palm down for a top-down TCP (the near column solved at
rot err 0.44; -pi/2 flipped the palm backward and pushed it to 1.86).
- Slot lattice: the x=0.36 column sat at |target-shoulder| 0.397 against
the 0.394 vertical-forearm chain budget (IK pos err 0.076), and the
first fix's x=0.22 column structurally violated the board front edge
(needs >= 0.225-0.230); cross-column y stagger must exceed 0.0625 (the
amoxicillin+metformin half-width pair + min_separation). New lattice
x=0.24/y{+/-0.16,0} + x=0.28/y{+/-0.08}: exhaustive single-pair/pairwise
probe ALL FEASIBLE, sampler 200/200 seeds, worst |pregrasp-shoulder|
0.368 <= 0.394. Shelf dropped to waist height (z 0.76) accordingly.
- Held-DOF PD: kp 200 left a 0.0432 rad static droop (waist/hip gravity
torque ~8.6 N*m / kp); 800/40 holds it under 0.012 rad (200-step sim
assert < 0.02 passes).
New sim test test_g1_right_profile_builds (SCN-4, TC-5): 33 DOFs, wire
qpos == home, held qpos == held home, reachability_errors == [], held
droop < 0.02 rad after 200 steps. Passes in ~19 s (cached G1 build).
env_hash.json regenerated for the frozen-set physics.toml change.
Requirement IDs: SCN-3, SCN-4, TC-5 (draft)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The G1-F1 motion block: one official-URDF chain shared by the guard and the planner, world-frame FK, and a Genesis cross-check. - kinematics.py: UrdfChain gains optional fixed base/tip transforms (identity defaults — so101 bit-for-bit unchanged); cached g1_right_chain() = torso_link -> right_wrist_yaw_link with the pelvis fixed at the FK standing height 0.7872 m and the three waist joints at the a7dd234 all-zeros held home (origins only), plus the Dex1 grasp- center TCP: fixed right_base_joint 0.0415 + finger-AABB mid-x 0.0978 = 0.1393 m, rpy [0, pi/2, 0] (derivation in comments); joint order MUST equal G1_RIGHT_ARM_JOINTS (TC-5 wire = URDF document order). - env/limits.toml [embodiment.g1_right]: URDF 7+2 position stops, the conservative velocity envelope arm min(URDF, 2.0) rad/s and fingers 0.2 m/s (rationale appended to ADR-g1-embodiment), workspace AABB covering the a7dd234 shelf/tray/staging band and home TCP while excluding the torso column and the sub-waist region (calculation in comments), fallback_qpos = the a7dd234 wire home, gripper 0/1 with 0.2/0.0445 normalized rate. - budget_guard.fk_ee_pose: g1_right branch -> g1_right_chain().forward (world frame, same frame as the workspace AABB). - budget-guard manifest + reserved_dists mirror: arm opens g1_right. - tests/unit/test_g1_motion.py (5): chain/limits provenance, gripper 0/1 <-> finger-stop round-trip, deterministic contained FK with legal zero-violation and wild-clamped commands, workspace cover/exclusion probes, so101/franka non-regression. - tests/sim/test_g1_fk_parity.py: Genesis cross-validation over five deterministic configurations (direct wire-DOF indexing; Genesis drops the massless right_dex1_base_link so the probe anchors at right_wrist_yaw_link with the full 0.1393 offset) — worst error 1.222e-07 m / 1.520e-07 rad vs the 1e-4 / 1e-3 gate. - env_hash regenerated (CON-7); treatment table and project inventory regenerated (derivative checks green). Requirement IDs: BG-1, BG-2, BG-3, CON-5, CON-7, SCN-4
env_hash.json conflicted with G1-C1 (both touch the frozen set); regenerated with tools/env_hash.py --write. Verified on the merged tree: 391 unit passed, Genesis-vs-chain FK parity 1.2e-7 m. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TC-5 (G1-C2): profiles with held_joint_names carry a PARTIAL wire. n_dof becomes the wire length (9 for g1_right) for bridge_info, joint_state, and the joint_cmd length gate (extracted as require_wire_joint_cmd for unit-testability); legacy full-coverage profiles keep n_dof == robot.n_dofs bit-for-bit. home_hold is built as a full-length native vector (held DOFs at held_home_qpos via from_wire_joint_order base=); apply_commands overwrites ONLY wire positions — no command may ever touch a held DOF, whose PD target stays held home. teleport_reset re-homes both partitions through the same full-length home. tests/sim/test_g1_bridge.py (SCN-4, TC-5): a wire command moves the commanded arm joint decisively (0.1 rad steady-state PD error under default gains — tuning deferred to G1-F) while every held joint stays within 0.02 rad; over/under/native-33 joint_cmd payloads are rejected. Requirement IDs: TC-5 (draft), SCN-4, BRG-1
dora-genesis, arm-driver-sim, gripper-driver-sim, camera-source, verifier-oracle, task-state-machine, reset carry g1_right beside franka/so101, with matching reserved_dists mirrors (byte-pinned by test_validator). Gating tests pin the outcome: a g1_right expert_t0 validation now reports EMBODIMENT_MISMATCH for EXACTLY the G1-F-pinned trio (budget-guard, ik-trajectory, grasp-planner-topdown). Requirement IDs: TC-5 (draft), VAL-2
…ied arms G1-C2 (TC-6, ADR-34): get_runtime() builds the BehavioralRuntime from the run's AISLE_EMBODIMENT instead of hardcoded franka (layout and home_q), and behavioral requests for embodiments whose RST-2 motion chain is not verified (reset/motion.py pins Franka limits and a 7-dof home; so101 and g1_right would be driven by wrong limits and a wrong-length home) are REFUSED on reset_refused with a reason naming the embodiment and the teleport alternative — the runtime is never built, so no Franka limits can leak into a non-Franka graph. Teleport (mode 0) is untouched. Requirement IDs: TC-6, RST-2, ADR-34
test_g1_right_schema_conformance (A1, TC-5 draft, SCN-4): with AISLE_EMBODIMENT=g1_right the bridge announces embodiment g1_right, n_dof 9 (the wire, never the 33 native DOFs), joint_state is 9 long with names == G1_RIGHT_JOINTS, gripper_state stays normalized [0,1]. Derivatives for the frozen-set/anchored-byte changes: env_hash.json (reset/service.py), docs inventory, monolith treatment table. Requirement IDs: TC-5 (draft), SCN-4, CON-7
…-three residue Belongs with 5a25d28 (its test file was missed in the split): BRIDGE_DEPENDENT_NODES parametrized assertions (manifest + reserved_dists mirror carry g1_right) and the expert_t0 g1_right validation test whose EMBODIMENT_MISMATCH set is EXACTLY the G1-F-pinned trio. Requirement IDs: TC-5 (draft), VAL-2
…(G1-C2) Generated files (env_hash.json, monolithic treatment table, inventory) regenerated after conflicting with G1-F1. Semantic merge fix: G1-C2 pinned budget-guard as still refusing g1_right, but G1-F1 added its g1_right limits and FK, so the still-pinned set is now ik-trajectory and grasp-planner-topdown (G1-F2 scope). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The g1_right frozen-set changes (assets/g1, tools/env_hash.py) live on
feat/g1-support, not yet on protected main — the trusted origin/main
baseline refuses the gate tooling itself ('diverges from 24c35a0').
local is the documented dev override (rollout.py HAR-2); the M0-5 so101
case predates the divergence so it keeps the trusted default.
Requirement IDs: HAR-2, SPEC 090 M0-6
Three structural root-cause fixes from the first M0-6 gate (pass1=0.0, 40 collision / 6 wall_clamp): - home: arm up, wrist pitched near-palm-down, hand off the shelf — q [-1.2,-0.1,0,0.3,0,1.5,0,open,open], TCP [0.4356,-0.0892,1.0128], hand AABB z >= 0.973 (3.3 cm above the tallest box top 0.94); the a7dd234 ready pose deterministically overlapped near-column boxes at t~0.06 s. limits.toml fallback_qpos kept == home_qpos; workspace z_max 1.15 -> 1.30 covers the measured home->rise reorientation arc (peak 1.2534, free space above the shelf). - slot lattice: cross-body [0.24,+0.16] removed (lane2 transfer limitation), board y widened 0.44 -> 0.56; replacement [0.28,-0.24] (outer-loop suggestion) is NOT plannable — probe verdict below. - sim_warmup renders one frame per camera on the bridge's channels (overhead rgb+depth+seg, wrist rgb): the first render pays a one-time shader/kernel compile (measured 10.6 s/frame — the wall_clamp root cause); ADR notes render compilation must be prepaid too. New tests/unit/test_g1_scene_plan.py (SCN-4, BG-3): home clearance vs every slot's tallest box (>= 3 cm, worst-case jitter) PASSES; all-slot plannability (5 slots x 5 meds through the real StagedPlan + guard interpolation) — the [0.28,-0.24] slot xfails pending lane2: 16 slot candidates (transfer IK fails at y<=-0.23, pregrasp IK at x>=0.30, pairwise separation seals -0.22..-0.18), 23 trajectory/workspace combos and 5 pregrasp variants all fail; the transfer-stage DLS stall needs the ik-trajectory solver (measured route profile or smarter continuation). Warm render evidence: overhead 0.59 s / wrist 0.03 s warm. Requirement IDs: SCN-4, BG-3, CON-5, CON-8
…ot (G1-H, G1-H-r1) Gate not yet passing: the [0.28,-0.24] transfer stalls in ik-trajectory (5 xfail in test_g1_scene_plan, handed to lane2) and the plannable-slot smoke episode dropped its box (handed to lane1). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Evidence first (run 20261019-215328-9e8aeb, seed 1 relaunch trace): the Dex1-1 fingers DO close onto the box — normalized grip plateaus at 0.726 against the box surface (contact stops them short of the 1.0 command) — and the box lifts ~0.12 m (z max 0.995), then slides out of the pinch in transit (z 0.51 -> 0.05 down the slot column, xy pinned at (0.28,-0.08)). Genesis cannot retain the pinch under motion — the exact SO-101 precedent (ADR-27/ADR-18). Not a gain/command deficiency, so the fix is the latch, not the gains. physics.toml [embodiment.g1_right]: kinematic_carry_latch = true, close 0.65 (below the measured 0.726 on-box plateau), release 0.20 (planner pregrasp_cmd = 0), max_distance 0.15 (target center ~0.03 m from TCP at contact; slot neighbours >= 0.08 m, nearest-wins cannot cross-latch). dora_genesis.py: the latch path was already generic through ee_link/ee_frame_offset_xyz/the normalized grip — only the naming and error strings were SO-101-specific. so101_latch/so101_hand_link/ so101_tcp_offset -> carry_latch_obj/carry_hand_link/carry_tcp_offset; error and log lines now name cfg.embodiment. ADR-g1-embodiment gains the G1-H-r2 section: the latch is a simulation aid only and NOT evidence of real-robot grasp capability (real retention is a force-controlled pinch measured in the hardware phase). Derivatives regenerated (env_hash, monolith treatment table, docs inventory). Requirement IDs: SCN-4, CON-5, CON-8
The G1-H-r1 same-side slot [0.28, -0.24] stalled the staged plan the same way the removed cross-body column did; three measured planner fixes lift it, and test_g1_scene_plan.py goes to 0 xfail (26 passed). - tray roll retries both tool-z parities: the free-yaw transfer lands the wrist ~0.94 rad of yaw off the place frame and the direct in-place roll misses its basin while the 180-degree spin converges and the lower stage follows (symmetric Dex1 fingers make them the same physical release) - the lower stage retries both parities too: the tallest box (cetirizine, place_z 1.075) descends only in the flipped parity; the cold alternatives sit 3-4 rad away on other branches (unusable) - rise solves over the FIXED seed ladder (both parities) and takes the FIRST solution whose executor-interpolated arc keeps the TCP inside the workspace AABB: the home-seed solution arcs to z=1.341 against the 1.30 ceiling while another seed's stays flat at 1.144 — the Cartesian-ladder alternative stalled full-rows at substep 1 (home sits 1.98 rad off the neutral frame) - tests/unit/test_g1_scene_plan.py: the five lane1 xfail markers are DELETED (assertions unchanged); the whole 5x5 lattice plans guard-clean - ADR-g1-embodiment: G1-F3 resolution section records the three fixes and the measured numbers; the cross-body limitation note stays for the removed column Requirement IDs: SCN-4, BG-3, TC-5
ADR-g1-embodiment conflicted on sibling sections (G1-F3 resolution and G1-H-r2 latch); both kept. Generated files regenerated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s it (G1-F4) A grasp plan that runs to completion without a verdict made the node emit plan_done unconditionally (45ecf36 / moxin-org#161 HAR-3). expert_t0's ik-trajectory declares only joint_cmd,gripper_cmd,turn_done, so under lockstep aisle.turns raised ProtocolError 'unknown or reserved output ik-trajectory/plan_done', the node died, and the episode sat in wall clamp (lane1 smoke 20261019-224058-539bc6). franka/so101 verdicts normally land first — the same latent defect ships on main for every failure path; the G1 failure set exposed it. - plan_done_declared(turn_outputs): the declared set is AISLE_TURN_OUTPUTS — the same variable the typed host pins from the graph config in BOTH modes and the dora-genesis bridge already guards against (ADR-30 precedent) - the :1651 send is gated on it; graphs that wire plan_done (the 15 eval/shield/expert graphs) keep the HAR-3 retry arming byte-identical; expert_t0 and any undeclared graph get silence instead of a crash - tests/unit/test_ik_plan_done.py (4): env-list parsing; completed grasp plan under the expert_t0 declaration sends joint/gripper only; under a declaring declaration still emits {'ok': true}; so101 recorded radial-front grasp respects the declaration. Harness is an IDEAL ACTUATOR node (joint_state echoes the last joint_cmd, generated lazily until the plan completes) driving the REAL main() loop; the module docstring records why unit tests suffice instead of a sim/graph smoke (the crash mechanism is the send decision under the exact env the host exports, not transport or physics) - graphs/expert_t0.yaml and turn plans untouched (frozen) Requirement IDs: HAR-3, ADR-30
Outer-loop moxin-org#9 root cause: two lanes' parameters were individually sane and jointly contradictory — gripper_grasp_cmd 0.55 closed BELOW carry_latch_close 0.65, so the latch could never fire (the r3 smoke dropped the box). Per-med contact plateaus, derived from the finger qpos at two-face contact (f = grasp-axis width/2, g = (0.0245 - f)/0.0445): omeprazole 0.045 | amoxicillin 0.101 | ibuprofen/metformin 0.157 | cetirizine 0.213. The table lives in physics.toml and the ADR. - gripper_grasp_cmd 0.90: a SQUEEZE above every plateau (PD keeps holding force after contact; 0.55 was a dead stop below the latch threshold). - carry_latch_close 0.03: under the LOWEST plateau (0.045) — the latch engages mid-close BEFORE two-face contact, mirroring so101's design (0.68 under the measured 0.75 onset). Empty-hand close cannot mis-latch: the planner opens fully (pregrasp_cmd 0) before every approach, and max_distance 0.15 bounds the capture to the pinched volume. - carry_latch_release 0.01: wide hysteresis, no chatter. tests/unit/test_g1_grasp_consistency.py (SCN-4, ADR-18): per med, plateau > latch_close and grasp_cmd > plateau; hysteresis band; the so101 chain pinned (close under the 0.75 onset, cmd past close, release under close); the ADR table cross-checked against the derivation; plateaus monotone in width. tests/sim/test_g1_carry_latch.py: widest and narrowest meds through the bridge's REAL path (control_dofs_position full-length targets, held DOFs at held home, the exact grip normalization feeding a real CarryLatch every tick, set_pos/set_quat applied as the bridge applies them) — the latch fires mid-close and the box rides a 0.1 m commanded lift (box rise >= 0.9 x actual TCP rise; the arm's gravity steady-state sag is the known G1-F deferral). Derivatives regenerated (env_hash, docs inventory). Requirement IDs: SCN-4, ADR-18, CON-5
Outer-loop review of G1-H-r4: with carry_latch_close at 0.03 the latch fires as the fingers start closing, so carry_latch_max_distance_m is the only check that the TCP is at the box. 0.15 m would credit grasps several centimeters off and flatter the M0-6 gate. 0.05 m keeps true grasps (TCP ~0.03 m from the box center; test_g1_carry_latch still latches both the widest and narrowest med) and is pinned by a consistency test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nd for g1_right (G1-F5)
Two lockstep-stalling defects from the 20261019-234952 smoke:
ep1 — the 13-stage plan did not finish inside the tier's 60s episode:
- measured with the real StageStreamer cadence under a sagging actuator
(qpos = cmd - 0.08 rad, the documented MJCF gravity droop): worst
32.4s across the whole slot lattice at the 0.5 velocity scale,
28.3s at 0.8, 27.0s at 1.0; transfer alone 10.7s -> 5.6s
- free-space stages (retract/transfer) now run at 0.8 rad/s — 40% of
the guard's 2.0 rad/s qdot envelope (profile keys, authorized); the
episode budget and guard limits are untouched
- test_every_slot_execution_fits_the_episode_budget: systematic —
every slot x med's modeled duration fits tier_budgets('T0') minus a
20s stabilization margin (read from the rollout tiers, not hardcoded)
ep0 — first-launch planning burned 9 minutes of lockstep wall with no
crash and no verdict:
- root cause: ONE unreachable cold ik_solve walks the full 32-seed x
2-rotation ladder (measured 37.5s wall) and the staged chain
multiplies it across stages and parity retries, unbounded
- StagedPlan now runs under the profile's ik_plan_budget_s (30.0s;
legitimate plans measure 4.7-5.8s — ~5x headroom): every
stage-boundary failure names the budget when the deadline has
passed; the unreachable-pose test pins a loud plan.error well inside
90s wall (measured ~40s)
ADR-g1-embodiment gains the G1-F5 section recording both budgets and
the measurements. env_hash/table/inventory regenerated.
Requirement IDs: SCN-4, BG-3
env_hash.json conflicted with the latch fix (both touch physics.toml); regenerated. Verified merged: 144 unit + 14 sim passed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…gle open (G1-F6) The lane1 smoke 20261020-011256 saw ik-trajectory emit a TRIANGLE gripper_cmd (0.00 -> 0.90 over 23 ticks, straight back down 23 ticks): the box was lifted to retract height, never carried across, and the latch released it mid-air back onto its own slot. Reproduction with the F4 IdealActuatorNode driving the REAL main() loop proves the single-plan stage table is CORRECT — the close ramp holds flat through lift/retract/transfer (1014 ticks) and only descends at the tray during release. The triangle's down-edge is a RESTART: a plan that finishes via stage bails (tracking failure in sim) leaves the fingers closed; the next grasp_pose is accepted, and its free-space stages (rise/staging/pregrasp/preclose) command gripper 0.0 — on g1 that IS the profile's gripper_pregrasp_cmd — opening the still-latched box in mid-air. - _held_replan_stages(): when a replan starts with the gripper closed (held_grip >= half of grasp_cmd), every stage before close is rewritten to hold grasp_cmd, so the latch survives the re-approach until the new plan's own close/release semantics take over; held=False is the identity - main(): tracks held_grip across plans (set when a streamer finishes, cleared on reset_done episode boundaries and after each replan) - tests/unit/test_g1_gripper_hold.py (5): the hold-window invariant (monotone rise, never a dip below carry_latch_close until the single release descent) on g1_right/so101/franka; a duplicate grasp_pose mid-execution does not restart (one up-ramp, one descent); the held-replan rewrite pinned unit-level (pre-close stages hold grasp_cmd, close-onward unchanged, identity when not held). Docstrings cite HAR-3 and ADR-18. Requirement IDs: HAR-3, ADR-18, SCN-4, BG-3
Evidence: every bridge process's FIRST episode lost its first render window to a one-time shader/kernel compile (~10.9 s/frame, first-window tick ~2.8 s — the ep0 wall_clamp of the r3/r5 smokes). tools/ sim_warmup.py pays this in ITS OWN process and cannot cover the bridge: the compile cache is per-process. In a 50-seed gate this costs one episode + one relaunch per launch. dora_genesis.py warmup_render(): after the scene build and AFTER topic_rates is final (lockstep-filtered to the graph's declared outputs), BEFORE the first turn / first reset_done, render ONE throwaway frame per ENABLED camera channel — overhead rgb+depth+seg in one pass, wrist rgb — mirroring render_frames' channel logic exactly. The result is DISCARDED: nothing published, sim time not advanced, no step(), no RNG/state touched (CON-5); the only side effect is the renderer's compile cache. Per-channel seconds go to stderr and to a new OPTIONAL bridge_info field render_warmup_s (existing fields' semantics untouched). Applies to every embodiment — franka/so101 pay the same first-frame compile and benefit identically; no g1-only gate. tests/sim/test_g1_render_warmup.py: first real window after warmup costs the same order as the second (< 1.5 s vs the 10.9 s compile cliff; threshold basis in-file), and same-seed observations (joint_state bytes, oracle_state bytes, first rgb frame bytes) are BIT-IDENTICAL with and without the warmup — the warmup frames are discarded and change nothing (CON-5). test_bridge_info_shape pins the new optional field (None outside a run). Derivatives regenerated (env_hash, monolith treatment table, docs inventory). Requirement IDs: CON-5, BRG-6
Treatment table conflicted with G1-F6; regenerated. Outer-loop fix folded in: tests/sim/test_g1_render_warmup.py:33 exceeded the 100-column limit (ruff E501), which the lane's ACK reported as passing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ause (G1-C4) G1-F6 traced the gripper-reopen loop to TRACKING failure: the 7 wire arm joints rode Genesis's kp=100 URDF default (nexus_physics.toml) — the profile carried gripper and held-DOF gains but none for the arm — giving the ~0.075 m gravity droop per 0.1 m commanded lift. SO-101's light arm made the default adequate; the G1's 3.55 kg arm + 0.73 kg Dex1 did not. Sizing from MEASURED static torques (heaviest box pinned to the hand, home/shelf/tray 2 s holds; per-joint err x kp): shoulder pitch ~11.8 N·m, roll ~21.8, yaw ~15.0, elbow ~5.5, wrist roll ~4.2, pitch ~0.7, yaw ~0.9. A first geometric estimate (kp 1500/1500/1000/800/200/100/100) measured OVER budget at shelf/tray (shoulder_roll 0.0145 rad, shoulder_yaw 0.0149, wrist_roll 0.0208) and was replaced by the measured sizing — kp = tau/0.005 rad (2x under the 0.01 budget): [2500, 4500, 3000, 1200, 900, 250, 250]; kv = 2*sqrt(kp*J_eff) at zeta ~1.0-1.5: [50, 70, 55, 35, 12, 8, 8] — no overshoot ring at dt=0.01 across the 200-step holds. pharmacy.py applies them to EXACTLY the 7 wire arm DOFs, guarded on arm_kp presence — profiles without the keys (franka/so101) are bit-identical. tests/sim/test_g1_arm_gains.py: the three-configuration static-accuracy holds (per-joint < 0.01 rad, TCP < 1 cm) plus a gain-introspection check (arm gains on the wire DOFs, gripper gains untouched). Derivatives regenerated (env_hash, monolith table, docs inventory); ADR-g1-embodiment gains the G1-C4 section. Requirement IDs: SCN-4, CON-5
…aim entries (G1-I-a) Gate-independent closeout halves (hash/registry/benchmark regeneration stays in G1-I-b after the gate): 1. planning test speed: the 5m57s unit pair now runs 20s — the (slot, med) plans share one solve per session (_PLAN_CACHE) and the whole 25-combination lattice + duration model moved VERBATIM to tests/sim/test_g1_scene_plan.py (25 real IK plans at ~5.5s each cannot fit the unit marker's <1s definition); the wall-budget test moved to tests/sim/test_g1_plan_wall_budget.py for the same reason (its assertion waits out the real ~40s budget). Nothing weakened: 28 sim-side tests pass (215.58s), assertions unchanged; the unit file keeps the planning-free home-clearance test. 2. hardware dry-run/report top-level JSON carries embodiment and dof (outer-loop note from G1-G): dry-run builds them into the report; report stamps them AFTER report_hash so every other field — and the so101 default — stays byte-identical. 3. docs: README phase-status G1 row (branch-only, ADR PROPOSED, gate unrun, hardware_pending); getting-started 3d section (g1_runtime install, sim_warmup --embodiment g1_right, rollout --embodiment g1_right); claim-evidence gains g1-right-embodiment-simulation (unrun) and g1-right-hardware (hardware_pending) — catalog regenerated, --check ok, 20 claims. 4. PR draft (not in repo): ~/.octos/outer/g1/pr-draft.md — spec-change / env-change / feature split over the 151-file diff vs main (merge-base 24c35a0), plus the fixes that also apply to main (undeclared plan_done send, held-replan gripper open) and the frozen registry / benchmark v1 drift note for G1-I-b. Requirement IDs: CON-12, HWP-20, CLM-1
Planning-grid tests moved to tests/sim (each solves real IK, ~5.5 s), unit side now 20 s; dry-run embodiment/dof fields; README, getting-started and claim-evidence entries; no assertion weakened. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The r6 smoke placed the box correctly on the tray, then the arm's return swept it off (outer-loop trace read: NOT a gripper triangle — the home swing knocked the seated box off the tray). New systematic test (tests/sim/test_g1_scene_plan.py, three-zone semantics): for every slot x med, every post-release waypoint and the executor's interpolated stream must (a) never deepen the inherent ringing overlap at the release hover by > 5 mm (the open fingers physically ring the box), (b) never re-overlap once the hand's xy projection leaves the box, and (c) never fall back under 2 cm after first reaching it. Was red: -0.0514 m at the clear stage, +0.0062 m mid-home-swing. Fixes, each probe-falsified-alternatives-first: - clear stage: was q_transfer (TCP z 1.00, BELOW the tallest box top 1.09) -> lateral retreat ladder (+y toward the arm, z+0.04; the first solvable of 4 candidates), replacing the geometrically falsified pure-vertical (no IK above z~1.12 at the tray column) and the old below-top hop. - home stage: the raw retreat->home joint hop bowed to -y past the box column; chord waypoints stall (ik_continuation: no solutions on this span even with live parity), longer retreats don't help, z>=1.18 is unreachable. Winner: one IK-SOLVED high central waypoint TCP [0.30, -0.06, 1.16] (rotation = live retreat orientation, HOME seed) splitting the hop into two short ones: measured min gap +0.024 m on retreat->mid (cetirizine, worst med) and +0.18 m on mid->home. Fallback to the rise-top route if ik_solve ever returns None. so101/franka keep their gate-proven single-hop home, bit-identical. Withdrawal lattice: 25/25 pass (worst tail +0.024 m). Derivatives regenerated (env_hash, monolith experts/treatment table, inventory). Requirement IDs: SCN-4, BG-3, CON-5
…G1-F7) The f7 smoke (20261020-053259-20f22a) proved the PLACEMENT fix works — all three goal boxes seated correctly on the tray (< 3 deg tilt) and the withdrawal no longer sweeps them off at the clear stage. The two remaining dropped episodes traced to the retreat itself: the live arm tracks ~0.027 m BELOW the commanded TCP z at the tray column (G1-F2's measured DLS plateau), so the dz +0.04 retreat planned +0.024 m of clearance but physically overlapped the seated box (-0.056 m measured at ep2's row 508, box still seated at z 1.035 while the hand swept through its column for ~70 ticks). dz +0.08 absorbs the plateau with margin; the retreat ladder keeps +0.04 as fallback rungs. Withdrawal lattice re-run: 25/25 pass.
… cuts (G1-F8) Planning runs SYNCHRONOUSLY inside a lockstep turn, so a >10s plan freezes the dataflow at every episode start (outer smoke outer-smoke-f7b: turn watchdog expired at 1:8 while 'plan ready' landed 3s later; the lane1 F7 retreat ladder pushed the lattice worst to 15.7s). The F5 ik_plan_budget_s=30 contradicted the 10s watchdog — both budgets were individually sane, combined they were not. Work cuts, all measured at the real consumption point: - _dls tolerance EARLY EXIT at 0.5x tolerance (a just-inside-tolerance stop left the TCP 2.5cm off target and ate the F7 2cm withdrawal margin — measured tail_min 0.0004 vs the 0.02 gate; half-tolerance keeps nearly all saved iterations and restores the geometry). Enabled for g1_right and so101 (so101 full-budget plans measured 12-15s -> 0.15s); franka stays bit-for-bit. - ik_max_solver_iters 200 -> 80 and ik_max_starts 32 -> 16 (sweep: whole lattice still plans; the starts key is now actually READ by _g1_seeds — the range was hardcoded 31 while the docstring claimed the key) - batched FK path (UrdfChain.forward_batch + _axis_rotation_batch): one chain walk for the 7-perturbation Jacobian and the 4-way backtracking line search instead of serial calls (~60k forward calls per plan); equivalent to the serial path within 1.6e-11 over 80 iterations - the 5.5s budget (0.55 x ORDINARY_TURN_WATCHDOG_S, now a named constant in turn_barrier) is threaded through EVERY solver call — seed ladder, per-DLS-iteration, line search, rolls, the rise arc sweep — so a stalled plan fails LOUD inside the budget instead of burning through the watchdog (verified under load: unreachable pose fails at 5.66s with the budget named) Tests (sim, real solving): test_plan_budget_respects_the_turn_watchdog (budget <= 0.6 x watchdog, read from turn_barrier, not hardcoded) and test_planning_wall_clock_fits_the_watchdog_cap (g1 whole-lattice worst wall + so101 recorded radial + franka probe, all under the same cap); the wall-budget test accepts both loud failure modes (budget expiry or ladder exhaustion — the 16-start ladder now exhausts inside the budget). Full lattice: zero planning failures, worst 5.53s vs the 6.0s cap. ADR-g1-embodiment gains the G1-F8 section. Requirement IDs: ADR-30, SCN-4, BG-3
G1-F8 enabled the DLS early exit for so101 as well, changing the depth of so101 IK convergence on an embodiment with a measured M0 record. Narrow it to g1_right so so101 and franka stay bit-for-bit. The so101 leg of test_planning_wall_clock_fits_the_watchdog_cap becomes a strict xfail that records the main-branch finding (so101 full-budget plans take 12-15 s, past the 10 s turn watchdog). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…(G1-C5)
Two bridge-side findings from the outer-loop pinned smoke
(outer-smoke-g1, dora 48e96b5):
1. The C3 in-process render warmup ran AFTER node = Node(). The 11.2 s
first-render compile blocked the event loop, >1100 10 ms tick events
piled into the zenoh channel, and the bridge DROPPED the first reset
('event channel full') — episode 0 sat at 'holding at sim step 0
until the first reset' until the wall budget died. Fix: hoist the
warmup to BEFORE Node() — no subscription exists yet, nothing can
pile up. The channel set derives from env + cfg alone
(rung_topic_rates + the lockstep AISLE_TURN_OUTPUTS filter);
nothing between the scene build and the warmup needs the node. The
result stays discarded (CON-5 zero state impact — the C3
bit-identity sim test still pins it). New
tests/unit/test_g1_warmup_order.py pins the source-level ordering
(warmup call precedes Node(); exactly one call; channel derivation
pre-Node).
2. The 9 cm place offset hypothesis (latch captures an un-centered box
at the 0.03 grip crossing; the carry drags it; the F3 tool-z flip
doubles it) is REFUTED by forensics on the pinned relaunch-1 traces
(grip/joint/oracle joined on sim_time): capture offset 0.002 m,
release carried offset 0.003 m (drift 0.001 m over the transit), and
the RELEASED box sits 0.022 m from the tray center — essentially on
it. The 9 cm error appears only AFTER the release (final rest
(0.221, -0.325, 0.035), on the floor): the box was placed correctly
and then swept off during the post-release withdrawal — the G1-F7
failure family; that run predates 0d38613's dz +0.08
tracking-plateau margin. No latch change made (millimetre offsets,
nothing to center); the latch stays a simulation aid only, NOT
real-robot grasp evidence.
Derivatives regenerated (env_hash, monolith table, inventory).
Requirement IDs: CON-5, BRG-6, SCN-4
…-F9) The smoke run showed the arm sweeping non-target boxes and, after a correct place, the home swing knocking the box off the tray. - ik_trajectory: g1 home chain becomes retreat -> tuck -> step -> home (lane2 WIP), then each home hop goes through a containment check. The check simulates the executor's 0.01 rad joint march. A hop whose TCP rises above workspace_max.z - 2 cm (the tuck -> step hop peaked at ~1.40 m against a 1.30 m ceiling) is split at an IK waypoint on the Cartesian midpoint. Split sub-hops must also stay inside their endpoints' TCP box + 3 cm, so an inserted waypoint cannot introduce a new swing back over the placed box. Depth <= 3, deadline-bound. - physics.toml: trajectory_staging_z 1.00 -> 1.02. - tests/sim/test_g1_scene_plan: arm-link vertex clearance against non-target boxes; three-zone post-release withdrawal >= 2 cm. - Regenerated monolith tables and env_hash. Gates: ruff format/check clean; tests/sim/test_g1_scene_plan.py + test_g1_plan_wall_budget.py 61 passed, 1 xfailed (so101 main-branch finding); targeted units 137 passed; env_hash --check 0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…1-F9) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> # Conflicts: # analysis/monolithic-control/treatment-table-v1.json
…(G1-F10) Outer smoke f5ceb2d was 1/3; ep0 and ep2 failed as dropped. Oracle traces plus an in-process Genesis replay of the ep0 command stream showed the cause: - The Dex1 palm (wrist_yaw + Dex1 base, one merged collision link) sits only 0.027 m above the G1 TCP. The shared 0.035 grasp depth seated the palm about 4 mm into the box top at grasp. - The kinematic latch carried that overlap to the tray. - After release, the lateral clear dragged the box off the tray (right_wrist_yaw_link in contact from release on). A vertical lift-off is kinematically unavailable at the tray column: shoulder roll and wrist yaw sit at their limits there, and a tilt-locked lift does not converge. - physics.toml g1_right: grip_engagement_m = 0.015 (about 0.012 m palm clearance; cetirizine's TCP-to-center 0.040 m stays inside the 0.05 m latch radius). Tray lowered 0.95 -> 0.85 so every release TCP (0.985-0.995 m) is below the 1.00 m transfer hover and the lower stage is a true descent. - grasp_topdown: the grasp depth is profile-driven (default unchanged). - ik_trajectory: the home-adjacent step waypoint is solved from the home, tuck and measured good-branch seeds. It keeps the branch whose hop into home sags least. After the tray change, omeprazole landed on a shoulder-yaw-flipped branch that swept the (0.28, 0.08) column (-50 mm). - tests/sim: palm stays >= 5 mm above the placed box top through the whole post-release stream (red at 0.035: 15/25). Gates: ruff clean. G1 sim set (scene_plan, wall_budget, carry_latch, ik_genesis, bridge): 93 passed, 1 xpassed (the non-strict so101 watchdog xfail at its boundary). Unit targets: 103 + 19 + 108 passed. env_hash --check 0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… a slot (G1-F10b) Outer smoke 0acb406 was 2/3. Both drops were fixed; ep2 failed as a collision. With the shallow grasp the carried box hangs 2 cm lower, and the straight shelf->tray chord carried cetirizine over the (0.24, -0.16) column (bottom 0.924 vs ibuprofen top 0.92), pushing ibuprofen more than 3 cm. Raising the transfer is not available: trajectory_transfer_z 1.04 fails IK on the (0.24, 0.0) and (0.28, 0.08) columns. - ik_trajectory (g1_right): when the straight chord passes within trajectory_transfer_chord_clear_m (0.08) of another slot centre, the transfer goes through a front waypoint (trajectory_transfer_front_x = 0.13, at the slot's own y) and then to the tray hover. Measured chords: (0.28, -0.08) 0.004, (0.28, 0.08) 0.011, (0.24, 0.0) 0.048, (0.24, -0.16) 0.075 -> detour. (0.28, -0.24) 0.089 -> direct, because its -x back-out grazed the (0.24, -0.16) column with the fingers. - tests/sim: new test that the carried box (axis-aligned; the latch keeps its world yaw) keeps >= 1 cm from every non-target box from lift to lower. Red without the route (20/25), green with it. - tests/sim withdrawal: the tail zone is entered at 2.5 cm instead of 2 cm (5 mm hysteresis), because executor wobble at the crossing measured 0.1 mm while the hand was still retreating. The 2 cm floor is unchanged, and the corridor rule (never re-overlap) covers 2-2.5 cm. Gates: ruff clean. G1 sim set: 118 passed, 1 xpassed (non-strict so101 watchdog boundary). Unit targets: 230 passed. env_hash --check 0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rry (G1-F10/F10b) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e planning (G1-F11) The 50-seed gate on e6363bb scored pass1 0.76 (bar 0.80). All 12 failures targeted slot (0.28, 0.08). A Genesis hold probe showed right_shoulder_roll_link touching torso_link from shoulder roll ~0.0 rad: the plan asked +0.198, the arm stopped at +0.039, and the advance and close stages bailed after timing out. - physics.toml g1_right: - ik_shoulder_roll_max_rad = -0.05 (planning-only bound; the guard's env/limits.toml envelope is unchanged). - Slots re-latticed inside the measured reachable band (x 0.24-0.28, y -0.24..+0.02) at a 0.065 m pitch (operator ruling 2026-10-22). - trajectory_transfer_chord_clear_m 0.07. - executor_max_joint_vel_rad_s 1.5 (75% of the 2.0 rad/s guard envelope; operator ruling 2026-10-23 to speed up rather than raise the 150 s wall budget). - ik_trajectory: - The front-routed transfer backs out at staging height. - The home step waypoint is chosen by a contained chain that stays under the ceiling and off the shelf slots. Warm single-DLS rungs come first; containment splits low sweeps over slots, with slerped midpoint orientation. - The _q_mid gate tries a warm DLS before the cold ladder. - The clear's first rung backs off 2 cm in -x. - Worst plan 5.87 s -> 3.77 s, median 4.06 s -> 2.82 s. - executor_max_vel() reads env, then the profile, then 1.0. - tests: - New shoulder-roll bound test. - The arm-link sweep now covers all 25 slot x med cases and requires 8 mm in advance/close/lift/transfer, 15 mm elsewhere (ADR-g1-shelf-lattice; a 10-episode smoke moved non-target boxes <= 0.4 mm). - New executor-speed unit test. - docs/decisions/ADR-g1-shelf-lattice.md. Gates: ruff clean. tests/sim/test_g1_scene_plan.py + test_g1_plan_wall_budget.py all green on the pre-speed tree (157 passed, 1 xfailed). Unit targets: 231 passed. env_hash --check 0. PENDING: the render-dependent sim tests (carry_latch, ik_genesis, bridge) errored with no display attached (pyglet screens == 0). The smoke and gate on this commit are not yet run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Smoke on 2ee2cb9 scored 8/10. Seed 5 was dropped. On the way up from home, the wrist flip jammed on the shelf's upper rear rail (wrist yaw stuck 0.71 rad), so every grasp stage bailed and the misplaced hand pushed the target off the shelf edge. Geometry check: the home -> rise flip sweeps 58-67 mm through that rail (z 1.10, x 0.41-0.42) for every slot x med. Earlier lattices usually scraped past it. - physics.toml g1_right: shelf_level_heights [0.06, 0.34] -> [0.06, 0.60]. The rail now sits at z 1.36, above the 1.30 workspace ceiling. SCN-3's staggered two-level shelf is kept: removing the level would be a spec change. - tests/sim: every shelf board joins the arm-link sweep obstacles (red on the old rail height: 25/25, -58..-67 mm). - ADR-g1-shelf-lattice section 4. docs inventory regenerated. Gates: ruff clean. G1 sim set (scene_plan, wall_budget, carry_latch, ik_genesis, bridge): 163 passed, 1 xfailed. Unit targets: 272 passed. env_hash --check 0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… faster executor (G1-F11/F11b) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Unitree G1 support, scope A: the right arm (7 revolute joints) plus the
right Dex1-1 gripper (2 prismatic fingers) on a 9-DOF wire, the same shape as
franka. The other 24 DOFs are held at standing home in sim (pelvis fixed at0.7872 m) and belong to Unitree motion control on hardware. Model:
unitree_ros@5994d4fg1_29dof_mode_15_with_dex1_1.urdf(mode_machine 15).The real-robot SDK2 path is lazy-imported and evidence is
hardware_pending.Requirement IDs touched: TC-5, SCN-2, SCN-3, SCN-4, HAR-1, HAR-2, CON-5,
CON-7, CON-8, VER-2, HWP-8/18/20 (new spec 550).
What is in here
spec-change (needs owner review, CON-14)
specs/010-topic-contract.md: TC-5 adds the G1 right-arm wire order (7 joints + 2 fingers).specs/550-g1-hardware-phase-gate.md: the G1 hardware-phase gate.env-change (frozen set, CON-7)
assets/g1/**: official URDF, meshes, PROVENANCE/LICENSE (BSD-3-Clause).src/aisle/embodiment.py:G1_RIGHT_ARM_JOINTS; the wire ∪ held partition covers 33 DOFs.env/limits.toml:[embodiment.g1_right]limits, guard envelope, workspace AABB.src/aisle/scenes/physics.toml,pharmacy.py: theg1_rightscene profile.src/aisle/kinematics.py:UrdfChainbase/tip transforms andg1_right_chain().src/aisle/nodes/budget_guard.py: theg1_rightFK branch.feature
ik_trajectory.py,grasp_topdown.py):dora_genesis.py): held DOFs, Dex1 finger mapping,render warm-up before
Node(), kinematic carry latch (sim aid only).g1_driver.py, adapters,harness hardware_phase,docs/hardware/g1/**,tools/g1_runtime.py.g1_rightplus the newg1-driver.ADR-g1-embodiment;ADR-g1-driver;ADR-g1-shelf-lattice: re-latticed slots, 8 mm grasp/carry planningmargin, upper rail raised. Operator rulings 2026-10-22/23.
Fixes that also apply to
mainplan_donesent without being declared (G1-F4). Under lockstep thiscrashed with a ProtocolError. franka/so101 dodge it only because the verdict
lands first. It now sends only when the graph declares the output.
emitted 0.0 during rise/preclose and dropped the box mid-air. The pre-close
segments now keep
grasp_cmdwhile holding.the 10 s turn watchdog. This is recorded as a strict xfail; the G1 early exit
is kept
g1_right-only so the so101 record stays bit-for-bit.Gate status (honest)
e6363bbd53aac2(= this content)3dc268c(this head)The 150 s per-episode wall budget was not changed; G1 was sped up instead
(executor 1.5 rad/s, 75% of the 2.0 rad/s guard envelope).
Known red until the final regeneration (G1-I-b, after the gate)
tests/unit/test_freeze_registry.py::test_committed_registrations_check_clean_with_withheld_seedstests/unit/test_benchmark_release.py::test_version_manifest_and_release_audit_are_current_and_honestBoth turn red by design because the frozen set changed. The freeze successor
registrations and the benchmark v1 manifest get regenerated once the gate
passes.
Test plan
uv run ruff format --check . && uv run ruff check .tools/env_hash.py --checktests/sim/test_g1_{scene_plan,plan_wall_budget,carry_latch,ik_genesis,bridge}.py: 163 passed, 1 xfailedg1_rightsmoke ond53aac2: 10/10pass1 >= 0.80on a quiet hostpytest -m unitSimulation evidence only: real G1 reach, gains (SDK2 example kp 60 / kd 1.5 vs
the sim PD gains here) and self-collision margins are
hardware_pending.🤖 Generated with Claude Code