← Design notebook

BUILT BY AUTONOMOUS SYSTEMS · WORK IN PROGRESS

Body next plan (milestone M5, stages P2-P4): after the power skid and the open frame

Written 2026-10-27 by the body planner (Fable 5.1) for the user and for the Opus implementer who will work it. Sources read, all on main 0e1b0ec: docs/body-plan.md (the plan P0/P1 were built from; its part list is stale), docs/body-frame.md, docs/body-electrical.md, docs/body-safety.md, docs/body-orders.md, cad/body_*.py, cad/build_body.py, cad/test_body.py, exports/body/{body,bom,mass,leg-loads,wiring,print-plan}.json, exports/body/fea/summary.json, exports/body/sendcutsend/, catalog/body.json, docs/decisions.yaml (D39, D40, D43-D47, D54-D56), docs/turret-next-plan.md (sections 3-5: the proposed D48-D52), exports/turret/turret.json, exports/leg/{leg,rig-results}.json, exports/leg/fea/summary.json, cad/fivebar.py frame_mounts(), sim/leg_rig.py, sim/robot_v2.py, exports/sim/v2/summary.json, the leg/stops and legrig/program branches (read only), docs/requirements.md, docs/milestones.yaml, docs/head-payload.md, docs/handoff.md, and the J501 project (~/Code/j501-agx cad/parameters.json revision 0.9-g48r, exports/simulation-checks.json; read only). SendCutSend's 5052 and 6061 material pages were read today (section 2.3). The arithmetic behind section 2 is in /mnt/storage-10tb/claude-scratch/gyropup/body-planner/ledger_levers.py (its output beside it).

Every "decide" below is decided here (D41's rule: the user does not wait on decisions); they are written up as proposed decisions D57-D64 in section 6 for the implementer to record. Task numbers in this plan are B0-B17; the old plan's T-numbers are quoted where a task continues one of them.

0. In one paragraph

P0 (the power skid) and P1 (the open frame) are a sound build package: seven SendCutSend parts that pass every published rule, a frame at SF 2.8-4.4 in the leg's real landing loads, zero clashes over 22 stances, a 13S bus whose every part is rated, and an order list where every line has a price, a link and a backup. Order it today with three small fixes (section 1.2). The one thing the plan got wrong is the mass: the body and head came out at 13.3 kg against the 10.3 kg budget and the robot at 32.3 kg against 28.5, mostly for good reasons (3/16 in cheeks because 1/8 in failed, a 1/8 in tray, a power plate and a printed battery bay the plan did not have, the turret's 1.70 kg, the legs' thicker flanges and machined adapters). That budget cannot be met by any body that holds these parts, so it is retired. The call (section 2.4): the robot is designed for 32.5 kg (16.25 kg a leg) in every strength case, held under 32.0 kg as built by a ledger that gates every commit, and expected at about 31.8 kg after the levers of section 2.3 (about -0.7 kg scheduled for P2/P3, -1.0 kg more on call). The legs must be sized for 16.25 kg: at that mass their three weakest checks (the wheel link's sideways case 1.03, the front link 1.10, the hub dowel bearing 1.12) sit under the 1.2 target, so the leg workstream's stop redesign is the moment to fix them. Then P2 puts the turret on the deck over a 6061 mount plate that takes the tip-over case from SF 1.3 to over 2, P3 closes the shell in 2 mm PETG with a fuse board and an aluminium battery tub, and P4 is the dock. The sim, the firmware and the thermal check (the old T4, T7, T9, T10) are tasks B3, B6-B8 and B9 here.

1. Review: P0 and P1 as built

1.1 Ready to order and cut today

