Nimbin[12]?Games / Entropie / entropie/src/people/body.c

Entropie git · main

Entropie festival game; first steps

sdl c game entropie cmake · first commit 2026-10-10 · last commit 2026-11-02 (18 hours ago) · synced 10 hours ago

C 88.5% Markdown 9.1% GLSL 2.3%
git clone https://git.christianimmanuel.de/games/Entropie.gitwget https://git.christianimmanuel.de/games/Entropie/archive/Entropie.tar.gz
entropie/src/people/body.c 76.6 KB · 1490 lines raw
/*
 * The body over time: feet that step, falls, moods and fidgets.
 * Body_update moves everything along; body_pose.c turns it into a skeleton.
 */

#include "people/body_internal.h"

static const float FIDGET_LENGTH[FIDGET_COUNT] = { 0.0f, 1.6f, 2.4f, 2.6f, 2.0f, 3.0f, 6.0f, 2.8f, 2.4f, 5.0f, 5.0f, 5.5f, 5.0f };

static Vec3 clearSpot(const BodyFrame *f, Foot *foot, Vec3 spot, Vec3 other, float reach, bool sloppy, float s);
static Vec3 leapPast(const BodyFrame *f, Foot *foot, Vec3 aim, Vec3 other, float reach, float s);
static void crossOver(Body *b, const BodyFrame *f, float s);
#define KNEE_ROOM   0.35f  /* a foot this far back from anything higher than ... */
#define KNEE_HIGH   0.5f   /* ... this (scaled): room for the knee */
#define CROSS_OVER  0.12f  /* a foot lifted this much over something ... */
#define CROSS_FROM  0.15f  /* ... and this far into its step: half way over it */
#define CROSS_PAST  0.2f   /* the body goes no further than this past where that foot came down */
#define CROSS_DONE  0.06f  /* over: the body this close behind where that foot came down (or past it) */
#define CROSS_AWAY  0.75f  /* the body this far back from it: not going over after all */

static float random01(Body *b) {
    if (b->rng == 0) b->rng = 0x9E3779B9u ^ (unsigned)(b->clock * 7919.0f);
    b->rng ^= b->rng << 13;
    b->rng ^= b->rng >> 17;
    b->rng ^= b->rng << 5;
    return (float)(b->rng & 0xffffffu) / 16777215.0f;
}

static Vec3 rightOf(Vec3 facing) { return vec3_normalize(vec3_cross(facing, UP)); }

/* Turns a horizontal direction towards its right by angle (radians). */
static Vec3 turnRight(Vec3 dir, float angle) {
    return vec3_add(vec3_scale(dir, cosf(angle)), vec3_scale(rightOf(dir), sinf(angle)));
}

/* Step length grows with speed: short steps when slow, long strides when
   running (about 2.9 steps/s at 3 m/s, 4 steps/s at 6.5 m/s). */
static float stepLength(float speed, float s) {
    return clampf(0.3f + 0.27f * speed, 0.35f, 1.8f) * s; /* (a brisk walk: long steps, not a shuffle; a sprint: long strides) */
}

/* How far from the other foot a step over something may land: walking a
   careful step, running a leap. */
static float overReach(const Body *b, float s) {
    return fmaxf(OVER_STRIDE * s, OVER_LEAP * b->step_length);
}

/* Where a foot should stand: beside the body, half a step ahead when moving. */
/* The ground a foot can stand on around the body: no further down than a
   leg reaches (over an edge the foot stays up; the body falls if it must). */
static float groundUnder(const BodyFrame *f, float x, float z, float s) {
    return fmaxf(f->ground(f->ground_ctx, x, z, f->position.y + STEP_UP * s), f->position.y - MAX_DROP * s);
}

static Vec3 footSpot(const Body *b, const BodyFrame *f, int side, float s) {
    float sign = side == 0 ? -1.0f : 1.0f;
    float width = FOOT_X * (1.0f + 0.12f * proudness(b) - 0.2f * shyness(b) + 0.15f * b->style[0]); /* (their own way) */
    Vec3 spot = vec3_add(f->position, vec3_scale(rightOf(f->facing), sign * width * s));
    spot = vec3_add(spot, vec3_scale(f->facing, b->stand_ahead * s)); /* (in front of a seat) */

    Vec3 v = flat(b->velocity);
    float speed = vec3_length(v);
    if (speed > 0.05f) /* half a step ahead, but never further than a leg can land */
        spot = vec3_add(spot, vec3_scale(v, fminf(0.5f * b->step_length, MAX_LEAD * (1.0f + 0.45f * runAmount(speed)) * s) / speed)); /* (running: reaching out further) */

    if (b->unsteadiness > 0.0f) {
        /* Drunk: steps land a bit off */
        float n = sinf(b->clock * 2.3f + (float)side * 1.7f) * cosf(b->clock * 1.1f);
        spot = vec3_add(spot, vec3_scale(rightOf(f->facing), n * 0.15f * b->unsteadiness * s));
    }

    /* Standing about: one foot a little ahead, or the feet wider apart, now and then another way */
    float still = 1.0f - smoothstepf(0.05f, 0.3f, speed);
    if (still > 0.0f) {
        Vec3 st = b->stance[side];
        spot = vec3_add(spot, vec3_scale(vec3_add(vec3_scale(rightOf(f->facing), st.x), vec3_scale(f->facing, st.z)), still * s));
    }

    spot.y = groundUnder(f, spot.x, spot.z, s);
    /* Something higher than a knee just ahead (a wall, a ledge): the foot stays back
       far enough for the knee (it bends forward) */
    for (float d = 0.04f * s; d <= KNEE_ROOM * s; d += 0.04f * s) {
        Vec3 q = vec3_add(spot, vec3_scale(f->facing, d));
        if (f->ground(f->ground_ctx, q.x, q.z, spot.y + 1.2f * s) - spot.y > KNEE_HIGH * s) {
            spot = vec3_sub(spot, vec3_scale(f->facing, KNEE_ROOM * s - d + 0.04f * s));
            spot.y = groundUnder(f, spot.x, spot.z, s);
            break;
        }
    }
    return spot;
}

/* Direction a planted foot points: a little outwards, inwards when shy. */
static Vec3 footDir(const Body *b, Vec3 facing, int side) {
    float sign = side == 0 ? -1.0f : 1.0f;
    return turnRight(facing, sign * (TOE_OUT + 0.04f * b->style[1] - 0.42f * shyness(b)));
}

/* A foot's spot may not cross over to the other foot's side. */
static Vec3 uncrossed(Vec3 spot, const BodyFrame *f, int side, Vec3 other, float s) {
    Vec3 right = rightOf(f->facing);
    float sign = side == 0 ? -1.0f : 1.0f;
    float across = vec3_dot(vec3_sub(spot, other), right) * sign; /* > 0: on its own side */
    if (across < FOOT_GAP * s) {
        spot = vec3_add(spot, vec3_scale(right, sign * (FOOT_GAP * s - across)));
        spot.y = groundUnder(f, spot.x, spot.z, s);
    }
    return spot;
}

static Vec3 ankleAt(Vec3 ground_spot, float s) {
    return vec3_add(ground_spot, vec3(0.0f, ANKLE_Y * s, 0.0f));
}

/* Toe on the ground in front of the ankle, so feet follow slopes. */
static Vec3 toePlanted(const BodyFrame *f, Vec3 ankle, Vec3 dir, float s) {
    Vec3 tip = vec3_add(ankle, vec3_scale(dir, FOOT_LENGTH * s));
    tip.y = f->ground(f->ground_ctx, tip.x, tip.z, ankle.y + 0.1f * s) + 0.02f * s;
    tip.y = fmaxf(tip.y, ankle.y - 0.14f * s); /* toes over an edge stay level */
    return tip;
}

/* "Flat" is the way a planted foot points: the ankle sits higher than the toes. */
#define FLAT_PITCH (-atan2f(ANKLE_Y - 0.02f, FOOT_LENGTH))

/* How high a stepping foot is (0..1) through the step: highest early on
   (sooner the smaller early is), and it leaves and meets the ground gently. */
static float stepArc(float step, float early) {
    return sinf(PI_F * powf(step, early)) * smoothstepf(0.0f, 0.3f, step) * (1.0f - smoothstepf(0.8f, 1.0f, step));
}

/* How far the toes are up when the heel lands (walking; running lands flatter). */
static float heelStrike(float run) { return 0.36f * (1.0f - run); }

/* Swinging foot: it leaves the ground tilted as it was (on the toes), hangs,
   and the toes come up before the heel lands. */
static Vec3 toeSwinging(const BodyFrame *f, Vec3 ankle, Vec3 dir, float step, float pitch0, float strike, float s) {
    float swing = FLAT_PITCH - 0.25f * (1.0f - smoothstepf(0.2f, 0.6f, step)) + strike * smoothstepf(0.45f, 0.95f, step);
    float pitch = lerpf(pitch0, swing, smoothstepf(0.0f, 0.4f, step));
    Vec3 along = vec3_add(vec3_scale(dir, cosf(pitch)), vec3_scale(UP, sinf(pitch)));
    Vec3 tip = vec3_add(ankle, vec3_scale(along, FOOT_LENGTH * s));
    float ground = f->ground(f->ground_ctx, tip.x, tip.z, ankle.y + 0.1f * s) + 0.02f * s;
    if (ankle.y > ground) tip.y = fmaxf(tip.y, ground); /* (coming up from below an edge: no clamping) */
    return tip;
}

static void plantAll(Body *b, const BodyFrame *f, float s) {
    b->root_ok = false; /* (moved elsewhere: the hips are drawn right there) */
    for (int i = 0; i < 2; i++) {
        Foot *foot = &b->feet[i];
        foot->ankle = ankleAt(footSpot(b, f, i, s), s);
        foot->dir = footDir(b, f->facing, i);
        foot->toe = toePlanted(f, foot->ankle, foot->dir, s);
        foot->stepping = false;
    }
}

/* ---------- Falling down, lying, getting up ---------- */

#define LIE_DRAPE     0.5f   /* a falling body can come to lie across things this high (m above the feet) */
#define LIE_TURN_COST 1.2f   /* (per radian off the way the fall wanted to go) */

/* How bad it would be to come to lie this way: across something too high is
   out of the question, lumps under the body are uncomfortable. */
static float lieCost(const Body *b, Vec3 dir, float reach) {
    const float s = b->at_scale;
    const Vec3 feet = b->at, side = rightOf(dir);
    float cost = 0.0f, last = 0.0f;
    for (int k = 0; k <= 12; k++) {
        float along = reach - 0.9f + 0.15f * (float)k; /* from the feet end to the head end (unscaled) */
        for (int w = -1; w <= 1; w++) {
            if (w && (k < 5 || k > 10)) continue; /* (the shoulders and hips are wide) */
            Vec3 p = vec3_add(feet, vec3_add(vec3_scale(dir, along * s), vec3_scale(side, 0.2f * s * (float)w)));
            float h = b->ground(b->clutter_ctx, p.x, p.z, feet.y + 2.5f * s) - feet.y;
            if (h > LIE_DRAPE * s) cost += 20.0f + 20.0f * h;
            if (!w) {
                if (k) cost += 2.0f * fabsf(h - last);
                last = h;
            }
        }
    }
    return cost;
}

/* Where the body comes down: the way it was going if there's room, else a bit
   to the side, or shorter (crumpling at the foot of a wall), never into or
   over something high. */
