Nimbin[12]?Games / Entropie / commits / a74125a

Entropie git · main

Entropie festival game; first steps

sdl c game entropie cmake · first commit 2026-10-10 · last commit 2026-11-02 (17 hours ago) · synced 8 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 1.58.0: stepping over is one smooth move; up onto blocks at a run

Christian Immanuel · 2026-10-14 21:08 · a74125a9c816fe1899735599ceaee464c8242737

- Walking over something knee high is a set move: the first leg over, the
  hips between the feet, the other leg after (higher things: up on the
  toes, swung out); it always finishes - no more legs through things or
  stuck half over (course, walking: legs in obstacles about a fifth)
- Jogging or faster at a block up to about hip high with room on top: a
  timed jump up onto it
- Running at something you can climb: you catch the edge instead of
  bouncing off, and the climb starts
 entropie/Makefile            |   2 +-
 entropie/README.md           |   7 +-
 entropie/STATE.md            |   6 +-
 entropie/commit_message.txt  |  15 +++--
 entropie/src/world/object.h  |   7 ++
 entropie/src/world/physics.c | 153 ++++++++++++++++++++++++++++++++++++++++++-
 entropie/src/world/world.c   |  47 +++++++++++++
 7 files changed, 225 insertions(+), 12 deletions(-)

diff --git a/entropie/Makefile b/entropie/Makefile
index 15bfbba..23c1ee3 100644
--- a/entropie/Makefile
+++ b/entropie/Makefile
@@ -1,5 +1,5 @@
 APP       := entropie
-VERSION   := 1.57.0
+VERSION   := 1.58.0
 
 PREFIX    ?= /usr
 BINDIR    ?= $(PREFIX)/bin
diff --git a/entropie/README.md b/entropie/README.md
index 55d5858..8c402ae 100644
--- a/entropie/README.md
+++ b/entropie/README.md
@@ -147,7 +147,12 @@ and satellite pictures (about 340 x 340 m).
   take-off spot
 - Jogging or faster, low things (up to about the knee) are jumped on their
   own: no slowing down before them, the take-off timed so the top of the
-  jump is over them, as high as it takes
+  jump is over them, as high as it takes. Blocks up to about hip high with
+  room on top: you jump up onto them. Higher (up to where your hands reach):
+  you catch the edge instead of bouncing off, and climb on
+- Walking over something knee high is one smooth move: the first leg goes
+  over, the body follows, the other leg comes after (higher things: up on
+  the toes, that leg swung out). Once started it always finishes
 - Running into something sober: low, you trip or go headlong over it; high,
   you stagger back or go down to the side you came in from. How badly
   depends on speed, fitness, energy and clumsiness
diff --git a/entropie/STATE.md b/entropie/STATE.md
index 79a937b..a2a3677 100644
--- a/entropie/STATE.md
+++ b/entropie/STATE.md
@@ -77,6 +77,8 @@ Talk topics: offer, ask, suggest, where (new woodpile spot), ask for a hand, ask
 - **Speed limit**: no joint moves faster than it can (hands 14, feet 12, knees and elbows 9, trunk 5 m/s); bones keep their length; a limb folded right up (foot by the hip, cross-legged) keeps its bend instead of being fitted again (that flipped the knee); between folded and stretched (30-42 % of its reach) the two blend, so an arm folding up on an edge no longer jumps (elbows popped 31 cm); an elbow swung up over an edge stays solved that way
 - **Any frame rate**: springs are solved exactly per frame, people pushing each other ease apart gently, helpers stay put once there (or blocked close by): no flicker at low, high or uneven frame rates
 - **Approaching obstacles**: the sporty fit their steps to a good take-off spot; jogging or faster (2.9 m/s+, some energy, not very drunk) low things (15 cm up to about knee high, up to 0.7 m deep) are jumped on their own (`physics.c` hurdle / jumpOver): no slowing down before them (the run-up counts), off when the top of the jump comes over its middle, as high as it takes to clear it by 14 cm (feet tucked 18 cm) and long enough to land 25 cm past it, at most a full jump (else stepped over, slower); course test (jog / sprint): legs in obstacles about a third of before, joint jumps -30 %; running into something: trip or headlong over it if low, stagger back or fall to the side if high (speed, fitness, energy, clumsiness)