What Evidence Cost (2026-10-27 prices)
The SendCutSend set: 2 x cheek (5052 3/16 in, 2 bends, 8 PEM SS-M5-0, 8 countersinks), floor tray (5052 1/8 in, 4 bends, 6 PEM S-M4-0, 2 x M3 taps), belly plate (6061 0.100 in, 12 PEM), deck (5052 0.100 in, 4 bends), compute pallet (6061 0.125 in, 8 countersinks), power plate (5052 0.100 in, 16 flush PEM), 2 x bus bar (C110 0.125 in, 5 x M6 taps) exports/body/sendcutsend/*.dxf and .step; cad/body_sheet.py reports no rule broken on any part (bend radius and K, half-die distance, PEM edge distances, the channel 2x rule, countersink depth, 0.125 in minimum for countersinks); every flat inside 30 x 44 in ~$383 estimate; the instant prices are read when the DXFs are uploaded (B1 records them)
The frame's strength exports/body/fea/summary.json: S1 landing SF 4.42 away from the standoff ends (1.52 at the bonded ends, a singular corner), S1F fault 2.88 (1.7), S1b one-leg 2.8, S2 twist 0.013 deg, S3 pitch 27, T1 tray 2.5, D1 hand 2.53 / 2.89 with 0.33-0.38 mm at the load, D2 turret fall 1.31 against the 1.2 abuse target; loads from the rig at 15.97 kg a leg (D56) -
The hip-frame interface cad/body_params.py frame_mounts() reads fivebar.frame_mounts() (x 0, +-83; z +-70; the plane y -82.3; pilots 11 mm) and measures the same holes from the frame's solid; test_cheek_holes_on_the_frames_inserts passes; M5 x 12 through the 4.75 mm cheek engages 7.3 mm of the 7.9 mm insert -
Clearance cad/build_body.py clash sweep: 0 clashes at 22 stances; nearest moving part 25.06 mm (the upper links to the belly plate at 280 mm); the knee pins 29 mm from the narrowed bay door -
The electrics docs/body-electrical.md: 39 wires with gauge, ends and fuse; every part rated across 39-53.3 V; the MINI 58V branch fuses; the MRBF 80 A main; the DPDT stop relay with the turret's dry loop on pole A and its 24 V on pole B (what docs/turret-next-plan.md 3.2 asked for) $1,494 bought (incl. the $290 welder and the $100 charger)
The prints 50.2 h on the P1S: posts, riser, isolators, DIN rail pieces, plate posts, bus insulators, the PETG bay halves and door, TPU skids; nothing over the bed $47 of filament
The build and test plan docs/body-frame.md 6 (eight assembly steps), docs/body-electrical.md 6 (eight bring-up steps with pass numbers) -

Everything the leg's interface asked for is taken (the six inserts, the plane, the harness exits past the cheeks' ends, the wheel harness through the cheek windows), and both of the turret's cross-workstream items (the lidar 143 mm ahead of the pan axis, the harness grommet at the right rear (-130, -60)) are in cad/body_params.py with tests.

1.2 Change before ordering

  1. The live ends of the branch leads are sockets, not pins. The wire list puts an XT60H male on the body's end of every RS-04 and VESC branch (W10-W15) and an XT30 male on the 24 V and 12 V branches (W21, W24): those pins are live whenever the pack is in (the + bus is unswitched; the switch is on the negative). The same rule the charge port already follows (hazard 11: "no live pins on the robot") applies: the body's branch leads end in XT60H-F / XT30U-F (sockets); the motor pigtails, the VESC leads and the J501's and lidar box's adapter leads carry the males. In the Pyrodrone order: 2 x XT60H-F 5-pack instead of the XT60H-M 5-packs (the females for the charge port stay); XT30U-F 8-pack for the body, the XT30U-M 8-pack for the device leads. $0 net. cad/body_wiring.py W10-W15, W21, W24 and the leg's pigtail request (section 5.1 item 5) say which end is which.
  2. Two DDR-120C-24, not one. Master Electronics flags it "no longer being manufactured" (docs/body-plan.md 1.3); the old plan's instruction to buy two stands. +$69.20 (Bravo Electro). The SD-100C-24 is the successor if both ever die.
  3. Read the instant prices before the cart goes (the ~$383 is an estimate) and record them in exports/body/bom.json made_to_order with the date, so the Build page stops saying "estimate".

Nothing else changes before ordering. In particular the cheeks stay 3/16 in (section 2.3), the deck is cut as drawn (the turret mount plate of P2 bolts under it through its existing riser holes), and the PETG battery bay is accepted for the bench and the tether (procedure: charging attended, on a non-flammable surface) until P3's aluminium tub.

1.3 Risks carried into P2-P4

Risk Where it bites Measure
Mass: 32.3 kg at P3 against the plan's 28.5; every kilogram raises the legs' loads about in proportion (section 2.2) the legs' weakest checks, the jump height, the balance gains tuned in sim at 26.2 kg section 2: the design mass 32.5 kg, the ledger gate, the levers; T4 (B3) at the real mass
The 24 V rail in a jump near empty. The 13S2P P30B pack is about 112 mOhm (13 x 15 mOhm / 2 + leads): 9 V of sag at the 80 A jump pulse; at a 42 V resting pack the bus dips to 33 V, under the DDR-120C-24's 33.6 V minimum: the J501 could brown out mid-jump the Jetson, the turret firmware (B8): no jump below 44 V resting (35 V at 80 A), the jump scaled with bus voltage above that; the Daly K's discharge over-current delay set to 3 s if its app exposes it (else its default checked in step 9) so an 80 A pulse never trips it; P0 step 9 (B12) puts an 80 A pulse on the pack at 44 V with the 24 V output on a scope
The turret's tip-over case at SF 1.31 (D2), passed only as an abuse case the deck round the riser the turret mount plate (B4, D59): target 2.0
Twelve M5 inserts in printed PA6-CF carry the body: 0.5-0.7 kN each in the landing (bottom row, tension) the hip frames' flanges the leg plan's bench test B4, the insert coupon (docs/leg-prototype-plan.md 5: pull-out >= 2.4 kN), covers it at SF >= 3.4; the cheeks also bear on the flange ends
The pack's prospective short-circuit current against the fuses' interrupt ratings (open item in docs/body-electrical.md 7) the MINI 58V (1 kA) and MRBF (2 kA) by arithmetic it is about 430-480 A (53.3 V over ~112 mOhm plus leads), under 1 kA, and the BMS trips in microseconds first; B12 confirms the BMS's short-circuit setting from its card. Closed unless the BMS card says otherwise
The low-side main switch: with it OFF the + bus is still live and the - bus and the frame (bonded to the - bus, W33) float toward + the bench, the meter's ground, any - bus conductor touching the frame it is how every e-board is wired and it fails safe (a + chafe blows a fuse once the switch closes; a - chafe is harmless); rule: every - bus conductor insulated from the frame, meter and scope grounds on the - bus only with the switch ON; bring-up step 1 gains "switch off: - bus to pack - reads open"
The DDR-120C-24's end of life; every Pololu 60 V buck backordered the 24 V and 12 V rails two DDR-120C now; the Pololu D42V110F12 is an opportunistic lever (section 2.3) when it returns
The J501's second CAN port (the datasheet says one, the wiki two) the turret's bus ip link in P0 (B12); the turret stays on its USB-CAN adapter if only can0 comes up
The body's CAD reads the J501's parameters from an uncommitted working tree (~/Code/j501-agx, revision 0.9-g48r on branch rev0.8-thermal); the cooler centre is +-3 mm from a photo the pallet, the fan intake field, the riser's clearance B10 vendors a dated snapshot with a hash test and the fan centre is measured from the PCB edges when the carrier is on the pallet
Ground clearance at the leg's stops is 25 mm (the battery tub's skids at z -160.5; hip height = leg length + 84.5 mm), not the plan's 45 kerbs at a crouch, the dock's height the robot drives tall (the sim's curb scenarios); the dock is 30 mm, not 40 (B9); the TPU skids are the sit-down bumper
Regen on a full pack reaching 57 V the RS-04's 60 V fault, the MRBF's 58 V the P1 scope test (brake from 2 m/s, a full squat) stays the gate before any untethered run
The thermal check: the J501 project's own CFD for Rev 0.9-g48r is not settled (exports/simulation-checks.json: fins not calibrated, plate margin unknown) the shell's vents (T7) B7 uses a hand model and the thermometer; the CFD only after the J501's case settles, one heavy solver at a time on this machine
A 32 kg robot on a tether the first stand-up a gantry or ceiling anchor rated >= 100 kg with 0.3 m of slack, a rope not a line, two people

2. The mass problem, first

2.1 Where the 32.3 kg are (exports/body/mass.json, stage P3)

Group kg What is in it
Legs 18.96 2 x (exports/leg/leg.json 9.28 + 0.2 announced for the 13 mm flanges and the stops); of which motors 5.68, wheels 4.40, printed 5.22
Frame 4.31 cheeks 0.98, floor tray 0.75, deck 0.65, belly 0.24, pallet 0.23, posts 0.25, riser 0.17, power plate and posts 0.20, bus bars and insulators 0.16, PETG bay and skids 0.36, hardware 0.30 (est)
Compute 1.51 carrier 0.23, module 0.28, copper spreader 0.20, G48R cooler 0.45 (est), lidar box 0.12 and 3 m cable coil 0.23 (est)
Power 2.02 DDR-120C 0.45, 2 x VESC 0.46, switch 0.11, shunt 0.09, DDR-60L-12 0.22, MRBF 0.09, fuses and holders 0.11, MCU board 0.08, VN-100 0.02, harness 0.40 (est)
Head 2.53 VLP-16 0.83, the turret at its 1.70 kg budget (turret.json: 1.639 with 30 balls)
Battery 1.54 13S2P P30B with the Daly K BMS
Shell 1.40 panels 0.80 (est, 3 mm PETG), 2 x D435 0.15, eyes 0.02, 2 x SSC1 pod 0.31, belly skids and door 0.12
Robot 32.27 body and head 13.31 against the plan's 10.3; P1 (no turret, no shell) 29.17

Against the plan's section 1.8 estimate (its table says 29.5 kg; its rows add to 30.2): the cheeks +0.5 (3/16 in on standoffs against a 1/8 in U), the tray +0.15, the deck +0.2, the power plate with its posts and bus bars +0.36 (the plan hung those parts on the belly plate), the printed battery bay +0.36 (the plan's cassette was inside the pack's 2.0 kg, and the pack itself came in 0.46 lighter), the shell +0.2, the G48R cooler and spreader +0.18 against the plan's 34 mm cooler, the turret +0.36, the legs +0.7 (D46's machined adapters and the flanges); the converters -0.18 and the battery -0.46 went the other way. About 16.8 kg of the 32.3 are bought parts the body cannot change (motors, wheels, lidar and cable, turret, compute, battery, VESCs, converters); the rest is the legs' printed parts (5.2), the frame (4.3), the shell (1.4) and wiring and hardware (0.7).

2.2 What every kilogram costs the legs

The leg rig's loads rise about in proportion to the mass it carries: from the leg's committed 14.75 kg a leg (exports/leg/rig-results.json) to the body's 15.97 kg (exports/body/leg-loads.json), +8.3 % of mass gave +7.1 % on the drop_150 axle force, +9.4 % on jump_peak and +8.5 % on the undamped power cut; the knee pins +6 to +10 %. (The jump's push-off is torque-capped, so it does not grow; the landings and the collapse onto the stops carry the mass.) The leg's strength checks (exports/leg/fea/summary.json, 14.75 kg) already have three under their 1.2 rare-event target or on it:

Check (target) at 14.75 kg at 16.25 kg (loads x1.10) at 15.97 kg, the ledger today
wheel link R1w, the full axle spike sideways on the link alone (1.2) 1.13 1.03 1.04
front link R1 (1.2) 1.21 1.10 1.12
hub rear dowel bearing R1 (1.2) 1.23 1.12 1.14
upper link R3K, the undamped power cut on the knee (1.0) 1.27 1.15 1.17
upper link R1 (1.2) 1.44 1.31 1.33
hip frame R1 (1.2) 1.60 1.45 1.48
everything else >= 1.76 >= 1.60 >= 1.62

So the robot as the ledger stands already puts three leg checks under target, and the leg workstream's stop redesign (branch leg/stops) is the right moment to fix them for the mass the robot will really have, once, instead of twice. The gearbox torques (99-108 N m in the jumps at 15.97 kg) stay under the RS-04's 120, and the jump at the 60 N m cap drops from 268 mm (14.75 kg) to 258 mm (15.97): about 4 % per 8 % of mass.

2.3 The levers, ranked

Grams are against exports/body/mass.json; costs are today's prices from docs/body-orders.md where the part is already listed and estimates (est) elsewhere. SendCutSend's 5052-H32 comes in 0.040, 0.063, 0.080, 0.090, 0.100, 0.125, 0.187 and 0.250 in (bendable) and 6061-T6 in 0.040-0.750 in (not bent), read today: there is no 0.160 in, so the cheeks' only step down is 0.125 in, which failed the landing case.

Scheduled (B2, B4, B5, B6): about -0.7 kg

# Lever Grams Cost Stage Why it is safe
1 Shell panels in 2 mm PETG with 1.6 mm ribs instead of 3 mm (PA6-CF only at the ear-pod bosses) -240 $0 (less filament) P3 the shell carries nothing structural (docs/body-plan.md 1.1); the walls span <= 140 mm between the tray and deck lips
2 Floor tray: lightening cutouts where nothing bolts and T1's stress map is low (a Rev B part; 1/8 in stays) -150 $62 (a second tray) P3 T1 is at SF 2.5 and 0.48 mm; the cutouts stay out of the overhang past the cheeks' ends, where the 0.100 in tray was 1.55
3 Deck: cutouts outside the lidar's and the TM patterns and the intake field (Rev B) -120 $65 (a second deck) P3 D1 re-run (<= 0.5 mm at the load), D2 with the mount plate
4 The turret mount plate under the deck at the riser: 6061-T6 0.125 in, 160 x 140, pocketed (D59) +135 $25 (est; the pallet is $30) P2 the one lever that adds: it takes D2 from 1.31 to over 2 (two plates in bending; the 0.125 in 6061 alone has 1.56 x the 0.100 in 5052's section modulus and 1.4 x its yield: 2.2 x the moment capacity on its own)
5 Deck posts printed hollow (22 mm square, 4 mm walls, 100 %) instead of solid -100 $0 P2 the posts carry 290 N each at the landing's 8.65 g (the deck, lidar and turret 3.4 kg over four); the inserts need 4 mm walls
6 Compute pallet: cutouts under the module between the tie-down pattern and the standoffs (Rev B, or the P1 pallet as cut) -60 $30 (a second pallet) or $0 P3 the pallet is clamped at four isolators and loaded by the J501 on its standoffs
7 The fuse board (D60) replaces ten inline MINI holders and their leads -35 $20 (JLCPCB 5 boards + 12 Keystone 3568/3570 holders at ~$0.30 on LCSC) P3 planned in D55; the inline holders stay on P0-P2 labelled "58 V fuses only"; the PCB holders are 32 V parts too, so D55's rule (the fuse interrupts; never pulled live) carries over, and on a board under the shell no one pulls a fuse without the cover off
8 The shunt on the fuse board (a 1 mOhm 5 W part under the INA228) instead of the 85 g Blue Sea 150 A block -75 $3 P3 80 A for 0.3 s is 6.4 W for 0.3 s in a 5 W part: fine; 5 A average is 25 mW; the Blue Sea shunt stays on P0/P1
9 The main path in 10 AWG (the 8 AWG 10 ft becomes the spare), the VESC leads in 14 AWG at the firmware's 25 A, lengths cut to P0's measurements -60 $25 (BNTECHGO 10 AWG 25 ft) P2 10 AWG silicone carries 55 A continuous; the 80 A jump pulse is 0.3 s; 1.5 m of 10 AWG is 4.9 mOhm, 0.4 V at 80 A

On call, in this order, when the ledger needs them (D57): about -1.0 kg more

# Lever Grams Cost When
10 Pololu D42V110F12 (60 V in, 9 A, ~15 g) instead of the 216 g DDR-60L-12 -200 $69.95 when Pololu restocks it (backordered 2026-10-27); the 12 V rail needs no isolation (both converters' -V are tied to the - bus anyway, W23, W27)
11 Cheeks Rev B: 1/8 in 5052 with 10 mm-hex M6 standoffs or four more standoffs (-330), or lightening holes in the 3/16 in web where S1 is under 40 MPa (-150) -150 to -330 $120 (two cheeks) P3 or later, only with the FEA >= 2.0 away from the standoff ends and 1.2 at them; never before P1's measured loads
12 Aluminium hex standoffs (M5, 120 mm) in place of the printed posts -80 ~$30 (part number to read) P3
13 The 34 mm WS-RDN-601 cooler (~250 g est) instead of the fitted G48R -200 $49 the user's call after the thermal test; the G48R is what is on the J501 today (j501-agx 0.9-g48r, user photos)
14 The lidar cable shortened to 1 m -150 $0 the user's call: Velodyne's manual says the cable is permanently attached (a waiver)
15 The turret's own levers: a 24-ball hopper (-11), a one-board bay (-40) -51 $0 the turret's, listed in its plan, not scheduled

Not pulled, and why

2.4 The call

3. The stages

3.1 P2: the head (after P1 balances on the tether)

The turret mount plate under the deck (B4), the riser, the turret on the TM pattern (after the turret's N16 sign-off), the DT12 harness and the STOP contact (already wired in P0: W34-W37), the lidar; the turret's own N18 tests on the robot (the barrel and every ball 10 mm clear of the lidar at every pan, the 30 cm drop on the stand); then target practice tethered, then untethered in a padded room. P2 also fits the hollow posts and the 10 AWG harness if P1's parts are reprinted or re-cut for any other reason (otherwise they wait for P3). Blocked on: the turret's exports/turret/mount.json (N5) for the final plate and riser (the plug's sweep box), and the turret hardware (N12-N16).

3.2 P3: the shell, the fuse board, the tub, the thermal test, the hazard review

Side walls in 2 mm ribbed PETG with the ear pods, the door and the port hatches cut from the mated plugs; the front face (eyes, D435 cradle); the rear (D435, four RP-SMA bulkheads, the radio receiver bay and stop jack, the switch's 16 mm button, the charge port); the belly box skids; the knee wedge, apex fillet and stop fairings against the leg's stops as merged; the battery tub in bent 0.063 in 5052 with printed end caps (the PETG bay burns: hazard 18); the fuse board with the shunt and INA228 on it; the Rev B tray, deck and pallet if the ledger needs them. The thermal test at 40 W with a thermometer; the touch temperatures; the hazard review on the P3 robot with the clash list before any session with children.

3.3 P4: the dock

The old plan's section 2.5 with one change: the battery is in the belly now, so the charge pads sit in the tub's floor, the lowest point of the body, and the dock is 30 mm tall so the pads land with the legs 8 mm above their stops (B9).

4. Tasks for the Opus implementer

Estimates are agent working hours; "compute" is FEA or MuJoCo time; "bench" is days with hardware in hand, where the user's hands are needed (the implementer writes the procedure and the log). Work in a feat/ worktree of ~/Code/gyropup with a PR into dev. New CAD goes in cad/body_*.py, exports in exports/body/. Nothing in cad/fivebar.py, cad/turret.py or sim/leg_rig.py changes without the owning workstream (requests to them are section 5). Heavy results go to /mnt/storage-10tb/claude-scratch/gyropup/ (the root drive is nearly full); only the summaries are committed.

4.1 Now, without hardware

# Task Files Acceptance Est.
B0 Baseline. Build main 0e1b0ec: cad/build_body.py --no-render --quick, cad/test_body.py, cad/body_bom.py; cad/body_studies.py --only D2 for one case. Record 29.17 / 32.27 kg, 0 clashes, D2 1.31, the sheet rules clean exports/body/* every build runs; the numbers reproduced within 1 % 0.5 h + 0.5 h compute
B1 The order, fixed and priced (section 1.2): the XT60H / XT30U genders in cad/body_wiring.py (W10-W15, W21, W24: sockets on the body) and the Pyrodrone lines; a second DDR-120C-24; the SendCutSend instant prices read from the uploaded DXFs and written to bom.json made_to_order with the date; docs/body-orders.md and the Build page regenerated cad/body_wiring.py, cad/body_bom.py, exports/body/bom.json, docs/body-orders.md, docs/body-electrical.md 3.1 no live pin on any body-side lead; every made-to-order line carries a read price; test_every_bought_line_has_a_price_and_a_backup passes 1.5 h
B2 The ledger as the gate (D57). cad/body_layout.py: the budget becomes {robot 32.0, legs 9.6 each, body_and_head 12.8, design mass 32.5}; LEG_STOPS_EXTRA_G read from exports/leg/leg.json when the leg publishes its new total; a LEVERS table (section 2.3: grams, cost, stage, status taken/open) written into mass.json; cad/test_body.py asserts the P3 ledger <= the ceiling once the P2/P3 levers' CAD exists (until then the test reports the gap and the open levers) cad/body_layout.py, cad/test_body.py, exports/body/mass.json, docs/body-frame.md 4 mass.json shows the ceiling, the gap and every lever's status; the test fails on a ledger over 32.0 kg with the levers taken 1.5 h
B3 The sim on the real body (old T4; D62). sim/robot_v2.py reads the body: BODY_X +-160 and Y_OFF from cad/body_params.py, the frame's solids as collision boxes (cheeks, tray, deck with lips, belly plate, the battery tub as the lowest body, the ear pods), every mass and centre from mass.json P3 (the legs from leg.json as now), the head payload from body_params.tm(); BUS_V 48.1 in robot_v2; the fifteen scenarios in exports/sim/v2 re-run; cad/body_legloads.py --half-mass 16.25 and cad/body_studies.py re-run at the design mass; the RL build (rl/gyropup_rl/robot.py) left to regenerate on its next run with the trained policy frozen under GYROPUP_MODEL_DIR sim/robot_v2.py, exports/sim/v2/, exports/body/leg-loads.json, exports/body/fea/, exports/body/body.json every scenario passes at the ledger's mass (31.8-32.0 kg; or a dated note per failure); mass_reconciles true; the jump >= 230 mm at 48.1 V; every body case passes its target at 16.25 kg; cad/test_body.py test_body_loads_from_the_mount_plane reads the new file 4 h + 3 h compute
B4 The turret mount plate (D59) and the riser. body_frame.py: a 6061-T6 0.125 in plate 160 x 140 under the deck centred on the pan axis, pocketed, Ø5.5 on the riser's 80 x 80 feet (clamped by the riser's four screws from below; its screws grow by 3.2 mm), two PEM SS-M5-0 at its +-x ends for M5 screws down through the deck (two new Ø5.5 holes in the Rev B deck; drilled by hand in the P1 deck at the plate's positions); the plate clear of the intake field and the fan's exhaust keep-outs; the riser reads exports/turret/mount.json when it lands (the plug's sweep box as a keep-out); the D2 case with the plate; the P2 SendCutSend cart (the plate; the Rev B tray/deck/pallet as options) cad/body_frame.py, cad/body_studies.py, cad/body_sheet.py, exports/body/sendcutsend/turret_plate_6061-T6_0.125in_x1.{dxf,step}, exports/body/fea/D2-turret-fall.*, exports/body/bom.json D2 >= 2.0 and D1 at the riser <= 0.5 mm at the load; the plate passes the sheet rules; 0 clashes with the sweep and the plug box; the ledger +135 g or less 3 h + 1 h compute
B5 The fuse board (D60). A KiCad project: twelve Keystone 3568 (or 3570) MINI holders (eleven branches and a spare) on 2 oz copper with M6 lug pads for the + bus input, the 1 mOhm 5 W shunt with Kelvin pads to an INA228 footprint (the Adafruit 5832 stays usable as a daughter), the - star point stud and the frame-bond stud, "58 V fuses only" in silk at every holder; JLCPCB gerbers; the catalog entries (board, holders, shunt) with prices and links; cad/body_layout.py carries its envelope on the power plate where the inline holders were; the wire list's P3 rows hardware/fuse-board/ (KiCad), catalog/body.json, cad/body_layout.py, cad/body_wiring.py, docs/body-electrical.md the board fits the plate_fuses box (120 x 42 x 32) or the layout shows where it moved; every branch's trace carries its fuse's rating at 2 oz; the INA228 reads the shunt within 1 %; JLCPCB's DFM passes; every line priced with a link 6 h
B6 Shell and guards CAD (old T7, part 1; D61). cad/body_shell.py, cad/body_guards.py: side walls 2 mm PETG with ribs, the ear-pod bosses (PA6-CF inserts, the SSC1's 5/16-18 feet), the right door, the port hatches cut from the mated plugs with cadagent.connectors (the J501's ports in its parameters.json; never a receptacle bbox), the front face (DualEye, the D435 cradle), the rear (D435, 4 x RP-SMA, the receiver bay 30 x 50, the stop jack, the switch's 16 mm button, the XT60 charge port bezel), the belly box and TPU skids, the battery tub in 0.063 in 5052 (bent, printed end caps, the pads' positions for B9), the knee wedge, apex fillet and stop fairings against the leg's stops as merged; the clash sweep with the shell on; the wall stepped in under z +35 where the upper links sweep ( y 102.5-132.5); the vents sized by B7; every part on the bed cad/body_shell.py, cad/body_guards.py, cad/build_body.py, exports/body/shell/*, docs/body-safety.md
B7 Thermal (old T7, part 2). A hand model first: the G48R's fan at its 16 CFM operating point (the J501 project's checks) against the intake field and the louvres' free area, the exhaust path at +-y under the deck, the DDR-120C at <= 50 % load on its side; the louvre areas set so the fan sees <= 5 Pa of extra back-pressure; the CFD (j501-agx tools/thermal.py, cadagent.cfd) only after the J501's own Rev 0.9-g48r case reports calibrated and steady, one heavy solver at a time; otherwise the thermometer test in B16 is the acceptance docs/body-thermal.md, exports/body/thermal/ the hand model's numbers with their assumptions labelled; the vents' areas in B6's CAD; the CFD case, if run, within 3 K of the hand model's intake temperature 3 h (+ 6 h compute if the CFD runs)
B8 Firmware skeleton (old T9, part 1; D63). firmware/balance/: C on the Nucleo-G474RE (CMake + arm-none-eabi, STM32 HAL): three FDCAN at 1 Mbit classic, the VN-100 at 800 Hz on a UART, the 1 kHz loop ported from sim/controller_v2.py, the RS-04 private protocol as the leg rig's rig/rs04.py defines it, the VESC current mode, the Jetson protocol on can0 (setpoints 100 Hz, state 200 Hz, 200 ms heartbeat: balance 2 s then sit), the STOP sense (the relay coil) and MCU_RUN, the INA228 over I2C, UVLO (warn 45.5 V, sit at 41.6 V) and the jump gate (no jump under 44 V resting, height scaled with voltage), the people-near caps as inputs; a HIL harness in Python reusing rig/rs04_emu.py and rig/sim_bus.py from legrig/program through a USB-CAN adapter; CI builds the firmware firmware/balance/, firmware/balance/hil/, docs/body-firmware.md the firmware builds in CI; the HIL replays the sim's stand_up within 5 % of leg length and lean against the emulated RS-04s; the jump gate and UVLO trip in the HIL at their numbers 8 h
B9 The dock (old T10; D64). cad/dock.py: the two brass pads (McMaster brass bar 25 x 60 x 3 on a PETG insulator) in the tub's floor, the printed funnel correcting +-8 mm, the spring contacts, the dock-present pair, the HLG-320H-54A (53.3 V, 3 A) in a printed housing, the AprilTag on the dock's face; the charge relay (10 A automotive, MCU-driven) in the charge port's line in the wire list; the docking scenario in robot_v2 (reverse over the tag at 0.3 m/s, sit to 108 mm, pads closed, the relay only after the tag, the sit and the present line); the parts list with prices cad/dock.py, exports/dock/*, sim/robot_v2.py scenario dock, docs/body-dock.md, catalog/body.json the pads land inside the funnel from +-8 mm and +-5 deg; the dock 30 mm tall clears the tub at every drive stance; the scenario passes ten times from random starts; every part priced with a link 6 h + 1 h compute
B10 Provenance and CI. Vendor the J501's parameters as references/vendor/j501/parameters-0.9-g48r.json with its git hash and the photo note; body_params.j501() reads the live file when present and the snapshot otherwise, and a test fails when they differ; exports/turret/mount.json and exports/leg/leg.json read with their hashes recorded in body.json; a CI job runs cad/test_body.py and cad/build_body.py --quick --no-render cad/body_params.py, cad/test_body.py, references/vendor/j501/, .github/workflows or the repo's CI the tests pass with and without ~/Code/j501-agx present; a changed cooler centre fails the test until the snapshot is updated on purpose 1.5 h
B11 Record. D57-D64 in docs/decisions.yaml (section 6's text); docs/body-frame.md, body-electrical.md, body-safety.md, body-orders.md updated for B1-B9; docs/milestones.yaml M5's evidence; the progress and Build pages show P2-P4 docs/*, designer every open line of this plan points at a task or a bench test; the Build page lists the P2 cart 1.5 h

Order: B0, B1 (the user orders), B2, B3 and B4 together (B3's loads feed B4's D2), B5-B9 in any order, B10, B11 last. About 45 h of agent time and 8 h of compute: four or five sessions. Printing of the P1 parts and the leg's and turret's builds run alongside.

4.2 With hardware in hand

# Task Files Acceptance Est.
B12 P0 bring-up (docs/body-electrical.md 6, plus: step 1 gains the switch-off float check; the Flipsky card read for the switch's side; the BMS's short-circuit setting from its card; ip link for can1; the INA228 calibrated against a meter; step 9: an 80 A, 0.3 s pulse (the two RS-04s stalled at a current limit, or an electronic load) on the pack at 44 V resting with the 24 V output on a scope: no dip under 20 V; the measured lengths and masses of every wire and part into the ledger) docs/body-electrical.md 6-7, exports/body/bringup.json, exports/body/mass.json the eight steps green with their numbers; the 24 V rail holds through the pulse; the ledger's power and harness rows say "measured" 2 h + 1 bench day
B13 Firmware on hardware (old T9, part 2). One RS-04 and one BotWheel closed-loop from the Nucleo; the VN-100 at 800 Hz; the stop relay from the radio; then both legs on the P1 frame on the tether firmware/balance/*, exports/body/firmware-tests.json the HIL's stand_up reproduced on hardware within 10 %; the stop drops torque and sits within 0.5 s of the transmitter off 6 h + 2 bench days
B14 P1 assembly and the tether tests (docs/body-frame.md 6; the plan's order: stand, hold, squat, drive 1 m/s, push, brake from 2 m/s with the scope on the bus, the first jump); the weigh-in of every group into the ledger; a thermometer on the cooler's intake and the DDR-120C on the open frame at 40 W (the baseline for P3) docs/body-tests.md, exports/body/p1-tests.json, exports/body/mass.json bus <= 57 V in every brake and squat; stand and hold for 60 s; the measured robot mass within 3 % of the ledger (else the ledger corrected and B2's gate re-run) 4 h + 3 bench days
B15 P2: the head. The mount plate and riser fitted; the turret on (after its N16); the DT12 plugged with the stop relay open; the turret's N18 tests; target practice tethered, then untethered in a padded room; the recoil scenario against the real robot's log exports/body/p2-tests.json, docs/body-tests.md every ball and the barrel 10 mm clear of the lidar at every pan; the robot holds balance through an 8-shot burst; the stop cuts the flywheels and sits the robot 3 h + 2 bench days
B16 P3: the shell. The 2 mm panels, guards, the tub, the fuse board (the inline holders and the Blue Sea shunt come out), the lights, eyes and cameras; the 40 W thermal test with a thermometer: the cooler's intake within 3 K of the room, the plate <= 70 C, the exhaust air <= 45 C at the louvres, the ear pods' housings <= 60 C at 50 % PWM; the touch temperatures; the hazard review with the clash list and the table exports/body/p3-thermal.json, docs/body-safety.md (every row's status to "test" with its number) every thermal number inside its limit, or the louvres opened and re-tested; the hazard table has no row left "open" before a session with children 6 h + 3 bench days + ~30 printer hours
B17 P4: the dock. Build and the docking test: ten approaches from +-0.3 m and +-15 deg, the charge logged by the INA228 exports/dock/tests.json 9 of 10 dock and charge at 3 A; the relay never closes without the tag, the sit and the present line 4 h + 2 bench days

5. The interfaces to freeze, and what is blocked on them

5.1 With the leg workstream (cad/fivebar.py, sim/leg_rig.py, branch leg/stops)

  1. The design mass: 16.25 kg a leg (D57). sim/leg_rig.py HALF_MASS 16.25 and BUS_V 48.1 (the body does not edit the rig; cad/body_legloads.py passes --half-mass); cad/leg_studies.py at those loads; the wheel link R1w, the front link R1 and the hub dowel bearing to >= 1.2 at that mass; the extra material <= 0.12 kg a leg; the jump re-run. The body's loads of docs/body-frame.md 3 were taken with the rig at 54 V: at 48.1 V the jump's speed cap is lower, so they are if anything conservative.
  2. The hip-frame interface, frozen with the cheeks' order: six M5 inserts at (x, z) = (0, +-70), (+-83, +-70) on the plane y = -82.3, pilots Ø6.4 x 11 mm, the flange end faces flat on that plane (face [-95, 95] x [62, 77] and its mirror); M5 x 12 button heads at 3 N m from inside the gap. Any move of a boss or the plane after the cart is ordered is a new pair of cheeks ($120). The leg/stops branch does not touch them (checked: its leg.json and frame_mounts are unchanged).
  3. Insert pull-out: the body's bottom row sees <= 0.7 kN a bolt in the landing, with the cheek bearing on the flange ends; the leg plan's bench test B4, the insert coupon (docs/leg-prototype-plan.md 5: pull-out >= 2.4 kN, torque against preload), is the body's evidence too. If it comes in under 1.4 kN the body's fallback is a through-bolted row (M5 nuts behind the flanges), agreed with the leg.
  4. Harness exits, frozen: the RS-04 leads leave past the cheeks' ends (fivebar.harness_angle(): the front one at (127.6, 24.5) at +25 deg, the rear at (-131.9, -11.1) at -169 deg, exports/body/body.json) and turn into the gap; the wheel harness through the cheek window |x| <= 20, |z| <= 25; the zone between the motors' rear covers and the centre plane stays free (the gusset at x = 0, z 28..62 stays).
  5. Motor pigtails: 450 mm from the rear cover, ending in XT60H-M (the body's branch lead carries the socket: section 1.2); the CAN on GH1.25 with 350 mm extensions to the MCU shelf; a 120 Ohm plug at the far motor of each leg.
  6. The sweep envelope near the body, frozen as a test: upper links |y| 102.5..132.5 reaching z <= +31 within |x| <= 225; the knee eyes at |y| >= 83 (the belly plate is 116 wide for them: hazard 1); the stop pins on the frame's outer face only. The body's clash sweep (cad/build_body.py) runs on both sides' CI; when leg/stops merges, B6 re-runs it before the fairings are drawn.
  7. The leg's mass: exports/leg/leg.json mass_g.total with the stops' redesign (today 9,282 g + 0.2 kg announced); the ledger reads it (B2). The ceiling allows 9.6 kg.

Blocked on the leg: the three strength fixes and the rig at 16.25 kg (B3's leg-side acceptance); leg/stops merged (B6's fairings); the coupon test (item 3); the pigtails (P1's wiring); the rig's bench test B1 (the RS-04's power-off damping, which sets the fault loads the body's S1F case uses: 275 N m undamped, 42 N m damped).

5.2 With the turret workstream (cad/turret.py, docs/turret-next-plan.md)

  1. The TM face at (x, z) = (-40, 246) (deck 216 + riser 30), the lidar's axis 143 mm ahead at x 103, the foot Ø144 and the bumper Ø172 (its rear at x -126, 34 mm inside the deck's rear edge); cad/turret_lidar.py --x 143 is the turret's N5; body_params.LIDAR_TO_PAN is the one source.
  2. The grommet at (-130, -60), right rear (D52 taken; test_turret_grommet_on_the_right_rear); the DT06 plug stands at y -78..-123 from the pan axis at z 265 and withdraws to -153: above the deck and the side wall's top, over the right ear pod (z 142..193). B6 keeps that box empty.
  3. exports/turret/mount.json (N5): mass with and without balls, the centre, the plug's sweep box, the recoil; body_params.tm() already reads it and takes the larger of it and the 1.70 kg budget. Until it lands the riser and the plate are built to the budget and the plug box of docs/turret-next-plan.md 3.1.
  4. The electrical interface, frozen as wired in P0: pins 1-2 +24 V through the stop relay's pole B and a 5 A MINI 58V fuse (W22, W34), 3-4 GND (W35), 5-6 CAN to the J501's can1 (W36; the J501 terminates), 7-8 the dry loop on pole A (W37; closed = run; opens on the radio stop, the receiver's failsafe and the sit). The DT12 is plugged only with the relay open (the turret's 3 x 1,000 uF). The 24 V rail is 120 W for the J501 and the turret together: the firmware staggers the flywheel start from the Jetson's boot; the DDR-240C-24 is the step up if B12 shows the rail at its limit.
  5. The riser's TM inserts: M5 x 4, 8 mm deep in PA6-CF, under the plate of D59; the turret's 30 cm drop test on the stand (N18) is the turret's; the body's D2 case is the deck's.

Blocked on the turret: mount.json (B4's final geometry), the turret hardware signed off (N16) for P2, the N18 tests on the robot, and the lidar table at 143 mm in the turret's CONFIG.

5.3 With the J501 project (read only)

The carrier's holes, the module's tie-down pattern, the G48R cooler (102 x 97 x 50.3 over the spreader at PCB (88, 70.5) +-3 mm) and the ports come from ~/Code/j501-agx/cad/parameters.json revision 0.9-g48r, an uncommitted working tree on branch rev0.8-thermal. B10 snapshots it. The fan centre is measured from the PCB edges when the carrier is on the pallet (B14) and the snapshot corrected if it moved. The cooler tie-down keeps the user's rule (four M3 x 35 flat heads from below through the pallet into the module-to-heatsink standoffs, 4 mm of thread; the M3 x 35 is still to source: docs/body-orders.md 4).

6. Proposed decisions (for docs/decisions.yaml; the implementer records them in B11)

7. Still to verify, and TODO

8. Notes from the work (2026-10-27)

9. D65 and D66 in the body (2026-10-27; issues #10, #12, #13)

10. D69 in the body (2026-10-28; issues #10, #19, #14, #20)

D69 swaps the BotWheel for a 10 in hoverboard-class hub motor with a 10 x 2.125 pneumatic tyre. It moves the mass gate to 35.0 kg as built, with a 35.5 kg design mass (17.75 kg a leg).

10.1 Where the body sees the wheel

Until the leg's foot change lands (#25), the body draws D69's wheel in the BotWheel's place (cad/body_params.py _fivebar()), using the same solid as sim/wheel_study.py: a 254 x 64 tyre ring, a Ø165 x 65 drum and the Ø17 shaft. The sim (sim/robot_v2.py use_d69_wheel()), the legs' loads (cad/body_legloads.py) and the firmware header (firmware/balance/tools/gen_model.py) put the same wheel into sim.leg_rig's state, at the ledger's 3.53 kg. No leg file is edited.

10.2 The mass gate (issue #10; D71 replaces D69's numbers)

D71 (main e899d28): 37.5 kg design (18.75 kg a leg), 37.0 kg as built. P3 is 36.75 kg, 36.25 kg with the scheduled levers: a 0.75 kg margin, and the test passes. The turret's 2.55 kg head (D66) would use about 0.85 kg of it. What follows is the state under D69.

The P3 ledger is 36.75 kg: 36.25 kg with the scheduled levers, which is 1.25 kg over 35.0. That includes the crush pads' 0.23 kg (10.3); before the pads it was 36.55 / 36.05 kg. The gate is not relaxed: test_the_mass_ledger_under_the_ceiling stays a strict xfail. The levers that could close the gap are listed in docs/body-sections.md 6. The body-only ones are worth 0.8-1.0 kg, which is not enough alone. The rest are:

D69's 35.0 kg came from a ledger taken before D65's packs.

10.3 Crush pads and the lowest leg (issue #19)

The bay's four pads are printed TPU 95A gyroid:

Property Value
Free height 50 mm
Stroke 30 mm
Crush plateau 600 N a pad
Ground clearance needed 40 mm

To keep the 40 mm, the lowest controlled leg rises from 160 to 185 mm. H_RANGE in sim/robot_v2.py reads it from cad/body_battery.py PAD; the firmware's GP_H_MIN_M follows, and a test ties the three together. The leg's stops do not move.

Results, with the densified pad modelled as stiff as the 1 ms step allows:

Case Body impact (limit 3,000 N) Stroke used
Three falls at the scenarios' push 2,723 N worst 22.1 of 30 mm
All nine (pushes x0.6, x1.0, x1.4) 2,724 N worst 27.6 mm
1 kg more 2,740 N
Rigid pads 7,257 N

The robot sits on the pads under a 145 mm leg; they are D69's parking brake. The pads add 0.23 kg against the 30 g skids. Evidence: sim/bay_pad_study.py, exports/body/bay-pad-study.json and bay-pad-depth.json. Still to do:

10.4 Track, stance and the wheel's connectors

BotWheel D69 10 in
Track (tyre centres) 414.3 mm 433.7 mm
Tyre band from the cheek's face (y, robot) 184.9-229.4 mm 184.9-248.9 mm
Hip height at the 0.6 stance 375 mm (289 mm leg) 426 mm (299 mm leg: the range is now 185-375)
Under the pads at a 300 mm leg - 155 mm
Tyre to the nearest body part (22 stances) - 72.5 mm (the ear bracket, folded leg); no 4-25 mm gap
15 robot scenarios (tools/simulate.py --v2) 14/15 (fall_side 3,408 N) 15/15 at 36.75 kg

Each wheel connects at the cheek's end, where its lead enters the cable chase beside the RS-04 harnesses. The body carries the sockets and the wheel's lead carries the pins:

Wire Signals Body side Wheel side Contacts
W50, W52 phases, 14 AWG DTP06-4S DTP04-4P size 12, 25 A
W51, W53 halls and NTC, 20 AWG shielded DT06-6S DT04-6P size 16

Prices are still to read. Mouser timed out for an automated read on 2026-10-28. The BOM gets these lines once their prices are read (D40).

10.5 IP54 (D69: splashes, puddles to 40 mm)

The sealed volume is the shell, the deck and the belly plate. The bay sits outside it: the packs are DeWalt's jobsite-rated tools, and their adapters face up under the belly plate.

Opening Seal Status
Shell panels to the belly plate, deck and each other 3 mm closed-cell EPDM foam gasket in the joints, compressed by the panels' screws design (B16)
Rear louvres (14 x 4 x 46 mm a side, the J501's air inlet) and the TRACO vent a drip lip over each bank, slots angled down, 30 ppi filter foam behind open: the foam's pressure drop is not in B7's hand model; it must keep the fan's extra back-pressure under 5 Pa, else the louvres grow
Port hatch, door the same gasket; the hatch closed while it rains design
Wheel and RS-04 leads at the cheeks' ends (the cable chase) a split grommet per lead; Deutsch DT/DTP connectors are IP67 when mated design
The pack leads through the belly's slot a grommet round the two 10 AWG leads design
Breathing one vent plug (ePTFE, M12) in the rear wall; the VESCs dump heat into the plate, so the box is not sealed tight design
The bay's side plates (three lightening windows each) splash reaches the adapters' terminals from the side: close the windows (the plate gets heavier) or add printed covers open: decide with the pack's measured interface (#20)
Hubs the wheel workstream's seal procedure (W8) the leg's

10.6 Still open