static void chooseLanding(Body *b, float most_turn) {
    if (!b->ground || b->at_scale <= 0.0f) return;
    static const float TURNS[] = { 0.0f, 0.35f, -0.35f, 0.7f, -0.7f, 1.1f, -1.1f, 1.6f, -1.6f, 2.3f, -2.3f, 3.14f };
    static const float REACHES[] = { 1.0f, 0.7f, 0.45f };
    float best = 1e9f, best_turn = 0.0f, best_reach = 1.0f;
    for (unsigned t = 0; t < sizeof TURNS / sizeof TURNS[0]; t++) {
        if (fabsf(TURNS[t]) > most_turn) continue;
        for (unsigned r = 0; r < sizeof REACHES / sizeof REACHES[0]; r++) {
            float reach = b->lie_offset * REACHES[r];
            float cost = lieCost(b, turnRight(b->fall_dir, TURNS[t]), reach) + LIE_TURN_COST * fabsf(TURNS[t]) +
                         2.0f * (1.0f - REACHES[r]);
            if (cost < best) {
                best = cost;
                best_turn = TURNS[t];
                best_reach = reach;
            }
        }
        if (best < 0.3f) break; /* (good enough: the way it was going) */
    }
    b->fall_dir = turnRight(b->fall_dir, best_turn);
    b->lie_offset = best_reach;
}

void Body_fall(Body *b, Vec3 dir) {
    if (b->pose != POSE_UP) return;
    dir = flat(dir);
    if (vec3_length(dir) < 1e-4f) dir = b->last_facing;
    /* No two falls alike: straight like a plank, knees giving way first, or
       turning on the way down; a bit off the push, a bit faster or slower */
    float r = random01(b);
    b->fall_style = b->unsteadiness > 0.3f && r < 0.5f ? FALL_CRUMPLE : (r < 0.35f ? FALL_PLANK : (r < 0.7f ? FALL_TWIST : FALL_CRUMPLE));
    b->fall_twist = b->fall_style == FALL_TWIST ? (random01(b) - 0.5f) * 1.8f : (random01(b) - 0.5f) * 0.4f;
    b->fall_time  = FALL_TIME * (0.8f + 0.5f * random01(b)) * (b->fall_style == FALL_CRUMPLE ? 1.3f : 1.0f);
    dir = flat(dir);
    if (vec3_length(dir) < 1e-3f) dir = vec3_length(b->last_facing) > 1e-3f ? b->last_facing : vec3(0.0f, 0.0f, 1.0f); /* (no way given: forward) */
    dir = turnRight(vec3_normalize(dir), (random01(b) - 0.5f) * 0.6f);
    b->fall_delay = 0.0f;
    b->pose       = POSE_FALLING;
    b->pose_time  = 0.0f;
    b->fall_dir   = vec3_normalize(dir);
    b->lie_offset = FALL_REACH;
    b->face_down  = vec3_dot(b->fall_dir, b->last_facing) > -0.3f; /* backwards: on the back */
    b->asleep     = false;
    b->down_time  = 1.8f + 1.4f * random01(b);
    b->from_knees = false;
    chooseLanding(b, 3.2f);
    b->face_down  = vec3_dot(b->fall_dir, b->last_facing) > -0.3f;
}

void Body_dropAndRoll(Body *b, Vec3 dir) {
    if (b->pose == POSE_DOWN && !b->rolling) { /* already down: roll right away */
        b->rolling = true;
        b->pose_time = 0.0f;
        b->face_down = true;
        b->down_time = fmaxf(b->down_time, ROLL_REST);
        return;
    }
    if (b->pose != POSE_UP) return;
    Body_fall(b, dir);
    b->fall_style = FALL_DIVE;
    b->fall_twist = 0.0f;
    b->fall_time  = DIVE_TIME;
    b->fall_dir   = vec3_normalize(flat(dir)); /* straight ahead, no wobble */
    b->face_down  = true;
    b->lie_offset = FALL_REACH * 1.05f;
    chooseLanding(b, 1.2f);
    b->down_time  = ROLL_REST;
    b->rolling    = true;
    b->roll       = 0.0f;
    b->roll_side  = 0.0f;
}

void Body_rollOnto(Body *b, Vec3 along, float side) {
    if (b->pose != POSE_UP) return;
    along = flat(along);
    b->pose       = POSE_FALLING;
    b->pose_time  = 0.0f;
    b->fall_dir   = vec3_length(along) > 1e-3f ? vec3_normalize(along) : b->last_facing;
    b->lie_offset = 0.0f;  /* (the hips where it is: on the edge) */
    b->face_down  = true;
    b->asleep     = false;
    b->from_knees = true;
    b->fall_style = FALL_PLANK;
    b->fall_twist = 0.0f;
    b->fall_time  = 0.45f; /* (down onto it, not a fall) */
    b->down_time  = ROLL_OVER_TIME + 0.5f;
    b->rolling    = false;
    b->roll = b->roll_side = 0.0f;
    b->roll_over  = side >= 0.0f ? 1.0f : -1.0f;
}

#define LAND_EASY      5.0f  /* m/s down (about 1.3 m): the legs take it as they are */
#define LAND_DEEP_     7.0f  /* (2.5 m) deep into the knees, bounced off ... */
#define LAND_KNEES     8.2f  /* (3.4 m) ... the knees alone take it up to here (the sure), harder: a roll or a crash */
#define LAND_ROLL_MOST 8.0f  /* (3.3 m) a roll takes it up to this ... */
#define LAND_SPORT     1.0f  /* ... the very sporty this much more (4.1 m) */
#define LAND_BREAK     10.5f /* (5.6 m) landing this hard, something gives (the worst) */
#define ROLL_FORWARD   1.2f  /* m/s along: a roll needs going somewhere */

int Body_land(Body *b, Vec3 dir, float speed, float forward, float sure, float sporty, float *bad) {
    *bad = 0.0f;
    if (b->pose != POSE_UP || speed < LAND_EASY) return LAND_SOFT;
    /* first always into the knees: bounce it off */
    float hard = (speed - LAND_EASY) / (LAND_DEEP_ - LAND_EASY); /* 0 .. 1 at a deep landing */
    b->land_squat = clampf(0.35f + 0.5f * hard, 0.0f, 1.1f);
    b->crouch_v += 3.0f + 2.0f * hard; /* (down at once, not eased) */
    /* going somewhere, not too high, and knowing how: over the shoulder, and up again */
    float limit = LAND_ROLL_MOST + LAND_SPORT * sporty;
    if (speed >= LAND_DEEP_ - 0.5f && forward >= ROLL_FORWARD && speed < limit && random01(b) < sure) {
        float lateral = vec3_dot(flat(dir), rightOf(b->last_facing)); /* (the shoulder on the side the way goes, else their own) */
        float side = fabsf(lateral) > 0.2f ? (lateral > 0.0f ? 1.0f : -1.0f) : (b->quirk[0] > 0.0f ? 1.0f : -1.0f);
        Body_shoulderRoll(b, dir, side);
        *bad = 0.05f * clampf((speed - LAND_DEEP_) / (limit - LAND_DEEP_), 0.0f, 1.0f);
        return LAND_ROLL;
    }
    if (speed < LAND_DEEP_) { *bad = 0.12f * hard * (1.0f - sure); return LAND_DEEP; } /* (the knees take it - an unsure one may twist an ankle) */
    float over = clampf((speed - LAND_DEEP_) / (LAND_BREAK - LAND_DEEP_), 0.0f, 1.0f);
    *bad = clampf(0.2f + 0.8f * over - 0.25f * sure, 0.05f, 1.0f);
    if (speed < LAND_KNEES && random01(b) < 0.4f + 0.5f * sure) { *bad *= 0.6f; return LAND_DEEP; } /* (deep, and still on the feet) */
    Body_fall(b, dir);
    return LAND_CRASH;
}

void Body_shoulderRoll(Body *b, Vec3 dir, float side) {
    if (b->pose != POSE_UP) return;
    Vec3 d = flat(dir);
    b->roll_dir = vec3_length(d) > 1e-3f ? vec3_normalize(d) : b->last_facing;
    b->roll_shoulder = side >= 0.0f ? 1.0f : -1.0f;
    b->shoulder_roll = 1e-4f;
    b->land_squat = 0.0f;
}

bool Body_isRolling(const Body *b) { return b->shoulder_roll > 0.0f; }

float Body_clumsiness(const Body *b) {
    return 0.5f + 0.5f * b->quirk[3];
}

/* Staggers backwards a few steps, then goes down that way. */
static void fallLater(Body *b, Vec3 dir, float delay) {
    if (b->pose != POSE_UP || b->fall_delay > 0.0f) return;
    b->fall_delay = delay;
    b->fall_pending = vec3_normalize(flat(dir));
    b->stumble = STUMBLE_TIME;
    b->landing = CATCH_STEP_TIME;
    b->stumble_push = vec3_add(b->stumble_push, vec3_scale(b->fall_pending, 1.6f));
}

void Body_trip(Body *b, int foot, Vec3 facing, float severity) {
    if (b->pose != POSE_UP || b->stumble > 0.0f || b->fall_delay > 0.0f) return;
    float side = foot == 0 ? -1.0f : 1.0f;
    Vec3 right = rightOf(facing);
    Vec3 lurch = vec3_add(vec3_scale(facing, 0.9f), vec3_scale(right, side * 0.5f));
    b->stumble = STUMBLE_TIME;
    b->landing = CATCH_STEP_TIME;
    b->stumble_push = vec3_add(b->stumble_push, vec3_scale(lurch, 0.6f + 1.2f * severity));
    /* Bad enough: down, over the foot that caught */
    if (random01(b) < (severity - 0.5f) * 0.6f)
        Body_fall(b, vec3_add(facing, vec3_scale(right, side * 0.7f)));
}

/* ---------- Smoking ---------- */

#define CIG_TIME      200.0f  /* seconds it burns (a bit quicker with each drag) */
#define EXHALE_TIME   1.4f

bool Body_smoke(Body *b, bool rolled, int hand) {
    if (b->smoke_phase != SMOKE_NONE || b->pose != POSE_UP) return false;
    b->smoke_phase = rolled ? SMOKE_ROLL : SMOKE_LIGHT;
    b->smoke_t = 0.0f;
    b->cig_left = 1.0f;
    b->cig_hand = hand;
    b->drag = b->exhale = 0.0f;
    b->drag_timer = 4.0f;
    return true;
}

bool Body_isSmoking(const Body *b) {
    return b->smoke_phase != SMOKE_NONE;
}

void Body_stopSmoking(Body *b) {
    if (b->smoke_phase == SMOKE_NONE) return;
    b->butt = b->smoke_phase == SMOKE_ON; /* (half rolled: just put away) */
    b->smoke_phase = SMOKE_NONE;
    b->drag = b->exhale = 0.0f;
}

bool Body_takeButt(Body *b) {
    bool butt = b->butt;
    b->butt = false;
    return butt;
}

static void updateSmoking(Body *b, float speed, float dt) {
    if (b->smoke_phase == SMOKE_NONE) return;
    if (b->pose != POSE_UP) { Body_stopSmoking(b); return; } /* (down: out of the hand) */
    b->smoke_t += dt;
    b->exhale = fmaxf(0.0f, b->exhale - dt);
    if (b->smoke_phase == SMOKE_ROLL) {
        if (b->hand_weight[0] > 0.5f || b->hand_weight[1] > 0.5f || speed > 1.0f) { b->smoke_t = fminf(b->smoke_t, 0.5f); return; } /* (hands needed: rolling waits) */
        if (b->smoke_t >= ROLL_CIG_TIME) { b->smoke_phase = SMOKE_LIGHT; b->smoke_t = 0.0f; }
        return;
    }
    if (b->smoke_phase == SMOKE_LIGHT) {
        if (b->smoke_t >= LIGHT_TIME) { b->smoke_phase = SMOKE_ON; b->smoke_t = 0.0f; b->exhale = EXHALE_TIME; }
        return;
    }
    float hold = speed < 0.5f && b->cig_hand < 2 ? 1.0f : 0.0f; /* (standing about: up in front; walking: it swings along) */
    b->cig_hold += clampf(hold - b->cig_hold, -2.5f * dt, 2.5f * dt);
    /* In a hand that's needed for something: into the mouth; back to a free hand after a drag */
    if (b->cig_hand < 2 && b->hand_weight[b->cig_hand] > 0.3f) b->cig_hand = 2;
    b->cig_left -= dt / CIG_TIME * (b->drag > 0.0f ? 4.0f : 1.0f);
    if (b->drag > 0.0f && (b->drag -= dt) <= 0.0f) {
        b->drag = 0.0f;
        b->exhale = EXHALE_TIME;
        if (b->cig_hand == 2)
            for (int h = 1; h >= 0; h--) if (b->hand_weight[h] < 0.05f) { b->cig_hand = h; break; }
    }
    if (b->drag <= 0.0f && (b->drag_timer -= dt) <= 0.0f) {
        b->drag = DRAG_TIME;
        b->drag_timer = 8.0f + 12.0f * random01(b) - 4.0f * b->shown.nerves; /* (nervous: more often) */
    }
    if (b->cig_left <= 0.0f) { b->butt = true; b->smoke_phase = SMOKE_NONE; b->drag = 0.0f; }
}