+- **Walking over knee-high things** (20 cm up to about the knee, walking, the landing free): a set move instead of the feet on their own (`physics.c` startStepOver / stepOverMove, `world.c` overFeet): 0.9 s + 1.6 s per m of height (less sporty: slower); the foot further back goes first (up, over, down 16 cm past it), the hips go to between the feet, then the other foot (swung out for higher things, the body up on the toes), then the body on past it. Always finishes. Course (walk): legs in obstacles from 60/15/1 frames (thigh/shin/foot, worst 25 cm) to 8/16/1 (10 cm), joint jumps 27 -> 16
+- **Up onto things at a run** (jogging or faster): a block up to 0.95 m high with 40 cm of room on top (fixed, or 40 kg and more) is jumped onto, timed to come down on it; higher (up to 2.05 m, sober, some energy) running into it you catch it (no bouncing off) and the climb starts
 - **Over obstacles**: lean into it, the leg swings out a little and back in; lift the foot first, then over; the body waits for the back leg; knee turns out, or the foot comes up; sprinting, leaps; careless people just step on small things; trips and scrapes by speed, height, condition
 - **Bushes**: trying to get over one ends in it, twigs snap
 - **Half way over something**: from when a foot is well into a step over something (lifted 12 cm+, 15 % into the step) until the body is over there (6 cm behind where that foot came down, at most 20 cm past it), you're crossing (`body.c` crossOver): stopping (or slower than 0.3 m/s) the body carries on over it at 0.6 m/s (`physics.c` carryOver); the foot over there stays down, the other comes after. Walking back takes you back. Standing still half over costs energy (2.5 %/s, a leg held up 6 %/s; less sporty, more); worn out, held longer than 1-6 s (fitness, energy) or legs over 0.95 m apart: you're made to go over; still stuck 1.5 s later: you fall (`world.c` halfOver)
@@ -131,7 +133,7 @@ Most important first.
 - Arms brushing past things while walking ease out of them at 2 m/s: a few more small joint jumps in the audit (walk 116 -> 161, the worst 21 cm; walking into a wall and climbing it is part of that run)
 
 **Body and moving**
-- Obstacle approach, still to do: prefer a step in between on top of something stable (or unstable, if clumsy) over a long stride; too high to step over: pull up or jump and grab on their own (now only when pushing against it)
+- Obstacle approach, still to do: prefer a step in between on top of something stable (or unstable, if clumsy) over a long stride; NPCs: pull up or jump onto things on their own (the player does when running at them)
 - Climbing rework: hands first (one reaches first, shoulder ahead), a knee up, the other foot next to it; or sit on it and lift the legs; or belly first and roll; by height, surface and person
 - Balance: hips over the feet, look for stable footing, play with balance
 - Personal movement styles: gesture variants, each person a bit their own; stepping over and climbing their own way; sporty ones more elegant
@@ -154,4 +156,4 @@ Most important first.
 - The walking map rebuild takes ~9 ms when fixed things change (a felled tree)
 
 ---
-State of `entropie 1.57.0 (build 3d7cb1a)`
+State of `entropie 1.58.0 (build 20c9497)`
diff --git a/entropie/commit_message.txt b/entropie/commit_message.txt
index ae7addb..23437af 100644
--- a/entropie/commit_message.txt
+++ b/entropie/commit_message.txt
@@ -1,7 +1,10 @@
-entropie 1.57.0: timed jumps over low things; no more collapsed sleepers
+entropie 1.58.0: stepping over is one smooth move; up onto blocks at a run
 
