Skip to the lesson
FixTheRobot Home / VLA / Phase 10 / 10.6

VLA · Phase 10 · Humanoid VLA Systems

10.6 — Balance, Contact Constraints and Embodiment-Specific Actions

Keeping a humanoid upright and respecting contacts while it acts.

  • Roadmap phase 10
  • Humanoid VLA Systems
  • About 14 minutes to read

A humanoid stays up only while physics allows it. This lesson derives the basic checks from first principles: where the weight sits, where the zero-moment point is, how much acceleration is allowed and when a foot slips.

Claims about papers, models and textbooks carry numbered references, listed with what each one is cited for in the References. Code marked “Toy model” is our own illustration, not the cited system.

Learning objectives

After this lesson, you should be able to:

  • Check static balance with the support polygon.
  • Compute the zero-moment point of an accelerating body.
  • Convert it into an acceleration limit.
  • Check for slipping with the friction cone.
  1. The policy requests a motion
  2. Compute the centre of mass and its acceleration
  3. Check the zero-moment point against the foot
  4. Check contact forces against friction
  5. Clip the request or hand over to the whole-body controller
Balance and contact checks.

The one-sentence idea

Check the physics that keeps the robot up before the motion happens.

These limits belong to this robot’s body, not to the policy.

The body has rules the policy does not know

A humanoid stays upright only while certain physical conditions hold: its weight must be supported over the feet, the feet must not slip, and the joints must stay within range. A VLA trained on demonstrations has no built-in knowledge of those conditions; they have to be enforced by the lower layers (lesson 10.5) and by checks on every command (lesson 9.4). The classic tool for walking robots is the zero-moment point (ZMP), reviewed by Vukobratović and Borovac, whose paper notes it was first practically demonstrated in 1984 at Waseda University on the dynamically balanced robot WL-10RD, and warns of delicate issues that can cause confusion if ZMP is applied mechanistically, for example when a humanoid loses dynamic balance.[1] This lesson builds the basic checks from first principles.

Static balance: is the weight over the feet?

Stand still: you are stable if the vertical projection of your centre of mass (CoM) falls inside the support polygon, the area spanned by your feet's contact with the floor. Reach an arm forward and the CoM moves forward with it. For a robot with a foot that extends 0.12 m in front of the ankle and 0.08 m behind it, and an arm of 4 kg reaching forward from a 40 kg body:

import numpy as np

body_mass, arm_mass = 36.0, 4.0                    # kg; the arm is part of the total of 40 kg
front, back = 0.12, 0.08                           # metres of foot in front of and behind the ankle (CoM of the body is above the ankle)

for reach in (0.0, 0.2, 0.4, 0.6, 0.8, 1.0):
    com_x = (arm_mass * reach) / (body_mass + arm_mass)          # forward shift of the overall centre of mass
    margin = front - com_x
    state = "stable" if -back <= com_x <= front else "TIPS FORWARD"
    print(f"arm reaches {reach:3.1f} m -> CoM shifts {com_x * 100:4.1f} cm forward, margin to the toe {margin * 100:5.1f} cm -> {state}")

Output:

arm reaches 0.0 m -> CoM shifts  0.0 cm forward, margin to the toe  12.0 cm -> stable
arm reaches 0.2 m -> CoM shifts  2.0 cm forward, margin to the toe  10.0 cm -> stable
arm reaches 0.4 m -> CoM shifts  4.0 cm forward, margin to the toe   8.0 cm -> stable
arm reaches 0.6 m -> CoM shifts  6.0 cm forward, margin to the toe   6.0 cm -> stable
arm reaches 0.8 m -> CoM shifts  8.0 cm forward, margin to the toe   4.0 cm -> stable
arm reaches 1.0 m -> CoM shifts 10.0 cm forward, margin to the toe   2.0 cm -> stable

Toy model. A one-dimensional picture with made-up masses and foot sizes. A real humanoid also moves its torso and hips to counter a reach, which is why whole-body controllers matter.

With these numbers the robot tips when the arm reaches beyond 1.2 m, which is not reachable, so a single arm is safe here; carrying a heavy load changes that quickly (try arm_mass = 15). Real controllers shift the hips backward to keep the CoM over the feet while the hands reach: whole-body control (lesson 10.5).

Moving: the zero-moment point

When the CoM accelerates, inertia adds to gravity. The point on the ground where the net moment is zero is the zero-moment point. For a point mass at height z moving horizontally with acceleration a (the “cart-table” simplification), balancing moments about the ground point p gives p = x − (z/g)·a. The robot is balanced while this point stays inside the support polygon. Check the formula against a direct moment calculation:

g, z = 9.81, 0.9                       # gravity; height of the centre of mass in metres
mass = 40.0
x = 0.0                                # horizontal position of the CoM, above the ankle

def zmp_formula(a):
    return x - (z / g) * a

def zmp_from_moments(a):
    # moment balance about a ground point p: gravity acts at x, the inertial force (-m a) acts at height z
    # (m g)(x - p) - (m a) z = 0  ->  p = x - a z / g
    p = x - (mass * a * z) / (mass * g)
    return p

for a in (0.0, 0.5, 1.0, 2.0, -1.0):
    print(f"acceleration {a:5.1f} m/s^2 -> ZMP at {zmp_formula(a) * 100:6.1f} cm, direct moment check {zmp_from_moments(a) * 100:6.1f} cm")

Output:

acceleration   0.0 m/s^2 -> ZMP at    0.0 cm, direct moment check    0.0 cm
acceleration   0.5 m/s^2 -> ZMP at   -4.6 cm, direct moment check   -4.6 cm
acceleration   1.0 m/s^2 -> ZMP at   -9.2 cm, direct moment check   -9.2 cm
acceleration   2.0 m/s^2 -> ZMP at  -18.3 cm, direct moment check  -18.3 cm
acceleration  -1.0 m/s^2 -> ZMP at    9.2 cm, direct moment check    9.2 cm

Accelerating forward moves the ZMP backward (at 1 m/s² it is 9 cm behind the CoM). If the foot extends only 8 cm behind the ankle, that acceleration is already too much. Speed up, stop, or swing a heavy arm, and the point moves; it must stay inside the polygon or the robot starts to rotate about an edge of the foot.

Turning the rule into an action limit

The inequality gives a limit on what the policy may ask: the largest forward acceleration is the one that puts the ZMP at the back edge of the foot, and the largest backward acceleration puts it at the toe. A filter between the policy and the body can clip requests to that range:

g, z = 9.81, 0.9
front, back = 0.12, 0.08            # metres of foot ahead of and behind the CoM (assumed)

a_max_forward = back * g / z        # ZMP = x - (z/g) a = x - back  ->  a = back g / z
a_max_backward = front * g / z
print(f"largest forward acceleration:  {a_max_forward:.2f} m/s^2")
print(f"largest backward acceleration: {a_max_backward:.2f} m/s^2")

def limit(requested):
    return max(-a_max_backward, min(a_max_forward, requested))

for requested in (0.3, 0.9, 2.0, -3.0):
    print(f"policy asks {requested:5.2f} m/s^2 -> allowed {limit(requested):5.2f} m/s^2")

Output:

largest forward acceleration:  0.87 m/s^2
largest backward acceleration: 1.31 m/s^2
policy asks  0.30 m/s^2 -> allowed  0.30 m/s^2
policy asks  0.90 m/s^2 -> allowed  0.87 m/s^2
policy asks  2.00 m/s^2 -> allowed  0.87 m/s^2
policy asks -3.00 m/s^2 -> allowed -1.31 m/s^2

Toy model. A planar point-mass model with assumed foot dimensions. Real humanoids use multi-contact models and measured contact forces.

This is an example of an embodiment-specific rule: it depends on this robot's mass distribution and foot shape, so it cannot be learned from another robot's data or taken from a generic policy. Keep such rules in the adapter layer, tested and documented (lesson 9.2).

Contacts: not slipping

A foot, or a fingertip, grips only while the force along the surface stays below friction times the force pressing into it: |Ft| ≤ μ·Fn, the friction cone. A policy that asks for a large horizontal push on a slippery floor makes the foot slide:

import numpy as np

weight = 40.0 * 9.81
mu = {"rubber on dry floor": 0.8, "plastic on wet floor": 0.2}
for surface, m in mu.items():
    max_horizontal = m * weight / 2                    # one foot carries half the weight
    for needed in (60.0, 120.0):
        print(f"{surface:<22} needs {needed:5.1f} N horizontal -> limit {max_horizontal:5.1f} N -> {'holds' if needed <= max_horizontal else 'SLIPS'}")

Output:

rubber on dry floor    needs  60.0 N horizontal -> limit 157.0 N -> holds
rubber on dry floor    needs 120.0 N horizontal -> limit 157.0 N -> holds
plastic on wet floor   needs  60.0 N horizontal -> limit  39.2 N -> SLIPS
plastic on wet floor   needs 120.0 N horizontal -> limit  39.2 N -> SLIPS

The same command holds on one floor and slips on another, which is why robots estimate contact forces and why policies trained on dry-floor data can fail on a wet floor: a distribution shift (lesson 3.5) that nobody saw coming.

Common misconceptions

Misconception 1 “Static stability is enough.”

Correction Accelerating shifts the zero-moment point backward from the CoM.

Misconception 2 “Contact is binary.”

Correction A foot holds only within the friction cone.

Misconception 3 “The policy will learn the physics.”

Correction It may approximate it on data; the limits still need to be enforced.

Engineering takeaways

  1. CoM must project into the support polygon.
  2. ZMP = x − (z/g)a (point-mass model).
  3. Limits from foot size.
  4. Friction: |Ft| ≤ μ Fn.

Keep the weight over the feet and the feet on the floor.

Knowledge check

Try each question first, then reveal the explanation.

Question 1

A CoM at 0.8 m height accelerates forward at 1.962 m/s². Where is the ZMP relative to the CoM?

Explanation

16 cm behind it. 0.8/9.81 × 1.962.

Question 2

Friction coefficient 0.5, normal force 200 N. Maximum tangential force?

Explanation

100 N.

Question 3

Why must these limits be robot-specific?

Explanation

They depend on this robot’s mass, foot shape and contacts.

References

Numbered notes in the text, like [1], point here. Each entry says what it is cited for and how that was checked. Papers and books can be revised or misread, so follow the links before relying on a detail. Code marked “Toy model” is our own illustration of an idea, not the code of the cited system.

  1. Vukobratović, M. and Borovac, B. (2004). Zero-moment point: thirty five years of its life. International Journal of Humanoid Robotics 1(1), 157–173. doi:10.1142/S0219843604000083

    Cited for:

    • Reviews the zero-moment point (ZMP) concept, explicitly introduced thirty-three years before the paper and first practically demonstrated in 1984 at Waseda University on the dynamically balanced robot WL-10RD, with attention to delicate issues that can cause confusion when ZMP is applied mechanistically, for example when a humanoid loses dynamic balance. Checked against the paper’s abstract.

What’s next

Last in this phase: an integration case study on the Unitree G1.

Next lesson

10.7 — Integrating a VLA with a Unitree G1 humanoid

An integration case study on the Unitree G1.

Back to the VLA roadmap