bool Body_takeLanding(Body *b, int *foot, Vec3 *at) {
    if (!b->landed) return false;
    *foot = b->landed - 1;
    *at = b->landed_at;
    b->landed = 0;
    return true;
}

void Body_stagger(Body *b, Vec3 push) {
    if (b->pose != POSE_UP) return;
    b->stumble = STUMBLE_TIME;
    b->landing = CATCH_STEP_TIME;
    b->stumble_push = vec3_add(b->stumble_push, push);
}

void Body_lieDown(Body *b) {
    b->asleep = true;
    b->down_time = -1.0f;
    if (b->pose != POSE_UP) return; /* already on the ground: just stay there */
    b->pose       = POSE_LYING_DOWN;
    b->pose_time  = 0.0f;
    b->fall_dir   = vec3_scale(b->last_facing, -1.0f); /* sits down, then lies back */
    if (vec3_length(b->fall_dir) < 1e-3f) b->fall_dir = vec3(0.0f, 0.0f, -1.0f); /* (never faced anywhere yet) */
    b->lie_offset = 0.0f;
    b->face_down  = false;
}

void Body_stretch(Body *b) {
    b->fidget = FIDGET_STRETCH;
    b->fidget_side = 0;
    b->fidget_len = b->fidget_time = FIDGET_LENGTH[FIDGET_STRETCH] * 1.3f;
    b->fidget_fade = 1.0f;
    b->fidget_timer = b->fidget_len + 2.0f;
}

void Body_getUp(Body *b) {
    b->asleep = false;
    if (b->pose != POSE_UP) b->down_time = 0.0f; /* still going down: up again right after */
}

bool Body_isDown(const Body *b) {
    return b->pose != POSE_UP;
}

void Body_burp(Body *b) {
    if (b->burp <= 0.0f && b->pose == POSE_UP) b->burp = BURP_TIME;
}

void Body_vomit(Body *b) {
    if (b->vomit <= 0.0f && b->pose == POSE_UP) b->vomit = VOMIT_TIME;
}

bool Body_takeVomit(Body *b) {
    bool splash = b->vomit_splash;
    b->vomit_splash = false;
    return splash;
}

bool Body_isBusy(const Body *b) {
    return b->pose != POSE_UP || b->vomit > 0.0f || b->pee > 0.0f;
}

void Body_pee(Body *b, float seconds) {
    if (b->pee <= 0.0f && b->pose == POSE_UP) b->pee = b->pee_total = fmaxf(seconds, 1.0f);
}

bool Body_takePee(Body *b) {
    bool done = b->pee_done;
    b->pee_done = false;
    return done;
}

Vec3 Body_takeShift(Body *b) {
    Vec3 shift = b->shift;
    b->shift = vec3_zero();
    return shift;
}

/* Advances falling / lying / getting up. Returns false once standing again. */
static bool updateDown(Body *b, const BodyFrame *f, float s) {
    if (b->pose == POSE_UP) return false;
    b->pose_time += f->dt;
    b->velocity = vec3_zero();
    for (int i = 0; i < 2; i++) b->feet[i].stepping = false;

    /* Feel the ground under the body, so it lies along the bumps */
    Vec3 hips = vec3_add(f->position, vec3_scale(b->fall_dir, b->lie_offset * s));
    for (int k = 0; k < LIE_SAMPLES; k++) {
        Vec3 p = vec3_add(hips, vec3_scale(b->fall_dir, ((float)k - 4.0f) * LIE_STEP));
        b->lie_ground[k] = f->ground(f->ground_ctx, p.x, p.z, f->position.y + 1.0f) - f->position.y;
    }

    switch (b->pose) {
    case POSE_FALLING:
        b->stumble = STUMBLE_TIME * fmaxf(0.0f, 1.0f - b->pose_time / b->fall_time); /* arms out */
        if (b->pose_time >= b->fall_time) { b->pose = POSE_DOWN; b->pose_time = 0.0f; b->stumble = 0.0f; }
        break;
    case POSE_LYING_DOWN:
        if (b->pose_time >= LIE_DOWN_TIME) { b->pose = POSE_DOWN; b->pose_time = 0.0f; }
        break;
    case POSE_DOWN:
        if (b->roll_over != 0.0f) { /* over once, face down onto the back, moving sideways onto it like a log */
            float k = fminf(1.0f, b->pose_time / ROLL_OVER_TIME);
            b->rolling = true;
            b->roll = b->roll_over * PI_F * smoothstepf(0.0f, 1.0f, k);
            b->roll_side = b->roll * ROLL_RADIUS;
            if (k >= 1.0f) { /* on the back now: lying where the roll ended */
                b->shift = vec3_add(b->shift, vec3_scale(vec3_normalize(vec3_cross(b->fall_dir, UP)), b->roll_side * s));
                b->face_down = false;
                b->rolling = false;
                b->roll = b->roll_side = b->roll_over = 0.0f;
                b->down_time = 0.35f;
            }
            break;
        }
        if (b->rolling && b->pose_time < ROLL_TIME) { /* side to side, the whole body turning about its length */
            b->roll = ROLL_REACH * sinf(b->pose_time * ROLL_PACE);
            b->roll_side = b->roll * ROLL_RADIUS;
            break;
        }
        if (b->rolling) b->roll = b->roll_side = 0.0f;
        /* Wanting to move on gets you up sooner */
        if (b->down_time >= 0.0f && (b->down_time -= f->dt * (1.0f + 2.5f * b->urge)) <= 0.0f) {
            /* Get up where the hips lie */
            b->pose = POSE_GETTING_UP;
            b->pose_time = 0.0f;
            b->shift = vec3_scale(b->fall_dir, b->lie_offset * s);
            if (b->rolling) /* up where the roll ended */
                b->shift = vec3_add(b->shift, vec3_scale(vec3_normalize(vec3_cross(b->fall_dir, UP)), b->roll_side * s));
            b->lie_offset = 0.0f;
            b->replant = true;
            /* Drunk or clumsy: getting up may go wrong */
            float u = b->unsteadiness, clumsy = 0.5f + 0.5f * b->quirk[3];  /* 0..1 */
            float fail = clampf((u - 0.55f) * 0.6f, 0.0f, 0.4f);
            float wobble = clampf(0.9f * u + 0.12f * clumsy, 0.0f, 0.9f);
            float r = random01(b);
            b->trouble = r < fail ? 2 : (r < fail + (1.0f - fail) * wobble ? 1 : 0);
        }
        break;
    case POSE_GETTING_UP:
        b->pose_time += f->dt * 0.8f * b->urge; /* quicker when moving on */
        if (b->trouble == 2 && b->pose_time >= 0.55f * GET_UP_TIME) {
            /* Halfway up, the legs give way: back down for a moment */
            float side = random01(b) < 0.5f ? -1.0f : 1.0f;
            Vec3 dir = vec3_add(vec3_scale(rightOf(b->last_facing), side), vec3_scale(b->last_facing, -0.4f));
            b->pose = POSE_FALLING;
            b->pose_time = 0.0f;
            b->fall_dir = vec3_normalize(dir);
            b->lie_offset = FALL_REACH * 0.6f;
            b->face_down = false;
            b->from_knees = true;
            b->fall_style = FALL_PLANK;
            b->fall_twist = 0.0f;
            b->fall_time = FALL_TIME;
            chooseLanding(b, 3.2f);
            b->down_time = 0.8f + 0.8f * random01(b);
            b->trouble = 0;
            break;
        }
        if (b->pose_time >= GET_UP_TIME) {
            b->pose = POSE_UP;
            b->from_knees = false;
            b->rolling = false;
            b->roll = b->roll_side = 0.0f;
            if (b->trouble == 1) {
                /* Up, but not steady yet: a few catching steps */
                float side = random01(b) < 0.5f ? -1.0f : 1.0f;
                Vec3 lurch = vec3_add(vec3_scale(rightOf(b->last_facing), side), vec3_scale(b->last_facing, 0.5f));
                Body_stagger(b, vec3_scale(vec3_normalize(lurch), 0.8f + 1.4f * b->unsteadiness));
            }
            b->trouble = 0;
        }
        break;
    case POSE_UP:
        break;
    }
    return true;
}

/* Room to stand here: nothing under the heel, and room for the toes (which the foot rolls onto). */
static bool spotFree(const BodyFrame *f, Vec3 spot, float s) {
    Vec3 toes = vec3_add(spot, vec3_scale(f->facing, 0.13f * s));
    return f->clutter(f->ground_ctx, spot.x, spot.z, 0.12f * s, f->position.y) <= 0.0f && f->clutter(f->ground_ctx, toes.x, toes.z, 0.08f * s, f->position.y) <= 0.0f;
}

/* The highest thing lying along the way from a to b, a foot's width either side (0 = nothing).
   (The first bit is skipped: whatever the foot is just leaving.) */
static float clutterAlong(const BodyFrame *f, Vec3 a, Vec3 b, float r, float skip) {
    float len = vec3_length(flat(vec3_sub(b, a))), most = 0.0f;
    int n = (int)(len / r) + 1;
    if (n > 8) n = 8;
    for (int k = 0; k <= n; k++) {
        float t = (float)k / (float)n;
        if (t * len < skip) continue;
        Vec3 p = vec3_lerp(a, b, t);
        most = fmaxf(most, f->clutter(f->ground_ctx, p.x, p.z, r, f->position.y));
    }
    return most;
}

/* How deep a leg standing on this ankle would reach into what lies about (0:
   clear): the sole (heel to toes), the shin up to the knee (bent forward, as
   the pose bends it) and the thigh up to the hip, a limb's thickness round
   them. lead: the body moved on this far; hip_y: the hips' height. */