-- Jogging or faster, low things are jumped on their own: no slowing down
-  before them, the take-off timed so the top of the jump is over them, as
-  high as it takes (and long enough to land past them)
-- People lying down or passing out before ever facing anywhere no longer
-  collapse into a heap above the floor (no fall direction: on the back)
+- Walking over something knee high is a set move: the first leg over, the
+  hips between the feet, the other leg after (higher things: up on the
+  toes, swung out); it always finishes - no more legs through things or
+  stuck half over (course, walking: legs in obstacles about a fifth)
+- Jogging or faster at a block up to about hip high with room on top: a
+  timed jump up onto it
+- Running at something you can climb: you catch the edge instead of
+  bouncing off, and the climb starts
diff --git a/entropie/src/world/object.h b/entropie/src/world/object.h
index c979827..ea756ac 100644
--- a/entropie/src/world/object.h
+++ b/entropie/src/world/object.h
@@ -180,6 +180,13 @@ typedef struct Object {
     bool       climbing;      /* characters: pulling themselves up a ledge (no physics meanwhile) */
     bool       climb_feet;    /* ... the feet go here meanwhile (world; set by whoever moves the climb) */
     Vec3       climb_ankle[2], climb_toe[2];
+    /* Stepping over something knee high (walking): a set move, not left to the
+       feet - the leading leg over, the body over it, the other leg after (Physics) */
+    float      over_t, over_time;  /* 0 = not; else how far (0..1) and how long it takes */
+    Vec3       over_from, over_dir;
+    float      over_near, over_far, over_top; /* its faces along over_dir from over_from, its top over the feet */
+    int        over_lead;          /* the foot that goes first */
+    Vec3       over_foot0[2], over_land[2];
     int        sit_style;     /* characters: SIT_GROUND (cross-legged) or SIT_SEAT */
     struct {                  /* dogs */
         int   mode;
diff --git a/entropie/src/world/physics.c b/entropie/src/world/physics.c
index 59223be..39856bc 100644
--- a/entropie/src/world/physics.c
+++ b/entropie/src/world/physics.c
@@ -41,8 +41,22 @@
 #define JUMP_PAST           0.25f  /* landing this far past it */
 #define JUMP_MOST           1.05f  /* of a full jump at most */
 #define JUMP_EARLY          0.04f  /* m: take-off a touch early rather than late */
+#define OVER_MOVE_LOWEST    0.2f   /* walking over something at least this high (scaled) is a set move ... */
+#define OVER_MOVE_FROM      0.42f  /* ... starting this close to it ... */
+#define OVER_MOVE_SLOWEST   0.3f   /* ... (walking at it, not just standing there) ... */
+#define OVER_MOVE_TIME      0.9f   /* ... taking this long ... */
+#define OVER_MOVE_HIGH      1.6f   /* ... and this much more per meter high */
+#define OVER_LAND           0.16f  /* the first foot comes down this far past it */
+#define ONTO_HIGHEST        0.95f  /* up onto things this high at most (scaled) ... */
+#define ONTO_ROOM           0.4f   /* ... with this much room on top to land ... */
+#define ONTO_HOLDS          40.0f  /* ... (loose: this heavy, else it would go over) ... */
+#define ONTO_CLEAR          0.1f   /* ... the feet this much over its edge ... */
+#define ONTO_MOST           1.25f  /* ... a full jump and the run-up at most ... */
+#define ONTO_TIMING         1.1f   /* ... over the edge just after the top of the jump */
 
 static bool jumpOver(const Object *obj, const Object *o, Vec3 dir, float speed, float *v_up, float *center);
+static bool startStepOver(Object *obj, Object *const *objects, int count, const Terrain *terrain);
+static void stepOverMove(Object *obj, const Terrain *terrain, float dt);
 static bool canJumpOver(const Object *obj, float speed);
 #define PERSON_EASE         0.008f /* m per step people standing into each other move apart at most */
 #define LAND_ON_MOST        0.12f  /* characters get onto loose things only from about this close above */
@@ -486,7 +500,7 @@ static Vec3 steppingOver(const Object *obj, Vec3 m, Object *const *objects, int
     const bool jumper = canJumpOver(obj, speed);
     for (int i = 0; i < count; i++) {
         const Object *o = objects[i];
-        if (!Physics_stepsOver(obj, o)) continue;
+        if ((o->weight < STEP_OVER_LIGHT && !o->is_static) || Physics_lowObstacle(o) <= 0.0f) continue; /* (low for its own sake) */
         float jv, jc;
         if (jumper && jumpOver(obj, o, dir, speed, &jv, &jc)) continue; /* (going to jump it: no slowing down, the run-up counts) */
         const Collider *c = &o->colliders[0];
@@ -551,11 +565,132 @@ static bool jumpOver(const Object *obj, const Object *o, Vec3 dir, float speed,
 }
 
 static bool canJumpOver(const Object *obj, float speed) {
-    return speed >= JUMP_OVER_SPEED && obj->needs.stamina >= 0.15f && obj->drunk <= 0.6f &&
+    return obj->type != OBJ_DOG && speed >= JUMP_OVER_SPEED && obj->needs.stamina >= 0.15f && obj->drunk <= 0.6f &&
            obj->hands.state[HAND_LEFT] != HAND_GRABBING && obj->hands.state[HAND_RIGHT] != HAND_GRABBING &&
            !obj->body.airborne && obj->body.pose == POSE_UP && !obj->sitting && !obj->climbing && !obj->on_ladder;
 }
 
+/* Jogging or faster at something higher than a step over (a block, a crate)
+   with room on top: up onto it in a jump, timed to come down onto it. */
+static bool jumpOnto(const Object *obj, const Object *o, Vec3 dir, float *v_up, float *edge_out) {
+    if (o->collider_count == 0 || o->is_character || o->is_held || o->lifted || o->type == OBJ_DOG ||
+        o->type == OBJ_TREE || o->type == OBJ_BUSH || o->type == OBJ_LADDER || o->type == OBJ_FIREPLACE ||
+        o->type == OBJ_DECAL || o->type == OBJ_PUDDLE || o->type == OBJ_STINK) return false;
+    if (!o->is_static && o->weight < ONTO_HOLDS) return false; /* (would tip or slide away) */
+    const float s = obj->look.height / 1.75f;
+    const Collider *c = &o->colliders[0];
+    if (c->axes[1].y < 0.95f) return false; /* (a flat top only) */
+    Vec3 rel = vec3_sub(Object_colliderCenter(o, 0), obj->position);
+    rel.y = 0.0f;
+    float reach = projectRadius(c, dir), side = projectRadius(c, vec3(-dir.z, 0.0f, dir.x));
+    float ahead = vec3_dot(rel, dir), beside = fabsf(rel.x * -dir.z + rel.z * dir.x);
+    float top = Object_colliderCenter(o, 0).y + projectRadius(c, WORLD_UP) - obj->position.y;
+    if (beside > side - 0.15f || ahead - reach < 0.05f * s || ahead - reach > JUMP_LOOK * s) return false;
+    if (top <= STEP_OVER_HEIGHT * s || top > ONTO_HIGHEST * s || 2.0f * reach < ONTO_ROOM * s) return false;
+    float v = sqrtf(2.0f * GRAVITY * fmaxf(top + (ONTO_CLEAR - JUMP_TUCK) * s, 0.1f * s));
+    if (v > Object_jumpSpeed(obj) * ONTO_MOST) return false; /* (that's a climb) */
+    *v_up = v;
+    *edge_out = ahead - reach;
+    return true;
+}
+
+/* ---------- Stepping over something knee high: a set move ---------- */
+
+/* p (feet height) inside o, with a margin? */
+static bool pointIn(const Object *o, Vec3 p, float margin) {
+    for (int c = 0; c < o->collider_count; c++) {
+        const Collider *col = &o->colliders[c];
+        Vec3 rel = vec3_sub(p, Object_colliderCenter(o, c));
+        if (fabsf(vec3_dot(rel, col->axes[0])) < col->half_extents.x + margin &&
+            fabsf(vec3_dot(rel, col->axes[1])) < col->half_extents.y + margin &&
+            fabsf(vec3_dot(rel, col->axes[2])) < col->half_extents.z + margin) return true;
+    }
+    return false;
+}
+
+/* Walking (not running) up to something knee high to step over, a step away:
+   the set move starts. Not if where the feet would land is taken. */
+static bool startStepOver(Object *obj, Object *const *objects, int count, const Terrain *terrain) {
+    float speed = sqrtf(obj->movement.x * obj->movement.x + obj->movement.z * obj->movement.z);
+    const Body *b = &obj->body;
+    if (speed < OVER_MOVE_SLOWEST || canJumpOver(obj, speed) || b->pose != POSE_UP || b->airborne || b->stumble > 0.0f ||
+        obj->sitting || obj->climbing || obj->on_ladder || b->sit > 0.05f || obj->hands.grabbed || obj->type == OBJ_DOG) return false;
+    const float s = obj->look.height / 1.75f;
+    Vec3 dir = vec3(obj->movement.x / speed, 0.0f, obj->movement.z / speed);
+    if (vec3_dot(dir, Object_facing(obj)) < 0.7f) return false; /* (backing or sidling: as it comes) */
+    for (int i = 0; i < count; i++) {
+        const Object *o = objects[i];
+        if (!Physics_stepsOver(obj, o)) continue;
+        const Collider *c = &o->colliders[0];
+        Vec3 rel = vec3_sub(Object_colliderCenter(o, 0), obj->position);
+        rel.y = 0.0f;
+        float reach = projectRadius(c, dir), side = projectRadius(c, vec3(-dir.z, 0.0f, dir.x));
+        float ahead = vec3_dot(rel, dir), beside = fabsf(rel.x * -dir.z + rel.z * dir.x);
+        float top = Object_colliderCenter(o, 0).y + projectRadius(c, WORLD_UP) - obj->position.y;
+        float near = ahead - reach, far = ahead + reach;
+        if (beside > side + 0.1f * s || near > OVER_MOVE_FROM * s || near < 0.08f * s || Physics_lowObstacle(o) < OVER_MOVE_LOWEST * s) continue;
+        top = fmaxf(top, 0.05f * s); /* (downhill it may be lower than your feet) */
+        /* where the feet come down: free? */
+        Vec3 right = vec3(-dir.z, 0.0f, dir.x);
+        float land_at = far + OVER_LAND * s;
+        bool free = true;
+        for (int k = -1; k <= 1 && free; k += 2) {
+            Vec3 p = vec3_add(obj->position, vec3_add(vec3_scale(dir, land_at), vec3_scale(right, (float)k * 0.11f * s)));
+            p.y = Terrain_height(terrain, p.x, p.z) + 0.05f;
+            for (int j = 0; j < count && free; j++)
+                if (objects[j] != obj && !objects[j]->is_character && objects[j]->collider_count > 0 && objects[j]->type != OBJ_DECAL &&
+                    objects[j]->type != OBJ_PUDDLE && objects[j]->type != OBJ_STINK && pointIn(objects[j], p, 0.08f * s)) free = false;
+        }
+        if (!free) continue;
+        obj->over_t = 1e-4f;
+        obj->over_time = (OVER_MOVE_TIME + OVER_MOVE_HIGH * top / s) * (1.25f - 0.5f * obj->fitness) / fmaxf(Object_vigor(obj), 0.6f);
+        obj->over_from = obj->position;
+        obj->over_dir = dir;
+        obj->over_near = near;
+        obj->over_far = far;
+        obj->over_top = top;
+        /* the foot further back goes first (the other stands) */
+        float a0 = vec3_dot(vec3_sub(b->feet[0].ankle, obj->position), dir), a1 = vec3_dot(vec3_sub(b->feet[1].ankle, obj->position), dir);
+        obj->over_lead = a0 <= a1 ? 0 : 1;
+        for (int k = 0; k < 2; k++) {
+            obj->over_foot0[k] = b->feet[k].ankle;
+            float lateral = clampf(vec3_dot(vec3_sub(b->feet[k].ankle, obj->position), right), -0.14f * s, 0.14f * s);
+            if (fabsf(lateral) < 0.08f * s) lateral = (k == 0 ? -0.1f : 0.1f) * s; /* (feet apart) */
+            float at = k == obj->over_lead ? land_at : land_at + 0.08f * s;
+            Vec3 land = vec3_add(obj->position, vec3_add(vec3_scale(dir, at), vec3_scale(right, lateral)));
+            land.y = Terrain_height(terrain, land.x, land.z) + (b->feet[k].ankle.y - Terrain_height(terrain, b->feet[k].ankle.x, b->feet[k].ankle.z));
+            obj->over_land[k] = land;
+        }
+        obj->movement = vec3_zero();
+        return true;
+    }
+    return false;
+}
+
+/* The body over it: up to above its middle while the first leg goes over,
+   then on past it while the other one follows. */
+static void stepOverMove(Object *obj, const Terrain *terrain, float dt) {
+    obj->over_t = fminf(1.0f, obj->over_t + dt / fmaxf(obj->over_time, 0.2f));
+    float t = obj->over_t;
+    const float s = obj->look.height / 1.75f;
+    /* (straddling it: the hips between where the first foot came down and the other one, still back there) */
+    const int lead = obj->over_lead;
+    float landed = vec3_dot(vec3_sub(obj->over_land[lead], obj->over_from), obj->over_dir);
+    float behind = vec3_dot(vec3_sub(obj->over_foot0[1 - lead], obj->over_from), obj->over_dir);
+    float mid = fmaxf(0.5f * (landed + behind), 0.05f * s), end = obj->over_far + OVER_LAND * s + 0.04f * s;
+    float along = t < 0.5f ? mid * smoothstepf(0.08f, 0.5f, t) : lerpf(mid, end, smoothstepf(0.5f, 0.95f, t));
+    Vec3 p = vec3_add(obj->over_from, vec3_scale(obj->over_dir, along));
+    p.y = Terrain_height(terrain, p.x, p.z) + (obj->over_from.y - Terrain_height(terrain, obj->over_from.x, obj->over_from.z));
+    /* (something high: up on the toes of the leading foot while the other one comes over, its knee clear of it) */
+    p.y += clampf(obj->over_top - 0.3f * s, 0.0f, 0.1f * s) * smoothstepf(0.45f, 0.6f, t) * (1.0f - smoothstepf(0.85f, 1.0f, t));
+    obj->position = p;
+    obj->velocity = obj->movement = vec3_zero();
+    obj->is_grounded = true;
+    obj->ground_speed = 0.0f;
+    Object_syncColliders(obj);
+    if (t >= 1.0f) obj->over_t = 0.0f; /* over */
+}
+
 static void hurdle(Object *obj, Object *const *objects, int count) {
     float speed = sqrtf(obj->movement.x * obj->movement.x + obj->movement.z * obj->movement.z);
     if (!canJumpOver(obj, speed)) return;
@@ -569,6 +704,15 @@ static void hurdle(Object *obj, Object *const *objects, int count) {
         obj->is_grounded = false;
         return;
     }
+    for (int i = 0; i < count; i++) {
+        float v, edge;
+        if (!jumpOnto(obj, objects[i], dir, &v, &edge)) continue;
+        /* off now: over the edge a little after the top of the jump, coming down onto it */
+        if (edge > speed * v / GRAVITY * ONTO_TIMING + JUMP_EARLY) continue;
+        obj->velocity.y = v;
+        obj->is_grounded = false;
+        return;
+    }
 }
 
 /* ---------- Public ---------- */
@@ -586,6 +730,7 @@ void Physics_useGrid(struct Grid *grid) { g_grid = grid; }
 
 void Physics_step(Object *obj, Object *const *objects, int count, const Terrain *terrain, float dt) {
     if (obj->is_static || obj->is_held) return;
+    if (obj->over_t > 0.0f) { stepOverMove(obj, terrain, dt); return; } /* (a set move) */
 
     /* At rest: skip most steps (a push or a fall wakes it) */
     if (obj->climbing || obj->on_grill || obj->toilet) return; /* moved by hand, or out of sight */
@@ -623,6 +768,10 @@ void Physics_step(Object *obj, Object *const *objects, int count, const Terrain
     }
     if (obj->is_character && obj->on_terrain) obj->movement = slopeAdjusted(obj, terrain);
     if (obj->is_character && obj->is_grounded) hurdle(obj, objects, count);
+    if (obj->is_character && obj->is_grounded && obj->velocity.y <= 0.0f && startStepOver(obj, objects, count, terrain)) {
+        stepOverMove(obj, terrain, dt);
+        return;
+    }
     if (obj->is_character && obj->is_grounded) obj->movement = carryOver(obj, obj->movement);
     if (obj->is_character && obj->is_grounded) obj->movement = steppingOver(obj, obj->movement, objects, count);
     Vec3 motion = vec3_add(obj->movement, obj->velocity);
diff --git a/entropie/src/world/world.c b/entropie/src/world/world.c
index 201fffc..7887e4a 100644
--- a/entropie/src/world/world.c
+++ b/entropie/src/world/world.c
@@ -18,6 +18,7 @@
 #define KNOCK_STAGGER      6.0f /* ... that makes them stagger */
 #define RUN_INTO          2.6f  /* m/s: sober, running into something this fast trips or staggers you */
 #define RUN_OVER_HIGH     0.75f /* lower than this (scaled) you go over it, higher you bounce off */
+#define RUN_AT_CLIMB      2.05f /* ... up to this high (the hands reach the edge) you catch it to climb it */
 #define SCRAPE_DEPTH      0.04f /* a leg this far into something (scaled): a scrape */
 #define SCRAPE_COOL       2.5f  /* seconds before the next one counts */
 
@@ -322,6 +323,13 @@ static void runInto(World *world, Object *o, float into) {
     float shaky = clampf((0.2f + 0.4f * (1.0f - o->needs.stamina)) * unsure(o) + 0.8f * fminf(o->drunk, 1.0f), 0.0f, 1.5f);
     float hard = smoothstepf(RUN_INTO, RUN_INTO + 3.0f, into);
     float bad = hard * shaky;
+    /* Something to climb, run at on purpose (sober, not worn out): the hands go
+       onto it and you stop there, ready to pull up - no bouncing off */
+    if (o->type == OBJ_PLAYER && o->bump_top >= RUN_OVER_HIGH * s && o->bump_top <= RUN_AT_CLIMB * s &&
+        o->drunk < 0.3f && o->needs.stamina > 0.2f && dice(world) > 0.5f * bad) {
+        Body_stagger(&o->body, vec3_scale(n, 0.15f + 0.2f * hard)); /* (a little give in the arms) */
+        return;
+    }
     if (o->bump_top < RUN_OVER_HIGH * s) { /* low: over it */
         if (dice(world) < 0.4f * bad) Body_fall(&o->body, vec3_normalize(vec3_add(ahead, vec3_scale(side, 0.3f * dice(world)))));
         else Body_trip(&o->body, 0, ahead, clampf(0.2f + 0.6f * bad, 0.0f, 1.0f));
@@ -331,6 +339,7 @@ static void runInto(World *world, Object *o, float into) {
     }
 }
 
+#define OVER_FEET_CLEAR  0.17f  /* stepping over: the ankles this much over it (the soles about 10 cm) */
 #define HALF_OVER_STRAIN 0.025f /* energy a second standing half over something (legs apart) ... */
 #define HALF_OVER_LIFTED 0.06f  /* ... and with a leg held up over it */
 
@@ -496,6 +505,43 @@ static void stepOn(World *world, Object *who, int foot, Vec3 ankle) {
     }
 }
 
+/* Stepping over something (the set move, see Physics): the first foot lifts
+   and goes over (up first, over, down past it), then the other one, the
+   knee up and out. The feet are clear of it all along. */
+static void overFeet(Object *o) {
+    const float s = o->look.height / 1.75f, t = o->over_t;
+    const Vec3 dir = o->over_dir;
+    const float clear = o->over_top + OVER_FEET_CLEAR * s; /* (over the feet's start height) */
+    for (int i = 0; i < 2; i++) {
+        Foot *ft = &o->body.feet[i];
+        bool lead = i == o->over_lead;
+        float u = lead ? smoothstepf(0.02f, 0.5f, t) : smoothstepf(0.5f, 0.98f, t);
+        Vec3 from = o->over_foot0[i], to = o->over_land[i];
+        float go = smoothstepf(0.18f, 0.82f, u);              /* over ... */
+        float up = smoothstepf(0.0f, 0.36f, u) * (1.0f - smoothstepf(0.72f, 1.0f, u)); /* ... once up, and down after */
+        Vec3 at = vec3_lerp(from, to, go);
+        float base = lerpf(from.y, to.y, go);
+        at.y = base + up * (clear + (o->over_from.y - base)) ;
+        /* (the trailing leg swings out a little going over) */
+        Vec3 right = vec3(-dir.z, 0.0f, dir.x);
+        float side = (i == 0 ? -1.0f : 1.0f) * (lead ? 0.03f * s : 0.09f * s + 0.5f * fmaxf(0.0f, o->over_top - 0.3f * s)) * up;
+        at = vec3_add(at, vec3_scale(right, side));
+        ft->ankle = at;
+        ft->dir = dir;
+        Vec3 toe = vec3_add(at, vec3_scale(dir, 0.14f * s));
+        toe.y = at.y - (up > 0.05f ? 0.04f : 0.06f) * s; /* (lifted: toes up a little) */
+        ft->toe = toe;
+        ft->stepping = false;
+        ft->step = 1.0f;
+        ft->step_from = ft->step_to = ft->spot_was = at;
+        ft->over = 0.0f;
+        ft->lift = 0.0f;
+    }
+    o->body.lean_goal = fmaxf(o->body.lean_goal, 0.3f);
+    o->body.crossing = false;
+    if (o->over_t >= 1.0f || o->over_t <= 0.0f) o->body.replant = false;
+}
+
 /* Hands, feet and skeleton, then carried items follow the hands. */
 static void animateCharacter(World *world, Object *o, float dt) {
     Hands_update(o, dt);
@@ -533,6 +579,7 @@ static void animateCharacter(World *world, Object *o, float dt) {
             ft->stepping = false;
         }
     if (!o->climbing) o->climb_feet = false;
+    if (o->over_t > 0.0f) overFeet(o);
     int foot;
     Vec3 ankle;
     if (Body_takeLanding(&o->body, &foot, &ankle)) stepOn(world, o, foot, ankle);