A first-order thermal model per joint, fitted to this A1 rather than borrowed. It predicts the temperature the robot actually reports to within 0.39 °C on a sensor that resolves whole degrees — and the second log, 784 seconds of the robot doing nothing, is what makes it right.
One lumped node per motor. Three parameters each.
| symbol | meaning | units |
|---|---|---|
| a_j | inverse thermal time constant, 1/(R·C) | 1/s |
| b_j | ohmic gain, k/C — τ² stands in for I²·R_d | K/(s·(N·m)²) |
| T_env,j | idle equilibrium — where this joint settles doing nothing | °C |
τ² substitutes for I² because motor torque is very nearly proportional to current. That is the same substitution arXiv 2603.01631 makes, and our own hardware agrees: against the temperature each joint actually recorded, τ² correlates at r = +0.990 on the calves, while |τ·ω| — the quantity the locomotion reward actually penalises — correlates at r = −0.954.
Two logs, jointly. Neither one is enough alone.
| log | what it is | what it constrains |
|---|---|---|
| run0050 | 818 s walking outdoors, calves 37 → 67 °C | b, and a through curvature |
| run0047 | 784 s at phase 0, rms |τ| = 0.05 N·m | T_env |
run0050 never goes quiet, so it cannot see where an idle robot settles.
Fitted on its own it put T_env at a single 36.29 °C and then got the idle regime
wrong in sign — predicting cooling where the robot warms.
run0047 has no torque, so it says nothing about b.
Together they are well posed.
The SDK reports temperature as an integer, so a 1 °C quantum across a 0.02 s step is a 50 °C/s derivative — differentiating the signal produces noise, not data. Instead the model is forward-integrated through the entire torque trace and the trajectory residual is minimised, multi-start over decades of a and b so a local optimum cannot pass for the answer.
One instinct worth naming and discarding: that an active session is a poor place to fit a thermal model. It is the opposite. A varying load is persistent excitation, and it is precisely what separates the input-driven term from the decay term. Section 04 shows it works.
Sensor resolution is 1 °C. RMSE is at or below the quantum on every joint, on both logs.
./python.sh tools/plot_thermal_fit.py.
| joint | RMSE walk | vs 1 °C quantum | RMSE idle | a [1/s] | time const. | b | T_env |
|---|---|---|---|---|---|---|---|
| FL_hip | 0.27 | 0.28 | 1.83e−3 | 9.1 min | 2.15e−3 | 41.7 | |
| FR_hip | 0.34 | 0.36 | 1.96e−3 | 8.5 min | 2.74e−3 | 41.2 | |
| RL_hip | 0.24 | 0.22 | 6.46e−4 | 25.8 min | 9.68e−4 | 38.3 | |
| RR_hip | 0.33 | 0.21 | 2.12e−3 | 7.9 min | 1.92e−3 | 37.4 | |
| FL_thigh | 0.33 | 0.26 | 1.86e−3 | 9.0 min | 2.07e−3 | 37.4 | |
| FR_thigh | 0.29 | 0.28 | 1.21e−3 | 13.8 min | 1.70e−3 | 39.6 | |
| RL_thigh | 0.33 | 0.26 | 1.46e−3 | 11.4 min | 1.80e−3 | 36.5 | |
| RR_thigh | 0.33 | 0.26 | 1.35e−3 | 12.4 min | 1.73e−3 | 37.2 | |
| FL_calf | 0.42 | 0.17 | 3.12e−3 | 5.3 min | 1.61e−3 | 37.3 | |
| FR_calf | 0.48 | 0.31 | 2.94e−3 | 5.7 min | 1.52e−3 | 36.6 | |
| RL_calf | 0.61 | 0.33 | 2.78e−3 | 6.0 min | 1.45e−3 | 36.6 | |
| RR_calf | 0.69 | 0.34 | 2.96e−3 | 5.6 min | 1.43e−3 | 35.5 |
The vertical rule at the end of each bar is the sensor's own 1 °C quantum. Mean 0.39 °C walking, 0.27 °C idle, across a signal that moves 30 °C. The calves are the worst-fitted and are also the ones that matter — 0.69 °C on a 30 °C excursion is 2.3%.
The obvious worry with a curve that never plateaus.
If the heating curve never levels off, does a trade off freely against b? Tested directly — force a away from its optimum, refit b freely, measure the damage:
| joint | fitted a | RMSE | ×0.25 | ×0.5 | ×2 | ×4 | ×10 |
|---|---|---|---|---|---|---|---|
| FL_calf | 3.16e−3 | 0.42 | 1.19 | 0.81 | 1.02 | 1.99 | 3.35 |
| RL_calf | 2.82e−3 | 0.60 | 1.50 | 1.05 | 1.33 | 2.67 | 4.69 |
A factor of two is clearly distinguishable; a factor of ten is excluded outright. The calves' cooling rate is real information in this data, not an artefact of the parameterisation — and that is what lets section 05 rule something out rather than merely disagree with it.
The hips are not identified this way. They barely heat, so there is no curvature to fit, and RL_hip's 25.8-minute time constant should be read as "unconstrained" rather than measured. Harmless: the calves are the binding constraint, and the hips' steady state (b/a), which is what every projection uses, is well determined even where a alone is not.
Theirs is the richer model. Ours is the right one for this signal.
arXiv 2603.01631 uses a 14-node coupled whole-body network — a node per motor plus the onboard computer, with conduction resistances between neighbours, parameterised from Lin et al., ROBOT 2025. As physics that is a better model than twelve independent first-order nodes, and nothing here claims otherwise.
The claim is narrower: ours is right for this robot and this signal, on three counts.
motorState.temperature straight into the policy observation. Their
parameters describe their model's nodes; ours describe the sensor we deploy against.
Training on one node and feeding another at runtime is a sim-to-real mismatch on the
single input the whole method rests on.Four limits, one of them with a free fix.
run0050 is a walking robot; a stationary one cools more slowly than these
constants say. run0047 cannot separate the two because it never gets hot
enough to show a decay. The missing measurement is a hot cooldown: run
the robot warm, then leave it powered and sitting while the log keeps writing. That
sends no motor commands at all, so it is read-only under the standing robot rule.run0050's own
average τ², the "minutes to 66 °C" projection reports never trips
for a run whose rear calves did trip — a run average folds in every easy stretch, and what
cooks a motor is the hard part. Those numbers are a comparison instrument between arms
measured the same way, not a wall-clock prediction.