static float legInto(const BodyFrame *f, Vec3 ankle, Vec3 dir, int side, Vec3 lead, float hip_y, float s) {
    Vec3 hip = vec3_add(vec3_add(f->position, lead), vec3_add(vec3(0.0f, hip_y, 0.0f),
                                                             vec3_scale(rightOf(f->facing), (side == 0 ? -HIP_X : HIP_X) * s)));
    /* the knee: two bones from ankle to hip, bending forward */
    Vec3 line = vec3_sub(hip, ankle);
    float d = fmaxf(vec3_length(line), 1e-4f), shin = SHIN * s, thigh = THIGH * s;
    line = vec3_scale(line, 1.0f / d);
    Vec3 knee;
    if (d >= shin + thigh) {
        knee = vec3_add(ankle, vec3_scale(line, shin));
    } else {
        float a = (shin * shin - thigh * thigh + d * d) / (2.0f * d);
        float h = sqrtf(fmaxf(shin * shin - a * a, 0.0f));
        Vec3 fwd = vec3_sub(f->facing, vec3_scale(line, vec3_dot(f->facing, line)));
        fwd = vec3_length(fwd) > 1e-3f ? vec3_normalize(fwd) : UP;
        knee = vec3_add(ankle, vec3_add(vec3_scale(line, a), vec3_scale(fwd, h)));
    }
    const float sole = ankle.y - ANKLE_Y * s;
    float most = 0.0f;
    for (int k = 0; k < 9; k++) {
        Vec3 p;
        float r, bottom;
        if (k < 3) { /* heel, ankle, toes */
            p = vec3_add(ankle, vec3_scale(dir, (k == 0 ? -0.05f : k == 1 ? 0.05f : FOOT_LENGTH) * s));
            r = 0.05f * s;
            bottom = sole;
        } else {     /* up the shin, and the lower thigh */
            p = k < 7 ? vec3_lerp(ankle, knee, 0.25f * (float)(k - 2)) : vec3_lerp(knee, hip, 0.2f * (float)(k - 6));
            r = LIMB_R * s;
            bottom = p.y - LIMB_R * s;
        }
        float q = f->clutter(f->ground_ctx, p.x, p.z, r, bottom - 0.05f); /* (whatever reaches above it here) */
        if (q > 0.0f) most = fmaxf(most, q - 0.05f);
    }
    return most;
}

/* Where a stepping foot aims, carried along as the body moves and turns; but
   a new idea of where to stand (another stance, done standing about) only
   glides over, never jumps (the foot would flick there and back). */
static Vec3 gliding(Foot *foot, Vec3 aim, const BodyFrame *f, float speed, float s, float dt) {
    Vec3 right = rightOf(f->facing);
    Vec3 rel = flat(vec3_sub(aim, f->position));
    Vec3 local = vec3(vec3_dot(rel, right), 0.0f, vec3_dot(rel, f->facing));
    Vec3 change = vec3_sub(local, foot->aim_local);
    float most = (AIM_GLIDE + 1.5f * speed) * s * dt, len = vec3_length(change);
    if (len > most) local = vec3_add(foot->aim_local, vec3_scale(change, most / len));
    foot->aim_local = local;
    Vec3 out = vec3_add(f->position, vec3_add(vec3_scale(right, local.x), vec3_scale(f->facing, local.z)));
    out.y = aim.y;
    return out;
}

/*
 * Drunk: a step may go wrong. Uneven ground and slopes make it more likely.
 * The foot lands off target, the body lurches and the next step is a quick
 * catch. Very drunk, the catch may fail and down you go.
 */
static void maybeStumble(Body *b, const BodyFrame *f, Foot *foot, float speed, float s) {
    float u = b->unsteadiness;
    if (u < 0.05f || b->stumble > 0.0f) return;

    Vec3 spot = foot->step_to;
    Vec3 ahead = vec3_add(spot, vec3_scale(f->facing, 0.3f * s));
    float ground_here  = spot.y - ANKLE_Y * s;
    float ground_ahead = f->ground(f->ground_ctx, ahead.x, ahead.z, spot.y + STEP_UP * s);
    float rough = fabsf(spot.y - foot->ankle.y) / s;          /* step up or down */
    float slope = fabsf(ground_ahead - ground_here) / (0.3f * s);
    /* Tipsy is mostly fine; really drunk is where it gets wobbly */
    float chance = u * u * (0.03f + 1.2f * rough + 0.5f * slope) * (speed > 0.4f ? 1.0f : 0.25f);
    if (random01(b) >= chance) return;

    /* The foot that goes wrong takes the body its way */
    float side = foot == &b->feet[0] ? -1.0f : 1.0f;
    Vec3 right = rightOf(f->facing);
    Vec3 miss = vec3_add(vec3_scale(right, side * (0.12f + 0.2f * u) * s),
                         vec3_scale(f->facing, (random01(b) - 0.4f) * 0.25f * s));
    spot = vec3_add(spot, miss);
    spot.y = groundUnder(f, spot.x, spot.z, s) + ANKLE_Y * s;
    foot->step_to = spot;

    b->stumble = STUMBLE_TIME;
    b->landing = CATCH_STEP_TIME;
    b->stumble_push = vec3_add(vec3_scale(right, side * (0.8f + 1.2f * u)),
                               vec3_scale(f->facing, 0.4f * u));

    if (u > 0.6f && random01(b) < (u - 0.6f) * 0.6f) {
        if (random01(b) < 0.4f) /* reeling backwards a few steps first */
            fallLater(b, vec3_add(vec3_scale(f->facing, -1.0f), vec3_scale(right, side * 0.4f)), 0.5f + 0.6f * random01(b));
        else
            Body_fall(b, vec3_add(vec3_scale(right, side), vec3_scale(f->facing, 0.6f)));
    }
}

/* Head glances, blinking, burps, idle weight shifts. */
static void updateExpression(Body *b, const BodyFrame *f, float speed) {
    const float dt = f->dt;
    b->shown.pride  = ease(b->shown.pride, b->mood.pride, EASE_MOOD, dt);
    b->shown.energy = ease(b->shown.energy, b->mood.energy, EASE_MOOD, dt);
    b->shown.nerves = ease(b->shown.nerves, b->mood.nerves, EASE_MOOD, dt);
    b->chin = ease(b->chin, b->chin_goal, EASE_POSE, dt);
    b->disgust = fmaxf(0.0f, b->disgust - 0.5f * dt);
    b->burp = fmaxf(0.0f, b->burp - dt);
    if (b->pee > 0.0f && (b->pee -= dt * (1.0f + 0.6f * b->pee_power)) <= 0.0f) { /* pushing: done sooner */
        b->pee = 0.0f;
        b->pee_done = true;
    }
    if (b->pose != POSE_UP) b->pee = 0.0f;
    if (b->vomit > 0.0f) {
        float before = VOMIT_TIME - b->vomit;
        b->vomit = fmaxf(0.0f, b->vomit - dt);
        if (before < VOMIT_SPLASH && VOMIT_TIME - b->vomit >= VOMIT_SPLASH) b->vomit_splash = true;
        if (b->pose != POSE_UP) b->vomit = 0.0f;
    }

    /* Turning: the head leads */
    Vec3 last = vec3_length(b->last_facing) > 0.5f ? b->last_facing : f->facing;
    float turned = atan2f(vec3_dot(f->facing, rightOf(last)), vec3_dot(f->facing, last));
    b->turn_rate = ease(b->turn_rate, dt > 0.0f ? turned / dt : 0.0f, 8.0f, dt);
    b->last_facing = f->facing;

    /* Looking around now and then, more often when nervous */
    if ((b->glance_timer -= dt) <= 0.0f) {
        bool ahead = random01(b) < 0.45f;
        float range = speed > 1.5f ? 0.3f : 0.75f;
        b->glance_yaw   = ahead ? 0.0f : (random01(b) * 2.0f - 1.0f) * range;
        b->glance_pitch = ahead ? 0.0f : (random01(b) * 0.4f - 0.25f) * range;
        b->glance_timer = (1.2f + 3.0f * random01(b)) * (1.0f - 0.6f * b->shown.nerves);
    }

    /* Blinking */
    b->blink = fmaxf(0.0f, b->blink - dt);
    if ((b->blink_timer -= dt) <= 0.0f) {
        b->blink = BLINK_TIME;
        b->blink_timer = (2.0f + 4.0f * random01(b)) * (1.0f - 0.5f * b->shown.nerves);
    }

    /* Standing around: weight moves from one leg to the other */
    if ((b->weight_timer -= dt) <= 0.0f) {
        b->weight_goal = random01(b) < 0.3f ? 0.0f : (random01(b) < 0.5f ? -1.0f : 1.0f);
        b->weight_timer = (4.0f + 6.0f * random01(b)) * (1.0f - 0.6f * b->shown.nerves);
    }
    float idle = 1.0f - smoothstepf(0.1f, 0.5f, speed);
    b->weight_shift = ease(b->weight_shift, b->weight_goal * idle, 1.2f, dt);

    /* Now and then one hand does something: on the hip, scratching the head,
       at the chin, in the pocket. Moving or busy hands end it. */
    b->gesture_time = fmaxf(0.0f, b->gesture_time - dt);
    bool talking = b->gesture >= GESTURE_WAVE; /* (said in passing: walking on, it goes on) */
    if (speed > (talking ? 2.5f : 0.3f) || b->pose != POSE_UP || b->hand_active[b->gesture_side]) { /* fades out from where it is */
        if ((b->gesture_fade -= dt / GESTURE_FADE) <= 0.0f) b->gesture_time = 0.0f;
    }
    if ((b->gesture_timer -= dt) <= 0.0f && b->gesture_time <= 0.0f) {
        b->gesture_timer = (5.0f + 9.0f * random01(b)) * (1.0f - 0.5f * b->shown.nerves);
        int side = random01(b) < 0.5f + 0.3f * b->quirk[0] ? 1 : 0;
        if (speed < 0.15f && b->pose == POSE_UP && !b->hand_active[side]) {
            /* which one: how they feel, and their own habits (each person has two they make most) */
            const float nerves = b->shown.nerves, tired = fmaxf(-b->shown.energy, 0.0f), lively = fmaxf(b->shown.energy, 0.0f);
            const float proud = proudness(b), shy = shyness(b);
            float weight[GESTURE_COUNT] = {
                [GESTURE_HIP]    = 0.6f + 1.5f * proud,
                [GESTURE_HEAD]   = 0.6f + 0.8f * nerves,
                [GESTURE_CHIN]   = 0.6f + 0.6f * nerves,
                [GESTURE_POCKET] = 0.8f + 0.6f * shy,
                [GESTURE_NECK]   = 0.3f + 1.2f * tired + 0.6f * shy,
                [GESTURE_NOSE]   = 0.3f + 1.2f * nerves,
                [GESTURE_EAR]    = 0.2f + 1.0f * nerves + 0.5f * shy,
                [GESTURE_HAIR]   = 0.3f + 1.0f * proud + 0.4f * lively,
                [GESTURE_THUMB]  = 0.3f + 1.0f * proud,
                [GESTURE_CHEST]  = 0.2f + 0.8f * shy,
                [GESTURE_SHADE]  = 0.2f + 0.4f * lively,
            };
            weight[b->habit[0]] *= 3.0f;
            weight[b->habit[1]] *= 2.0f;
            float total = 0.0f;
            for (int k = 1; k < GESTURE_COUNT; k++) total += fmaxf(weight[k], 0.0f);
            float r = random01(b) * total;
            int g = GESTURE_POCKET;
            for (int k = 1; k < GESTURE_COUNT; k++) {
                r -= fmaxf(weight[k], 0.0f);
                if (r <= 0.0f) { g = k; break; }
            }
            b->gesture = g;
            b->gesture_side = side;
            b->gesture_len = b->gesture_time = 2.5f + 3.0f * random01(b);
            b->gesture_fade = 1.0f;
        }
    }

    /* The whole body fidgets now and then: shakes a leg loose, rocks on the
       toes, stretches, rolls the shoulders, folds the arms. Who you are
       decides what (nervous: tapping, shaking; tired: stretching; proud:
       arms folded). Walking, working or sitting ends it. */
    bool at_ease = idle > 0.9f && b->pose == POSE_UP && b->sit < 0.05f && b->pee <= 0.0f && b->vomit <= 0.0f &&
                   !b->hand_active[0] && !b->hand_active[1] && b->crouch_goal < 0.1f && b->stumble <= 0.0f;
    b->fidget_time = fmaxf(0.0f, b->fidget_time - dt);
    if (!at_ease && (b->fidget_fade -= dt / FIDGET_FADE) <= 0.0f) b->fidget_time = 0.0f; /* (fades out from where it is) */
    if ((b->fidget_timer -= dt) <= 0.0f && b->fidget_time <= 0.0f) {
        /* (out of breath: soon after stopping, one after the other - catching it) */
        const float puffed = smoothstepf(0.35f, 0.75f, b->exertion);
        b->fidget_timer = (3.0f + 8.0f * random01(b)) * (1.0f - 0.45f * b->shown.nerves) * (1.0f - 0.75f * puffed);
        if (at_ease && b->gesture_time <= 0.0f) {
            const float nerves = b->shown.nerves, tired = fmaxf(-b->shown.energy, 0.0f), proud = proudness(b);
            const float fit = b->fitness;
            const float weight[FIDGET_COUNT] = {
                [FIDGET_SHAKE_LEG]  = 0.8f + 1.5f * nerves,
                [FIDGET_ROCK]       = 0.7f + 0.8f * nerves - 0.4f * tired,
                [FIDGET_STRETCH]    = 0.4f + 1.6f * tired + 1.2f * puffed * fit,
                [FIDGET_SHOULDERS]  = 0.6f + 0.3f * puffed,
                [FIDGET_NECK]       = 0.6f + 0.8f * tired,
                [FIDGET_CROSS_ARMS] = 0.5f + 1.5f * proud - 0.3f * shyness(b),
                [FIDGET_TAP_FOOT]   = 0.3f + 2.0f * nerves,
                [FIDGET_RUB_HANDS]  = 0.2f + 1.2f * nerves,
                [FIDGET_HEAD_BEHIND] = 0.2f + 0.6f * proud + 0.5f * fmaxf(b->shown.energy, 0.0f) - 0.3f * shyness(b),
                [FIDGET_AKIMBO]     = 0.3f + 1.4f * proud - 0.3f * shyness(b) + 2.0f * puffed,
                /* catching breath, by who they are: the unfit bent over, the sporty hands on the head */
                [FIDGET_KNEES]      = 4.0f * puffed * (1.15f - fit) - 1.0f * proud,
                [FIDGET_ON_HEAD]    = 3.5f * puffed * (0.2f + fit),
            };
            float total = 0.0f;
            for (int k = 1; k < FIDGET_COUNT; k++) total += fmaxf(weight[k], 0.0f);
            float r = random01(b) * total;
            int pick = FIDGET_SHAKE_LEG;
            for (int k = 1; k < FIDGET_COUNT; k++) {
                r -= fmaxf(weight[k], 0.0f);
                if (r <= 0.0f) { pick = k; break; }
            }
            b->fidget = pick;
            b->fidget_side = random01(b) < 0.5f ? 0 : 1;
            b->fidget_len = b->fidget_time = FIDGET_LENGTH[pick] * (0.8f + 0.4f * random01(b));
            b->fidget_fade = 1.0f;
        }
    }

    /* Standing about, the feet settle into another stance now and then: one
       a little ahead, wider apart, back together (each change is a step) */
    if (idle < 0.5f) {
        b->stance[0] = b->stance[1] = vec3_zero();
        b->stance_timer = 4.0f + 6.0f * random01(b);
    } else if (at_ease && (b->stance_timer -= dt) <= 0.0f) { /* (not while busy: hands on the knees, ...) */
        b->stance_timer = (5.0f + 10.0f * random01(b)) * (1.0f - 0.4f * b->shown.nerves);
        int side = random01(b) < 0.5f ? 0 : 1;
        float r = random01(b);
        Vec3 old = b->stance[side];
        if (r < 0.35f)      b->stance[side] = vec3(0.0f, 0.0f, STANCE_STEP * (random01(b) < 0.7f ? 1.0f : -0.8f)); /* one foot ahead (or back) */
        else if (r < 0.55f) b->stance[side] = vec3((side ? 1.0f : -1.0f) * STANCE_STEP * 0.9f, 0.0f, 0.0f);      /* wider */
        else                b->stance[side] = vec3_zero();                                                       /* back together */
        if (vec3_length(vec3_sub(old, b->stance[side])) < 0.05f) b->stance[side] = vec3(0.0f, 0.0f, STANCE_STEP);
        b->stance[1 - side] = vec3_scale(b->stance[1 - side], 0.5f);
    }
}

void Body_talkGesture(Body *b, int kind) {
    int side = !b->hand_active[1] ? 1 : !b->hand_active[0] ? 0 : -1;
    if (side < 0 || b->pose != POSE_UP) return;
    b->gesture = kind == TALK_WAVE ? GESTURE_WAVE : kind == TALK_SHRUG ? GESTURE_SHRUG : GESTURE_POINT;
    b->gesture_side = side;
    b->gesture_len = b->gesture_time = kind == TALK_WAVE ? 1.8f : 1.4f;
    b->gesture_fade = 1.0f;
    b->gesture_timer = b->gesture_len + 4.0f;
}

void Body_think(Body *b, float seconds) {
    int side = !b->hand_active[1] ? 1 : !b->hand_active[0] ? 0 : -1;
    if (side < 0 || b->pose != POSE_UP || seconds < 0.5f) return;
    b->gesture = random01(b) < 0.65f ? GESTURE_CHIN : GESTURE_HEAD;
    b->gesture_side = side;
    b->gesture_len = b->gesture_time = seconds;
    b->gesture_fade = 1.0f;
    b->gesture_timer = seconds + 3.0f;
}

/* Leaving the ground: which foot pushes off last, and (running) which one leads. */
static void takeOff(Body *b, const BodyFrame *f, float speed) {
    b->jumping = true;
    b->lead_foot = -1;
    Vec3 fwd = speed > 0.6f ? vec3_normalize(flat(b->velocity)) : f->facing;
    float ahead0 = vec3_dot(vec3_sub(b->feet[0].ankle, f->position), fwd);
    float ahead1 = vec3_dot(vec3_sub(b->feet[1].ankle, f->position), fwd);
    if (speed > 0.8f) {
        b->push_foot = ahead0 < ahead1 ? 0 : 1; /* the rear foot pushes off ... */
        b->lead_foot = 1 - b->push_foot;        /* ... the front one goes for the landing */
        b->push_time = PUSH_OFF_TIME * 1.2f;
    } else {
        /* Standing: nearly always one foot a little before the other */
        float r = random01(b);
        b->push_foot = r < 0.12f ? -1 : (r < 0.56f + 0.2f * b->quirk[0] ? 1 : 0);
        b->push_time = PUSH_OFF_TIME * (0.6f + 0.6f * random01(b));
    }
    for (int i = 0; i < 2; i++) b->feet[i].stepping = false;
}

/* Feet off the ground: tucked under the body; running, the leading leg
   reaches forward and the trailing one hangs back, heel up. */
static void inTheAir(Body *b, const BodyFrame *f, float s, float speed) {
    const float dt = f->dt;
    Vec3 fwd = speed > 0.6f ? vec3_normalize(flat(b->velocity)) : f->facing;
    bool rising = b->vy > 0.0f;
    /* Dropping from high up: knees pulled up to the belly, then (timed) the legs
       reach down for the ground */
    float tuck = 0.0f, reach = 0.0f;
    if (b->vy < -2.0f && f->ground) {
        float h = f->position.y - f->ground(f->ground_ctx, f->position.x, f->position.z, f->position.y + 0.1f);
        float v = -b->vy, t = (-v + sqrtf(v * v + 2.0f * 9.81f * fmaxf(h, 0.0f))) / 9.81f; /* (till the feet are down) */
        float high = smoothstepf(0.5f * s, 1.2f * s, h + 0.1f * v * v);
        reach = high * (1.0f - smoothstepf(0.12f, 0.28f, t));
        tuck  = high * smoothstepf(2.0f, 4.0f, v) * (1.0f - reach);
    }
    for (int i = 0; i < 2; i++) {
        Foot *foot = &b->feet[i];
        foot->stepping = false;
        if (i == b->push_foot && b->air_time < b->push_time && tuck <= 0.0f) continue; /* still pushing off */
        float sign = i == 0 ? -1.0f : 1.0f;
        float ahead, lift, pitch;
        if (b->lead_foot >= 0) {
            bool lead = i == b->lead_foot;
            ahead = lead ? 0.34f : -0.30f;
            lift  = lead ? (rising ? 0.30f : 0.10f) : 0.26f;
            pitch = lead ? 0.25f : -0.9f;       /* toes up to land on the heel / pointing down behind */
        } else {
            ahead = 0.03f;
            /* the foot that left first tucks up higher, the push-off leg stays longer */
            lift  = (rising ? 0.20f : 0.11f) + (b->push_foot < 0 ? 0.0f : (i == b->push_foot ? -0.04f : 0.04f));
            pitch = -0.35f;
        }
        if (tuck + reach > 0.0f) { /* (knees up, then legs down, feet flat to land) */
            ahead = ahead + (0.12f - ahead) * tuck + (0.05f - ahead) * reach;
            lift  = lift + (0.40f - lift) * tuck - lift * reach;
            pitch = pitch + (-0.15f - pitch) * fmaxf(tuck, reach);
        }
        Vec3 hang = vec3_add(f->position, vec3_add(vec3_scale(rightOf(f->facing), sign * FOOT_X * s), vec3_scale(fwd, ahead * s)));
        hang.y = f->position.y + lift * s;
        /* on a spring, from the way it was moving when it left the ground */
        float omega = reach > 0.3f ? 22.0f : i == b->push_foot ? 10.0f : 14.0f; /* (reaching for the ground: quick) */
        if (!b->airborne) foot->air_v = foot->vel;
        Vec3 was = vec3_sub(foot->toe, foot->ankle); /* (how the foot pointed, before it moves on) */
        Vec3 carried = vec3(b->velocity.x, b->vy, b->velocity.z); /* (it goes along with the body) */
        springStep3(&foot->ankle, &foot->air_v, hang, carried, omega, dt);
        foot->dir = f->facing;
        Vec3 along = vec3_add(vec3_scale(f->facing, cosf(pitch)), vec3_scale(UP, sinf(pitch))); /* (toes the way the body faces) */
        if (vec3_length(was) > 1e-4f) along = vec3_normalize(vec3_lerp(vec3_normalize(was), along, fminf(1.0f, 10.0f * dt)));
        foot->toe = vec3_add(foot->ankle, vec3_scale(along, FOOT_LENGTH * s));
        if (vec3_length(flat(along)) > 0.05f) foot->dir = vec3_normalize(flat(along)); /* (lands pointing that way) */
    }
    b->airborne = true;
}

/* On something narrow (a log): the arms go out and the body teeters from side
   to side - a wobble that grows with clumsiness, drink and hurry and that the
   sporty keep small. Past the edge of it: a step off sideways. */
#define TEETER_OMEGA  3.2f  /* rad/s: how quickly a wobble swings back */
#define TEETER_DAMP   0.35f /* (some is caught, the rest swings on) */
#define TEETER_KICK   40.0f  /* how hard the wobbles come */

static void updateBalance(Body *b, const BodyFrame *f, float speed, float dt) {
    b->balance += (b->balance_goal - b->balance) * fminf(1.0f, (b->balance_goal > b->balance ? 4.0f : 2.0f) * dt);
    b->dance += (b->dance_goal - b->dance) * fminf(1.0f, 2.0f * dt);
    if (b->dance < 0.002f) b->dance = 0.0f;
    if (b->balance < 0.02f || b->airborne || b->on_ladder || dt <= 0.0f) {
        b->teeter *= fmaxf(0.0f, 1.0f - 3.0f * dt);
        b->teeter_v = 0.0f;
        return;
    }
    float shaky = (0.25f + 0.6f * Body_clumsiness(b) + 1.6f * b->unsteadiness + 0.35f * fminf(speed, 2.0f) + 0.4f * b->shown.nerves)
                * (1.25f - 0.75f * b->fitness) * b->balance;
    float kick = (random01(b) * 2.0f - 1.0f) * TEETER_KICK * shaky;
    b->teeter_v += (kick - TEETER_OMEGA * TEETER_OMEGA * b->teeter - 2.0f * TEETER_DAMP * TEETER_OMEGA * b->teeter_v) * dt;
    b->teeter += b->teeter_v * dt;
    if (fabsf(b->teeter) > 1.0f && b->stumble <= 0.0f) { /* too far over: a step off, that side */
        float side = b->teeter > 0.0f ? 1.0f : -1.0f;
        Body_stagger(b, vec3_add(vec3_scale(rightOf(f->facing), side * 1.3f), vec3_scale(f->facing, 0.2f)));
        b->teeter = b->teeter_v = 0.0f;
        b->stepped_off = true;
    }
}

void Body_update(Body *b, const BodyFrame *f) {
    const float s = f->height / REF_HEIGHT;
    const float dt = f->dt;
    b->clock += dt;
    b->clutter = f->clutter; /* (the pose keeps the legs clear of it) */
    b->clutter_ctx = f->ground_ctx;
    b->ground = f->ground;
    b->push_out = f->push_out;
    b->at = f->position;
    b->at_scale = s;
    for (int i = 0; i < 2; i++) { /* how the feet moved last frame */
        Foot *foot = &b->feet[i];
        foot->vel = dt > 0.0f ? vec3_scale(vec3_sub(foot->ankle, foot->prev), 1.0f / dt) : vec3_zero();
        if (vec3_length(foot->vel) > 15.0f) foot->vel = vec3_zero(); /* (put somewhere else) */
        foot->prev = foot->ankle;
    }

    /* Velocity from actual motion, whatever caused it (walking, pushing, being pushed) */
    if (!b->ready) {
        b->ready = true;
        /* Everybody gets their own dice, and their own little lopsided ways */
        b->rng = 0x9E3779B9u ^ (unsigned)(int)(f->position.x * 7919.0f) ^ ((unsigned)(int)(f->position.z * 104729.0f) << 7);
        if (b->rng == 0) b->rng = 1;
        for (int k = 0; k < 4; k++) b->quirk[k] = random01(b) * 2.0f - 1.0f;
        for (int k = 0; k < 4; k++) { /* (from the quirks, not the dice: everything else stays as it was) */
            float h = sinf(b->quirk[k] * 127.1f + b->quirk[(k + 1) % 4] * 311.7f + (float)k * 74.7f) * 43758.5453f;
            b->style[k] = (h - floorf(h)) * 2.0f - 1.0f;
        }
        {   float h = sinf(b->quirk[0] * 91.3f + b->quirk[2] * 47.1f + 13.7f) * 43758.5453f;
            b->stride = (h - floorf(h)) * 2.0f - 1.0f; } /* (long strides, or short quick ones: the same speed) */
        b->habit[0] = (unsigned char)(1 + (int)(random01(b) * (float)(GESTURE_COUNT - 1)) % (GESTURE_COUNT - 1));
        b->habit[1] = (unsigned char)(1 + ((int)b->habit[0] + (int)(random01(b) * (float)(GESTURE_COUNT - 2))) % (GESTURE_COUNT - 1));
        b->gesture_timer = 3.0f + 6.0f * random01(b);
        b->last_position = f->position;
        b->last_facing = f->facing;
        b->velocity = vec3_zero();
        b->step_length = stepLength(0.0f, s);
        b->pelvis_y = PELVIS_Y;
        b->last_y = f->position.y;
        b->push_foot = b->lead_foot = -1;
        plantAll(b, f, s);
    }
    if (b->replant) {
        b->replant = false;
        b->last_position = f->position;
        plantAll(b, f, s);
    }
    Vec3 moved = flat(vec3_sub(f->position, b->last_position));
    b->last_position = f->position;
    Vec3 v = dt > 0.0f ? vec3_scale(moved, 1.0f / dt) : vec3_zero();
    if (vec3_length(v) > TELEPORT_SPEED) {
        b->velocity = vec3_zero();
        plantAll(b, f, s);
    }
    b->velocity = vec3_lerp(b->velocity, v, fminf(1.0f, EASE_VELOCITY * dt));
    /* The body itself carries some weight: when the character jitters (pushed
       about, wedged against something) the hips don't shake with it */
    if (!b->root_ok || vec3_length(v) > TELEPORT_SPEED) {
        b->root_vis = flat(f->position);
        b->root_vel = flat(b->velocity);
        b->root_ok = true;
    } else if (dt > 0.0f) {
        springStep3(&b->root_vis, &b->root_vel, flat(f->position), flat(b->velocity), ROOT_OMEGA, dt);
        Vec3 off = vec3_sub(b->root_vis, flat(f->position));
        float far = vec3_length(off), most = ROOT_LAG * s;
        if (far > most) b->root_vis = vec3_add(flat(f->position), vec3_scale(off, most / far));
    }
    float speed = vec3_length(b->velocity);
    updateExpression(b, f, speed);
    updateSmoking(b, speed, dt);
    if (updateDown(b, f, s)) return;
    if (b->shoulder_roll > 0.0f) { /* over the shoulder: a set move, the feet go where the body is */
        b->shoulder_roll += dt / SHOULDER_ROLL_TIME;
        Vec3 r = rightOf(f->facing);
        for (int i = 0; i < 2; i++) { /* (the feet come along with the body: under it when up again) */
            Foot *foot = &b->feet[i];
            foot->stepping = false;
            foot->ankle = vec3_add(f->position, vec3_add(vec3_scale(r, (i == 0 ? -0.1f : 0.1f) * s), vec3(0.0f, 0.075f * s, 0.0f)));
            foot->dir = f->facing;
            foot->toe = vec3_add(foot->ankle, vec3_add(vec3_scale(f->facing, 0.14f * s), vec3(0.0f, -0.05f * s, 0.0f)));
        }
        if (b->shoulder_roll < 1.0f) return;
        b->shoulder_roll = 0.0f;
        b->replant = true; /* (up again: the feet under you) */
    }
    updateBalance(b, f, speed, dt);

    /* Side steps and backward steps are shorter than steps forward; shy people take small steps */
    float sideways = speed > 0.05f ? fabsf(vec3_dot(b->velocity, rightOf(f->facing))) / speed : 0.0f;
    float backward = speed > 0.05f ? fmaxf(0.0f, -vec3_dot(b->velocity, f->facing) / speed) : 0.0f;
    b->step_length = stepLength(speed, s) * (1.0f - 0.45f * sideways) * (1.0f - 0.35f * backward)
                   * (1.0f - 0.12f * shyness(b))
                   * (1.0f + 0.14f * b->stride)                                  /* their own: long and slow, or short and quick */
                   * (1.0f + 0.3f * (b->fitness - 0.5f) * runAmount(speed));     /* (sprinting, the sporty bound along) */

    /* Something to step over coming up: the sporty fit their steps to it, so a
       foot comes down at a good take-off spot just before it, no stutter */
    if (f->clutter && speed > 0.6f && b->fitness > PLAN_FIT && backward < 0.3f) {
        Vec3 dir = vec3_scale(flat(b->velocity), 1.0f / speed);
        float edge = -1.0f;
        for (float d = 0.3f * s; d < PLAN_AHEAD * s && edge < 0.0f; d += 0.1f * s) {
            Vec3 p = vec3_add(f->position, vec3_scale(dir, d));
            if (f->clutter(f->ground_ctx, p.x, p.z, 0.1f * s, f->position.y) > 0.04f * s) edge = d;
        }
        if (edge > 0.0f) {
            float take_off = (PLAN_TAKE_OFF + 0.08f * speed) * s;
            float room = edge - take_off - 0.5f * b->step_length; /* (the next foot lands half a step ahead) */
            if (room > 0.0f) {
                float n = fmaxf(1.0f, roundf(room / b->step_length));
                float fit = clampf(room / n, 0.75f * b->step_length, 1.25f * b->step_length);
                b->step_length = lerpf(b->step_length, fit, smoothstepf(PLAN_FIT, PLAN_FIT + 0.4f, b->fitness));
            }
        }
    }

    /* Uphill: shorter steps; downhill: longer ones */
    float grade = 0.0f;
    if (speed > 0.2f) {
        Vec3 ahead = vec3_add(f->position, vec3_scale(flat(b->velocity), 0.5f * s / speed));
        float from_y = f->position.y + STEP_UP * s;
        grade = (f->ground(f->ground_ctx, ahead.x, ahead.z, from_y)
               - f->ground(f->ground_ctx, f->position.x, f->position.z, from_y)) / (0.5f * s);
    }
    b->grade = ease(b->grade, clampf(grade, -1.0f, 1.0f), 5.0f, dt);
    b->step_length *= b->grade > 0.0f ? 1.0f - clampf(b->grade * 1.5f, 0.0f, 0.45f)
                                      : 1.0f + clampf(-b->grade * 0.9f, 0.0f, 0.3f);

    /* Action goals */
    if (b->squat_pee && b->pee > 0.0f) b->crouch_goal = fmaxf(b->crouch_goal, 1.0f); /* squatting to pee */
    if (b->land_squat > 0.0f) { /* landed from high up: into the knees, up again */
        b->crouch_goal = fmaxf(b->crouch_goal, b->land_squat);
        b->land_squat = fmaxf(0.0f, b->land_squat - 1.6f * dt);
    }
    springCritical(&b->crouch, &b->crouch_v, b->crouch_goal, POSE_OMEGA, dt); /* bending down speeds up, slows down */
    b->crouch = clampf(b->crouch, 0.0f, 1.2f);
    b->sit    = ease(b->sit, b->pose == POSE_UP ? b->sit_goal : 0.0f, 2.5f, dt);
    springCritical(&b->lean, &b->lean_v, b->lean_goal, POSE_OMEGA, dt);
    for (int h = 0; h < 2; h++)
    {   /* eases in and out (a spring): a hand never jerks off towards something */
        springCritical(&b->hand_weight[h], &b->hand_wvel[h], b->hand_active[h] ? 1.0f : 0.0f, HAND_WEIGHT_OMEGA, dt);
        b->hand_weight[h] = clampf(b->hand_weight[h], 0.0f, 1.0f);
    }

    /* Jumping: off one foot (rarely both at once); running, the rear foot
       pushes off and the front one reaches ahead to land first */
    float rise = dt > 0.0f ? (f->position.y - b->last_y) / dt : 0.0f;
    if (f->grounded && fabsf(f->position.y - b->last_y) > 0.08f * s && fabsf(f->position.y - b->last_y) < 0.7f * s) {
        /* hopped up (or dropped) onto something in one go: the hips stay where they were and follow */
        b->pelvis_y -= (f->position.y - b->last_y) / s;
        rise = 0.0f;
    }
    b->vy = ease(b->vy, rise, 20.0f, dt);
    b->last_y = f->position.y;
    if (!f->grounded && b->air_time <= 0.0f && rise > TAKE_OFF_SPEED && !b->jumping) takeOff(b, f, speed);
    b->air_time = f->grounded ? 0.0f : b->air_time + dt;
    if (b->air_time > AIR_GRACE || (b->jumping && !f->grounded)) {
        inTheAir(b, f, s, speed);
        return;
    }
    if (b->airborne && f->grounded) {
        /* Landing: both feet reach for the ground quickly, one a moment before the other */
        b->airborne = false;
        b->landing = LANDING_STEP_TIME;
        for (int i = 0; i < 2; i++) {
            b->feet[i].stepping  = true;
            b->feet[i].step      = 0.0f;
            b->feet[i].step_from = b->feet[i].ankle;
            b->feet[i].step_v0   = b->feet[i].vel;   /* (on from how it was moving) */
            if (vec3_length(b->feet[i].step_v0) > 4.0f * s) b->feet[i].step_v0 = vec3_scale(vec3_normalize(b->feet[i].step_v0), 4.0f * s);
            b->feet[i].spot_was  = b->feet[i].step_to = ankleAt(footSpot(b, f, i, s), s);
            b->feet[i].pitch0    = asinf(clampf((b->feet[i].toe.y - b->feet[i].ankle.y) / fmaxf(FOOT_LENGTH * s, 1e-4f), -1.0f, 1.0f));
            b->feet[i].delay     = 0.0f;
            b->feet[i].clear_shift = vec3_zero();
            b->feet[i].over      = 0.0f;
        }
        if (b->lead_foot >= 0) b->feet[1 - b->lead_foot].delay = TRAIL_LAND_DELAY;
        else if (b->push_foot >= 0) b->feet[b->push_foot].delay = 0.04f;
    }
    if (f->grounded) {
        b->jumping = false;
        b->lead_foot = b->push_foot = -1;
    }
    b->landing = fmaxf(0.0f, b->landing - dt);
    b->stumble = fmaxf(0.0f, b->stumble - dt);
    if (b->fall_delay > 0.0f) { /* reeling: keeps going, then down */
        b->stumble = fmaxf(b->stumble, 0.3f);
        if ((b->fall_delay -= dt) <= 0.0f) Body_fall(b, b->fall_pending);
        else if (b->fall_delay < 0.3f && b->fall_delay + dt >= 0.3f)
            b->stumble_push = vec3_add(b->stumble_push, vec3_scale(b->fall_pending, 0.9f));
    }

    /* Swing time: walking ~ one step, running longer (flight phase) */
    /* (setting off, the body is already going: quick first steps keep up with it) */
    const float pace = fmaxf(speed, fminf(vec3_length(v), 2.0f * speed + 1.0f));
    const float swing = b->step_length / fmaxf(pace, 0.01f) * (1.0f + 0.4f * runAmount(speed));
    const float step_time = b->landing > 0.0f ? LANDING_STEP_TIME
                          : clampf(swing, MIN_STEP_TIME, MAX_STEP_TIME);
    const float hip_y = ((b->pelvis_y > 0.3f ? b->pelvis_y : PELVIS_Y) - HIP_DROP) * s; /* (the legs hang from there) */

    /* Feet that are stepping keep aiming at their (moving) spot */
    bool any_stepping = false;
    for (int i = 0; i < 2; i++) {
        Foot *foot = &b->feet[i];
        if (!foot->stepping) continue;
        if (foot->delay > 0.0f) { /* still in the air a moment: stays where it is */
            foot->delay -= dt;
            any_stepping = true;
            continue;
        }
        /* the spot moves with the body; the way round what lies there was chosen when the step began */
        Vec3 spot = uncrossed(footSpot(b, f, i, s), f, i, b->feet[1 - i].ankle, s);
        if (vec3_length(foot->clear_shift) > 1e-4f) {
            spot = vec3_add(spot, foot->clear_shift);
            spot.y = groundUnder(f, spot.x, spot.z, s);
        }
        /* The aim moves with the body, less and less towards the end: the
           foot slows down onto the ground instead of being dragged along */
        Vec3 aim = gliding(foot, ankleAt(spot, s), f, vec3_length(flat(b->velocity)), s, dt);
        float follow = 1.0f - smoothstepf(0.55f, 0.95f, foot->step);
        Vec3 next = vec3_add(foot->step_to, vec3_scale(flat(vec3_sub(aim, foot->spot_was)), follow));
        /* (but not onto something lying there: then it lands where it was going) */
        bool lunge = foot->over > 0.0f && vec3_length(flat(vec3_sub(next, b->feet[1 - i].ankle))) > overReach(b, s) &&
                     vec3_length(flat(vec3_sub(next, b->feet[1 - i].ankle))) > vec3_length(flat(vec3_sub(foot->step_to, b->feet[1 - i].ankle)));
        if (!lunge && (!f->clutter || spotFree(f, next, s) || !spotFree(f, foot->step_to, s)))
            foot->step_to = next;
        else if (foot->step < OVER_LEAP_LATE && vec3_length(flat(vec3_sub(next, foot->step_to))) > OVER_LEFT * s)
            foot->step_to = leapPast(f, foot, next, b->feet[1 - i].stepping ? b->feet[1 - i].step_to : b->feet[1 - i].ankle, overReach(b, s), s);
        foot->spot_was = aim;
        /* (over an edge the ground below the aim jumps: the foot goes there smoothly) */
        float to_y = groundUnder(f, foot->step_to.x, foot->step_to.z, s) + ANKLE_Y * s;
        foot->step_to.y = ease(foot->step_to.y, to_y, foot->step > 0.85f ? 40.0f : 18.0f, dt);
        /* (over something: slower; running: a leap, and a step held back behind the body comes down quick) */
        float late = speed > LEAP_SPEED ? fmaxf(0.0f, -vec3_dot(flat(vec3_sub(foot->step_to, f->position)), f->facing)) / s : 0.0f;
        const float step_was = foot->step;
        foot->step = fminf(1.0f, foot->step + dt * (1.0f + LATE_QUICK * late) / (step_time * (1.0f + OVER_SLOW * (1.0f - runAmount(speed)) * foot->over / s)));

        /* The foot rises early in the swing and reaches forward late. Running:
           the heel kicks up behind first, then the knee drives forward. */
        float run = runAmount(speed);
        float t = smoothstepf(0.0f, 1.0f, powf(foot->step, 1.0f + 0.8f * run)); /* (running: stays behind a while, then comes through) */
        Vec3 pos = vec3_lerp(foot->step_from, foot->step_to, t);
        {   /* a foot already on the move (coming down from a jump) carries on smoothly */
            float u = foot->step;
            pos = vec3_add(pos, vec3_scale(foot->step_v0, u * (1.0f - u) * (1.0f - u) * step_time));
        }
        if (f->clutter && foot->step < 0.9f) {
            /* something turned up in the way (the aim moved on with the body): lift higher, quickly but smoothly */
            float need = clutterAlong(f, pos, foot->step_to, 0.09f * s, 0.0f);
            float want = need > 0.0f ? need + OVER_CLEAR * s : 0.0f;
            if (want > foot->over) foot->over = fminf(want, foot->over + OVER_RISE * s * dt);
        }
        pos.y += fmaxf((STEP_LIFT + 0.3f * run) * s * stepArc(foot->step, 0.75f - 0.35f * run) /* (running: the heel up behind, early) */
                         * clampf(0.4f + speed * 0.3f, 0.4f, 1.2f) * (1.0f + 0.25f * b->shown.energy),
                       foot->over * stepArc(foot->step, 1.0f)); /* a big step over something (no higher than that) */
        foot->swing_run = fmaxf(run, foot->swing_run - SWING_RUN_EASE * dt); /* (slowing down mid-swing: the curve goes over gently) */
        if (foot->swing_run > 0.0f && foot->over <= 0.0f) {
            /* Running: off the toes the heel folds up under the seat, the knee drives through in
               front, the foot reaches out and comes down (a curve that goes with the body) */
            Vec3 side = vec3_scale(rightOf(f->facing), (i == 0 ? -1.0f : 1.0f) * 0.07f * s); /* (running: the feet nearly on one line) */
            Vec3 tuck  = vec3_add(vec3_add(f->position, side), vec3_add(vec3_scale(f->facing, RUN_TUCK_BACK * s), vec3(0.0f, RUN_TUCK_UP * s, 0.0f)));
            Vec3 drive = vec3_add(vec3_add(f->position, side), vec3_add(vec3_scale(f->facing, RUN_DRIVE_AHEAD * s), vec3(0.0f, RUN_DRIVE_UP * s, 0.0f)));
            float u = foot->step, m = 1.0f - u;
            Vec3 curve = vec3_add(vec3_add(vec3_scale(foot->step_from, m * m * m), vec3_scale(tuck, 3.0f * m * m * u)),
                                  vec3_add(vec3_scale(drive, 3.0f * m * u * u), vec3_scale(foot->step_to, u * u * u)));
            pos = vec3_lerp(pos, curve, foot->swing_run);
        }
        if (foot->over > 0.0f) {
            /* Over something the leg swings round a little, out to the side and back in to land,
               while the body's momentum carries it on: the shin clears without climbing so high */
            float out = OVER_SWING_OUT * s * smoothstepf(0.0f, OVER_SWING_FULL * s, foot->over) * stepArc(foot->step, 1.0f);
            pos = vec3_add(pos, vec3_scale(rightOf(f->facing), i == 0 ? -out : out));
        }
        if (f->clutter) {
            /* never into what lies there: lifted over it, and over what it's about to reach (smoothly) */
            Vec3 ahead = vec3_add(pos, vec3_scale(flat(vec3_sub(foot->step_to, foot->step_from)), 0.25f));
            float floor = fmaxf(groundUnder(f, pos.x, pos.z, s), groundUnder(f, ahead.x, ahead.z, s)) + (ANKLE_Y + 0.02f) * s;
            float want = foot->step < 0.97f ? fmaxf(0.0f, floor - pos.y) : 0.0f;
            /* the whole leg too: the shin and the knee come over it, not through it */
            /* (a foot far off is just catching up; standing about, feet just shift, they don't climb anything) */
            bool near = vec3_length(flat(vec3_sub(pos, f->position))) < LEG_NEAR * s && speed > LEG_MOVING;
            float into = foot->step < 0.97f && near ? legInto(f, vec3_add(pos, vec3(0.0f, foot->lift, 0.0f)), foot->dir, i, vec3_zero(), hip_y, s) : 0.0f;
            want = fminf(fmaxf(want, foot->lift + into), LIFT_MOST * s);
            float up = (into > 0.0f ? LEG_RISE : LIFT_RISE) * s * dt;
            foot->lift = want > foot->lift ? fminf(want, foot->lift + up) : fmaxf(want, foot->lift - LIFT_RISE * s * dt);
            /* down onto the ground with the step: never faster than a foot comes down, and the step
               isn't over before the foot is down */
            float cap = LIFT_MOST * s * (1.0f - smoothstepf(LEG_HOLD - 0.05f, 0.97f, foot->step));
            if (foot->lift > cap) foot->lift = fmaxf(cap, foot->lift - LIFT_DROP * s * dt);
            if (foot->lift > 0.03f * s && foot->step > 0.95f) foot->step = 0.95f;
            pos.y += foot->lift;
            /* not clear yet: the foot goes up first and over after (unless it can't get that high) */
            /* (running there's no waiting: the knee drives through, high, and it may scrape) */
            if (near && speed < LEAP_SPEED && foot->step < LEG_HOLD && foot->lift < LIFT_MOST * s - 0.01f &&
                legInto(f, pos, foot->dir, i, vec3_zero(), hip_y, s) > LEG_TOUCH * s) {
                pos.x = foot->ankle.x;
                pos.z = foot->ankle.z;
                foot->step = step_was;
            }
        }
        foot->ankle = pos;
        foot->dir = vec3_normalize(vec3_lerp(foot->dir, footDir(b, f->facing, i), fminf(1.0f, dt * 12.0f)));
        float strike = heelStrike(run) * (b->landing > 0.0f ? 0.3f : 1.0f);
        foot->toe = toeSwinging(f, pos, foot->dir, foot->step, foot->pitch0, strike, s);

        if (foot->step >= 1.0f) {
            foot->stepping = false;
            foot->ankle = foot->step_to;
            b->landed = i + 1;
            b->landed_at = foot->ankle;
            foot->toe = toePlanted(f, foot->ankle, foot->dir, s);
            foot->slap = strike;  /* on the heel: the toes come down next */
            foot->slap_v = 0.0f;
            b->last_step = i;
        } else {
            any_stepping = true;
        }
    }

    crossOver(b, f, s);

    /* Start a new step with the foot that drifted furthest from its spot (or
       that the body turned away from). Walking: one foot at a time.
       Running: the next foot may leave before the other lands. */
    float overlap = 0.45f * runAmount(speed);
    float limit = (speed > 0.2f ? STEP_THRESHOLD : IDLE_THRESHOLD) * s;
    int best = -1;
    float best_need = 0.0f;
    Vec3 best_spot = vec3_zero();
    for (int i = 0; i < 2; i++) b->feet[i].planted = b->feet[i].stepping ? 0.0f : b->feet[i].planted + dt;
    for (int i = 0; i < 2; i++) {
        Foot *foot = &b->feet[i], *other = &b->feet[1 - i];
        if (foot->stepping) continue;
        /* (running, the foot that came down stays down a moment: the leg bends under the body and pushes off) */
        if (foot->planted < STANCE_RUN * runAmount(speed) && foot->into <= LEG_TOUCH * s) continue;
        /* (running, a foot left far behind while the other is still on its way over something: off it goes, a leap) */
        float behind = -vec3_dot(flat(vec3_sub(foot->ankle, f->position)), f->facing);
        bool leap = speed > LEAP_SPEED && ((other->over > 0.0f && behind > LEAP_BEHIND * s) || foot->into > LEG_TOUCH * s);
        if (other->stepping && other->step < 1.0f - overlap && !leap) continue;
        if (any_stepping && !other->stepping) continue;
        /* half over something: the foot already over there stays; the other one comes after it */
        if (b->crossing && !other->stepping && vec3_dot(flat(vec3_sub(foot->ankle, b->cross_to)), b->cross_dir) > -0.12f * s &&
            vec3_dot(flat(vec3_sub(other->ankle, b->cross_to)), b->cross_dir) < -0.25f * s) continue;

        Vec3 spot = ankleAt(uncrossed(footSpot(b, f, i, s), f, i, other->ankle, s), s);
        float drift  = vec3_length(flat(vec3_sub(spot, foot->ankle)));
        float aligned = vec3_dot(foot->dir, footDir(b, f->facing, i));
        bool turned  = aligned < TURN_THRESHOLD || (speed < 0.2f && aligned < 0.95f); /* standing: settle the feet */
        bool climb   = fabsf(spot.y - foot->ankle.y) > 0.15f * s;
        float need   = drift / limit + (turned ? 1.0f : 0.0f) + (climb ? 1.0f : 0.0f);
        if (i != b->last_step) need += 0.05f; /* tie: alternate feet */
        if (foot->into > LEG_TOUCH * s)
            need += 1.5f; /* the body moved on over something: this leg would go through it, up it comes */
        if (need > 1.0f && need > best_need) {
            best = i;
            best_need = need;
            best_spot = spot;
        }
    }
    if (best >= 0) {
        Foot *foot = &b->feet[best];
        foot->stepping  = true;
        foot->step      = 0.0f;
        foot->swing_run = runAmount(speed);
        /* off from where it was drawn (heel up, rolled over the toes): no jump */
        bool shown = b->shown_valid && vec3_length(vec3_sub(b->ankle_shown[best], foot->ankle)) < 0.25f * s;
        foot->step_from = shown ? b->ankle_shown[best] : foot->ankle;
        if (shown) {
            Vec3 d = vec3_sub(b->toe_shown[best], b->ankle_shown[best]);
            foot->pitch0 = asinf(clampf(d.y / fmaxf(vec3_length(d), 1e-4f), -1.0f, 1.0f));
            foot->ankle = b->ankle_shown[best]; /* (drawn as it was, this frame too) */
            foot->toe = b->toe_shown[best];
        } else {
            foot->pitch0 = FLAT_PITCH - 0.75f * foot->roll;
        }
        foot->roll      = 0.0f;
        foot->slap      = foot->slap_v = 0.0f;
        foot->step_v0   = vec3_zero();
        /* (how much they bother with what lies about: the sporty and the careful step round it) */
        float careless = clampf(0.08f + 0.6f * b->unsteadiness + 0.3f * (Body_clumsiness(b) - 0.5f)
                                + 0.35f * fmaxf(-b->shown.energy, 0.0f) + 0.3f * smoothstepf(2.0f, 5.0f, speed)
                                - 0.3f * (b->fitness - 0.5f), 0.0f, 0.85f);
        foot->step_to   = clearSpot(f, foot, best_spot, b->feet[1 - best].ankle, overReach(b, s), random01(b) < careless, s);
        maybeStumble(b, f, foot, speed, s); /* (drunk: may land off) */
        foot->clear_shift = vec3(foot->step_to.x - best_spot.x, 0.0f, foot->step_to.z - best_spot.z);
        foot->spot_was  = vec3_add(best_spot, foot->clear_shift);
        Vec3 rel = flat(vec3_sub(best_spot, f->position));
        foot->aim_local = vec3(vec3_dot(rel, rightOf(f->facing)), 0.0f, vec3_dot(rel, f->facing));
    }

    /* Planted feet behind the body roll onto their toes, smoothly; a foot
       that just landed on its heel puts the toes down */
    for (int i = 0; i < 2; i++) {
        Foot *foot = &b->feet[i];
        float into_was = foot->into;
        foot->into = 0.0f;
        if (foot->stepping) continue;
        float back = -vec3_dot(flat(vec3_sub(foot->ankle, f->position)), f->facing) / s;
        if (f->clutter && vec3_length(flat(vec3_sub(foot->ankle, f->position))) < LEG_NEAR * s) {
            /* now, or where the body is going (and it doesn't flicker off at once) */
            foot->into = fmaxf(legInto(f, foot->ankle, foot->dir, i, vec3_zero(), hip_y, s),
                               legInto(f, foot->ankle, foot->dir, i, vec3_scale(flat(b->velocity), LEG_AHEAD), hip_y, s));
            foot->into = fmaxf(foot->into, into_was - LEG_FORGET * dt);
        }
        /* (the leg over something behind: up on the toes, the knee goes up with the heel) */
        foot->roll = ease(foot->roll, fmaxf(smoothstepf(0.03f, 0.2f, back), foot->into > 0.0f ? 1.0f : 0.0f), 10.0f, dt);
        springCritical(&foot->slap, &foot->slap_v, 0.0f, 30.0f, dt);
    }
}

/* Half way over something: from when a foot is well on its way over it until
   the body is over there too (above where that foot came down). */
static void crossOver(Body *b, const BodyFrame *f, float s) {
    for (int i = 0; i < 2 && !b->crossing; i++) { /* (where the first foot goes over: the body goes that far, no further) */
        const Foot *ft = &b->feet[i];
        if (!ft->stepping || ft->over < CROSS_OVER * s || ft->step < CROSS_FROM) continue;
        Vec3 way = flat(vec3_sub(ft->step_to, ft->step_from));
        if (vec3_length(way) < 0.08f * s) continue; /* (lifted on the spot: not going over anything) */
        b->crossing = true;
        b->cross_foot = i;
        b->cross_to = ft->step_to;
        b->cross_dir = vec3_normalize(way);
        b->cross_left = vec3_dot(flat(vec3_sub(b->cross_to, f->position)), b->cross_dir);
        return;
    }
    if (!b->crossing) return;
    /* (a foot set a little further on, past it: the body goes after that one, no further) */
    float to = 0.0f;
    for (int i = 0; i < 2; i++) {
        const Foot *ft = &b->feet[i];
        if (!ft->stepping) to = fmaxf(to, fminf(vec3_dot(flat(vec3_sub(ft->ankle, b->cross_to)), b->cross_dir), CROSS_PAST * s));
    }
    float left = vec3_dot(flat(vec3_sub(b->cross_to, f->position)), b->cross_dir) + to;
    b->cross_left = left;
    float back = vec3_dot(flat(vec3_sub(b->feet[b->cross_foot].ankle, b->cross_to)), b->cross_dir);
    bool returned = back < -0.25f * s; /* (that foot came back: not going over after all) */
    bool away = left > CROSS_AWAY * s;  /* (the body went off the other way: not going over either - one foot was left stepping on its own) */
    if (left < CROSS_DONE * s || returned || away || b->pose != POSE_UP || b->airborne) {
        b->crossing = b->cross_force = false;
        b->cross_held = 0.0f;
    }
}

/* Where a foot lands: not on something lying there. A longer (or shorter)
   step past it, and the foot lifted over whatever is in the way. Too high to
   stand on and nowhere free: the foot stays where it is. */
static Vec3 clearSpot(const BodyFrame *f, Foot *foot, Vec3 spot, Vec3 other, float reach, bool sloppy, float s) {
    foot->over = 0.0f;
    if (!f->clutter) return spot;
    const float foot_r = 0.12f * s;
    float h = clutterAlong(f, foot->step_from, spot, 0.1f * s, 0.12f * s);
    if (spotFree(f, spot, s)) {
        foot->over = h > 0.0f ? h + OVER_CLEAR * s : 0.0f;
        return spot;
    }
    /* Not bothering (drunk, worn out, clumsy, in a hurry): onto the bottle or branch it goes */
    if (sloppy && f->clutter(f->ground_ctx, spot.x, spot.z, foot_r, f->position.y) < STEP_ON_SMALL * s) return spot;
    Vec3 right = rightOf(f->facing);
    static const float TRY[][2] = { { 0.22f, 0.0f }, { -0.16f, 0.0f }, { 0.34f, 0.0f }, { -0.3f, 0.0f },
                                    { 0.45f, 0.0f }, { 0.0f, 0.12f }, { 0.0f, -0.12f }, { -0.45f, 0.0f } };
    for (int k = 0; k < (int)(sizeof(TRY) / sizeof(TRY[0])); k++) {
        Vec3 p = vec3_add(spot, vec3_add(vec3_scale(f->facing, TRY[k][0] * s), vec3_scale(right, TRY[k][1] * s)));
        if (TRY[k][0] > 0.0f && vec3_length(flat(vec3_sub(p, other))) > reach) continue; /* (no lunge) */
        if (spotFree(f, p, s)) {
            p.y = f->ground(f->ground_ctx, p.x, p.z, spot.y + 0.4f * s) + (spot.y - f->ground(f->ground_ctx, spot.x, spot.z, spot.y + 0.4f * s));
            float hp = clutterAlong(f, foot->step_from, p, 0.1f * s, 0.12f * s);
            foot->over = hp > 0.0f ? hp + OVER_CLEAR * s : 0.0f; /* (the spot it goes to, not the one it didn't) */
            return p;
        }
    }
    float under = f->clutter(f->ground_ctx, spot.x, spot.z, foot_r, f->position.y);
    if (under > STAND_ON_MOST * s) return foot->step_from; /* nowhere free, and too high to stand on: stays */
    foot->over = h + OVER_CLEAR * s;
    return spot; /* low enough: on it then */
}

/* The body ran on while the foot waited in front of something: rather than
   landing far behind, the foot goes past it (if a leg reaches); else it stays put. */
static Vec3 leapPast(const BodyFrame *f, Foot *foot, Vec3 aim, Vec3 other, float reach, float s) {
    static const float PAST[] = { 0.22f, 0.34f, 0.45f, 0.6f };
    for (int k = 0; k < (int)(sizeof(PAST) / sizeof(PAST[0])); k++) {
        Vec3 p = vec3_add(aim, vec3_scale(f->facing, PAST[k] * s));
        if (vec3_length(flat(vec3_sub(p, other))) > reach) break;
        if (!spotFree(f, p, s)) continue;
        p.y = foot->step_to.y;
        float h = clutterAlong(f, foot->step_from, p, 0.1f * s, 0.12f * s);
        if (h > 0.0f) foot->over = fmaxf(foot->over, h + OVER_CLEAR * s);
        foot->clear_shift = vec3_add(foot->clear_shift, flat(vec3_sub(p, aim)));
        return p;
    }
    return foot->step_to;
}

Vec3 Body_takeStumblePush(Body *b) {
    Vec3 push = b->stumble_push;
    b->stumble_push = vec3_zero();
    return push;
}