Nimbin[12]?Games / Entropie / commits / 9011e4e

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.52.0: no stopping half over things, ladder held up and aimed

Christian Immanuel · 2026-10-14 20:12 · 9011e4e4c6c01de050492a72eb6c101b70fea41b

- Stopping half way over something: the body carries on over it (the foot
  over there stays, the other comes after); walking back takes you back
- Holding still half over costs energy (a leg held up more); worn out, too
  long or legs too far apart you're made to go over, else you fall
- A slid-out ladder is held near its foot, reaching up in front (steeper
  looking up); a click leans it where you look: on the edge when aimed at
  the top, against the face lower down; looking down slides it together
 entropie/Makefile            |   2 +-
 entropie/README.md           |   7 +++
 entropie/STATE.md            |   5 +-
 entropie/commit_message.txt  |  17 +++---
 entropie/src/people/body.c   |  42 ++++++++++++++
 entropie/src/people/body.h   |   8 +++
 entropie/src/people/hands.c  |   8 +++
 entropie/src/world/ladder.c  | 128 +++++++++++++++++++++++++++++++++++++++++--
 entropie/src/world/ladder.h  |   5 +-
 entropie/src/world/object.h  |   1 +
 entropie/src/world/physics.c |  15 +++++
 entropie/src/world/world.c   |  28 ++++++++++
 12 files changed, 249 insertions(+), 17 deletions(-)

diff --git a/entropie/Makefile b/entropie/Makefile
index 0e64c91..d1ed684 100644
--- a/entropie/Makefile
+++ b/entropie/Makefile
@@ -1,5 +1,5 @@
 APP       := entropie
-VERSION   := 1.51.0
+VERSION   := 1.52.0
 
 PREFIX    ?= /usr
 BINDIR    ?= $(PREFIX)/bin
diff --git a/entropie/README.md b/entropie/README.md
index 645ba95..a1f5f35 100644
--- a/entropie/README.md
+++ b/entropie/README.md
@@ -148,6 +148,9 @@ and satellite pictures (about 340 x 340 m).
 - 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
+- Stop half way over something and the body carries on over it; you never
+  hang in a half-over pose. Holding still half over costs energy; worn out
+  (or legs too far apart) you go over, and if that's not possible you fall
 - Stepping over is a risk: you may stumble or fall, more so sprinting,
   over high things, tired, drunk or clumsy. Rushing or clumsy, a leg that
   can't keep clear scrapes ("Ouch!"), and at a run it may trip you
@@ -309,6 +312,10 @@ A hand that can't reach its grip lets go and takes hold again.
   slide it out and tip it onto the wall (about a second); slid out by hand
   first, it keeps its length. Elsewhere a click slides it out or together. Too close to something
   beside it, it says so instead of going through it
+- Slid out, you hold it near its foot and it reaches up into the air in front
+  of you (steeper when you look up). A click leans it where you look: at the
+  top of a wall it rests on the edge, lower down it leans against the face
+  there. Look down and click to slide it together
 - It leans on anything that holds it: walls, the car, a tree (not a bush),
   with level ground and room for its feet. The top rests on the edge (the
   gutter of a roof); too short for that, it leans against the face
diff --git a/entropie/STATE.md b/entropie/STATE.md
index 49e1d72..304df12 100644
--- a/entropie/STATE.md
+++ b/entropie/STATE.md
@@ -75,6 +75,7 @@ Talk topics: offer, ask, suggest, where (new woodpile spot), ask for a hand, ask
 - **Approaching obstacles**: the sporty fit their steps to a good take-off spot; sprinting fit and fresh, they leap low narrow things; running into something: trip or headlong over it if low, stagger back or fall to the side if high (speed, fitness, energy, clumsiness)
 - **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)
 - **Sure-footedness**: one factor (`world.c`, unsure: ~0.2 sporty and careful .. ~1 average .. ~2.7 clumsy and unfit) scales every trip, slip, scrape and run-into, and how bad it gets; clumsy and unfit, you also stumble over your own feet jogging or running
 - **Climbing**: hand on it (up to 1.35 m), hang and pull up (higher, both hands free), jump for the edge; ladder up and down, over the edge at the top. Walking up to something 0.7–2 m high, the hands go onto its edge first; pulling up, the feet push against the face below the edge, then the right knee and foot come over onto it, then the left (scripted in `game_actions.c`, climbFeet); knees bend up. High pull-ups: pulled up till the arms press down (hips near the edge), then the knee comes up and you rise; the hands go on ahead onto the top and stay there, the back bends over the edge as far as it takes to keep them on it (body_pose.c, reach_bend). Jumping: holding Space (or W) grabs an edge the hands reach (about 2.4 m); up to 0.35 m higher, the fingertips touch it ("Just out of reach"); hands go up wide, then onto the edge
 - **Drink and the rest**: sips, burps, sways, veers, sick, passes out, sleeps it off; pees (stream lands on the real ground, soaks in or puddles)
@@ -85,6 +86,8 @@ Talk topics: offer, ask, suggest, where (new woodpile spot), ask for a hand, ask
 
 Ladder: one click at a wall slides it out to fit (top on the edge, 0.9 m over it; up to its 6 m) and leans it; slid out by hand, it keeps that length. Putting it up is a 1.1 s animation (`ladder.c`, putUp): stood upright, slid out, tipped onto the wall; you stand still with both hands on it (`game.c`, puttingUp); it can only be climbed or taken once it leans.
 
+Raised ladder: slid out (extend goal > 0.5) and in hand, it's held 0.45 m up from its foot, the hand in front, the other hand higher up the rail, tipped up 60 deg in front (43-83 deg with the view pitch, eased 1.6 rad/s; `ladder.c` Ladder_placeHeld). A click leans it where you look (view ray, ladder length + 1.5 m; else a ray along the ladder, length + 0.8 m; a top face: the side towards you under that edge). Aimed within 0.45 m of the top it rests on the edge; lower, against the face at that height, flatter (no flatter than 55 deg: "aim at least X m up"); its foot at most 1.8 m from you. Looking down (view y < -0.6) a click slides it together; nothing aimed at: says so.
+
 Carrying long things (ladder, logs: 0.62 m and longer): they stop at people and the dog like at walls (unless you carry it together), push them out of the way (up to 25 cm a frame), knock light loose things aside; NPCs treat a long thing someone carries as in the way and walk round it (`world.c` pushOffCarried, `ai.c` ai_blockedBy).
 
 Clicking with a tool in hand: if there's something it could work on within its reach + 0.6 m and not behind you, the click is for the tool (or "Step up to it"), never a pick-up into the free hand (Tools_jobNearby).
@@ -149,4 +152,4 @@ Most important first.
 - The walking map rebuild takes ~9 ms when fixed things change (a felled tree)
 
 ---
-State of `entropie 1.51.0 (build f2fae86)`
+State of `entropie 1.52.0 (build 618b0b4)`
diff --git a/entropie/commit_message.txt b/entropie/commit_message.txt
index aa92eaf..bcad6bd 100644
--- a/entropie/commit_message.txt
+++ b/entropie/commit_message.txt
@@ -1,10 +1,9 @@
-entropie 1.51.0: lift one end of a heavy log
+entropie 1.52.0: no stopping half over things, ladder held up and aimed
 
-- A log too heavy to carry alone (but not for half its weight): held near
-  one end, that end comes up to the hip and the other drags on the ground
-- The hands slide to that end; dragging is slower; a helper joining
-  carries all of it; held in the middle it says to take it at one end
-
-Includes 1.50.0 (if not committed separately):
-- Piling styles: careful people stack neat, stable piles; careless ones
-  put pieces askew, and a high loose pile comes down now and then
+- Stopping half way over something: the body carries on over it (the foot
+  over there stays, the other comes after); walking back takes you back
+- Holding still half over costs energy (a leg held up more); worn out, too
+  long or legs too far apart you're made to go over, else you fall
+- A slid-out ladder is held near its foot, reaching up in front (steeper
+  looking up); a click leans it where you look: on the edge when aimed at
+  the top, against the face lower down; looking down slides it together
diff --git a/entropie/src/people/body.c b/entropie/src/people/body.c
index f8f7a43..a6509b2 100644
--- a/entropie/src/people/body.c
+++ b/entropie/src/people/body.c
@@ -9,6 +9,11 @@ static const float FIDGET_LENGTH[FIDGET_COUNT] = { 0.0f, 1.6f, 2.4f, 2.6f, 2.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 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) */
 
 static float random01(Body *b) {
     if (b->rng == 0) b->rng = 0x9E3779B9u ^ (unsigned)(b->clock * 7919.0f);
@@ -1012,6 +1017,8 @@ void Body_update(Body *b, const BodyFrame *f) {
         }
     }
 
+    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. */
@@ -1028,6 +1035,9 @@ void Body_update(Body *b, const BodyFrame *f) {
         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)));
@@ -1094,6 +1104,38 @@ void Body_update(Body *b, const BodyFrame *f) {
     }
 }
 
+/* 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) */
+    if (left < CROSS_DONE * s || returned || 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. */
diff --git a/entropie/src/people/body.h b/entropie/src/people/body.h
index 6394059..18dff96 100644
--- a/entropie/src/people/body.h
+++ b/entropie/src/people/body.h
@@ -139,6 +139,14 @@ typedef struct {
     bool  limb_pole_ok[4];
     float clock;
     float stumble;           /* > 0 while recovering from a misstep (seconds left) */
+    /* Half way over something (a foot stepping over it, or landed past it with
+       the body still behind): the body carries on over it (see Physics) */
+    bool  crossing;
+    bool  cross_force;       /* ... and now it must (worn out, too long, legs too wide apart) */
+    int   cross_foot;        /* the foot that went over */
+    Vec3  cross_to, cross_dir; /* where it lands, and the way over */
+    float cross_left;        /* how much further the body has to go */
+    float cross_held;        /* seconds standing still half over (costs energy) */
     float flail, flail_v;    /* arms out for balance (sprung) */
     float idle_s, idle_v;    /* standing about (0..1, sprung): hands into their habits */
     Vec3  root_vis, root_vel;/* where the hips are drawn over (world, horizontal): sprung, no jitter */
diff --git a/entropie/src/people/hands.c b/entropie/src/people/hands.c
index 2d3fe7c..244d9f3 100644
--- a/entropie/src/people/hands.c
+++ b/entropie/src/people/hands.c
@@ -885,6 +885,14 @@ void Hands_update(Object *who, float dt) {
                     }
                 }
             }
+            if (Ladder_isRaised(item)) { /* a slid-out ladder held up: the hand in front, the other higher up the rail */
+                local = vec3(sign * 0.1f * s, 0.95f * s, 0.32f * s);
+                if (h->state[1 - i] == HAND_FREE && item->raised > 0.0f) {
+                    Vec3 rail = vec3_scale(right, -sign * LADDER_WIDTH * 0.5f);
+                    b->hand_active[1 - i] = true;
+                    b->hand_target[1 - i] = vec3_add(Ladder_raisedGrip(item, 1.0f), vec3_add(rail, vec3_scale(right, -sign * 0.02f)));
+                }
+            }
             if (item->type == OBJ_CAN) { /* watering: the arm comes up and forward */
                 Vec3 lifted = vec3(sign * 0.13f * s, 1.02f * s, 0.40f * s);
                 local = vec3_lerp(local, lifted, smoothstepf(0.0f, 1.0f, who->pour));
diff --git a/entropie/src/world/ladder.c b/entropie/src/world/ladder.c
index 5819652..95eea47 100644
--- a/entropie/src/world/ladder.c
+++ b/entropie/src/world/ladder.c
@@ -1,4 +1,5 @@
 #include "world/ladder.h"
+#include <stdio.h>
 #include "world/physics.h"
 
 #define SLIDE_SPEED   0.7f    /* of the way out per second */
@@ -13,6 +14,18 @@
 #define CLIMB_SPEED   0.8f    /* meters along the ladder per second */
 #define HANDS_ABOVE   1.05f   /* hands this far above the feet (along the ladder) */
 #define MOUNT_REACH   1.2f
+#define RAISE_ANGLE   1.05f   /* a raised ladder: this steep (rad) looking straight ahead ... */
+#define RAISE_FOLLOW  0.9f    /* ... steeper looking up, flatter looking down ... */
+#define RAISE_LOW     0.75f
+#define RAISE_HIGH    1.45f
+#define RAISE_SPEED   1.6f    /* rad/s it tips up or down */
+#define RAISE_GRIP    0.45f   /* held this far up from its foot */
+#define FLATTEST      55.0f   /* degrees: no flatter than this (it would slide out) */
+#define AIM_VIEW      1.5f    /* looking at it: this much further than the ladder is long */
+#define LOOK_DOWN    -0.6f    /* looking down this steeply (view y), a click slides a held-up ladder together */
+#define AIM_FOOT      1.8f    /* its foot no further than this from you (else: step closer) */
+#define AIM_PAST      0.8f    /* held up, it leans on what its top points at up to this much past it */
+#define AIM_EDGE      0.45f   /* ... aimed this close under the top of it: the top rests on the edge */
 
 static const Vec3 UP = { 0.0f, 1.0f, 0.0f };
 
@@ -74,6 +87,7 @@ static void putUp(Object *l, float dt) {
 
 void Ladder_update(Object *l, float dt) {
     if (l->placing > 0.0f) { putUp(l, dt); return; }
+    if (l->raised > 0.0f) l->raised += clampf(l->raise_goal - l->raised, -RAISE_SPEED * dt, RAISE_SPEED * dt);
     if (l->extend == l->extend_goal) return;
     float step = SLIDE_SPEED * dt;
     l->extend = l->extend < l->extend_goal ? fminf(l->extend + step, l->extend_goal) : fmaxf(l->extend - step, l->extend_goal);
@@ -143,9 +157,66 @@ static Object *faceAhead(const World *world, const Object *who, const Object *la
     return hit;
 }
 
+/* The first solid thing along a ray (not the ladder, nor who carries it, nor people). */
+static Object *rayHit(const World *world, const Object *who, const Object *ladder, Vec3 origin, Vec3 dir, float max,
+                      Vec3 *point, Vec3 *normal) {
+    Object *hit = NULL;
+    float best = max;
+    for (int i = 0; i < world->count; i++) {
+        Object *o = world->objects[i];
+        if (o == ladder || o == who || o->is_character || o->is_held || o->collider_count == 0 || o->type == OBJ_DOG ||
+            o->type == OBJ_DECAL || o->type == OBJ_PUDDLE || o->type == OBJ_STINK) continue;
+        if (flatDist(o->position, origin) > 60.0f) continue;
+        for (int c = 0; c < o->collider_count; c++) {
+            Vec3 nn;
+            float t = rayToBox(origin, dir, Object_colliderCenter(o, c), &o->colliders[c], &nn);
+            if (t >= 0.0f && t < best) {
+                best = t;
+                hit = o;
+                *normal = nn;
+                *point = vec3_add(origin, vec3_scale(dir, t));
+            }
+        }
+    }
+    return hit;
+}
+
+/* Held up in the air: what its top points at (up to a bit past its length).
+   Its top face: the side of it towards you, just under that edge. */
+static Object *aimedAt(const World *world, const Object *who, const Object *l, Vec3 *face, Vec3 *n, float *aim_y) {
+    Object *thing = NULL;
+    if (who->type == OBJ_PLAYER && vec3_length(who->body.look_dir) > 0.5f) { /* where you look */
+        Vec3 eye = vec3_add(who->position, vec3(0.0f, 0.93f * who->look.height, 0.0f));
+        thing = rayHit(world, who, l, eye, vec3_normalize(who->body.look_dir), lengthOf(l) + AIM_VIEW, face, n);
+    }
+    if (!thing) { /* else where its top points */
+        Vec3 axis = upAlong(l), from = pointAt(l, RAISE_GRIP);
+        thing = rayHit(world, who, l, from, axis, lengthOf(l) - RAISE_GRIP + AIM_PAST, face, n);
+    }
+    if (!thing) return NULL;
+    *aim_y = face->y;
+    if (n->y > 0.5f) { /* on top of it: the edge towards you */
+        Vec3 to = flatv(vec3_sub(*face, who->position));
+        if (vec3_length(to) < 0.05f) return NULL;
+        to = vec3_normalize(to);
+        Vec3 o = vec3(who->position.x, face->y - 0.08f, who->position.z);
+        Vec3 p, nn;
+        if (rayHit(world, who, l, o, to, LEAN_REACH + 4.0f, &p, &nn) != thing) return NULL;
+        *face = p;
+        *n = nn;
+    }
+    return thing;
+}
+
 int Ladder_lean(World *world, Object *who, Object *l, bool fit, const char **why) {
     Vec3 face = vec3_zero(), n = vec3_zero();
-    Object *thing = faceAhead(world, who, l, &face, &n);
+    float aim_y = -1e9f; /* (held up: where its top should go) */
+    const bool raised = Ladder_isRaised(l);
+    /* (held up and looking down at the ground: slide it together) */
+    if (raised && who->type == OBJ_PLAYER && who->body.look_dir.y < LOOK_DOWN) return LEAN_NOTHING;
+    Object *thing = raised ? aimedAt(world, who, l, &face, &n, &aim_y) : NULL;
+    if (!thing) thing = faceAhead(world, who, l, &face, &n);
+    if (!thing && raised) { *why = "Look at where its top should go (a wall, a tree) and click - look down to slide it together"; return LEAN_NO; }
     if (!thing) return LEAN_NOTHING;
     if (fabsf(n.y) > 0.35f) { *why = "No upright side to lean it on there"; return LEAN_NO; }
     if (thing->type == OBJ_BUSH) { *why = "The bush won't hold a ladder"; return LEAN_NO; }
@@ -175,11 +246,27 @@ int Ladder_lean(World *world, Object *who, Object *l, bool fit, const char **why
     }
     const float len = Shapes_ladderLength(extend_to);
     bool reaches = h <= len * sinf(angle) - 0.15f;
+    /* aimed lower down the face (not at its top): against the face there, as high as it reaches */
+    float against = len * sinf(angle) - 0.1f;
+    float lean = angle;
+    if (aim_y > -1e8f && aim_y - ground < h - AIM_EDGE && aim_y - ground < against) {
+        /* (its top there: flatter, the foot further out - a ladder only leans so flat) */
+        reaches = false;
+        against = fmaxf(aim_y - ground, MIN_HEIGHT);
+        lean = asinf(clampf((against + 0.1f) / len, 0.0f, 1.0f));
+        if (lean < FLATTEST * PI_F / 180.0f) {
+            static char said[96];
+            snprintf(said, sizeof said, "Too long to lean that low - aim at least %.1f m up", len * sinf(FLATTEST * PI_F / 180.0f) - 0.1f);
+            *why = said;
+            return LEAN_NO;
+        }
+    }
     Vec3 rest = reaches && eaves ? vec3_add(face, vec3_scale(n, EAVES_OUT)) : face;
-    rest.y = ground + (reaches ? h : len * sinf(angle) - 0.1f);
-    Vec3 foot = vec3_add(rest, vec3_scale(n, (rest.y - ground) / tanf(angle) + 0.04f));
+    rest.y = ground + (reaches ? h : against);
+    Vec3 foot = vec3_add(rest, vec3_scale(n, (rest.y - ground) / tanf(lean) + 0.04f));
     foot.y = World_groundHeight(world, foot.x, foot.z, ground + 0.8f);
     if (fabsf(foot.y - ground) > 0.3f) { *why = "No level ground for its feet there"; return LEAN_NO; }
+    if (raised && flatDist(foot, who->position) > AIM_FOOT) { *why = "Too far away - step closer to put it up there"; return LEAN_NO; }
     Vec3 across = vec3_cross(n, UP);
     for (int s = -1; s <= 1; s += 2) {
         Vec3 p = vec3_add(foot, vec3_scale(across, (float)s * LADDER_WIDTH * 0.5f));
@@ -403,14 +490,45 @@ void Ladder_poseFeet(Object *who, float dt) {
     who->ladder_limbs_ok = true;
 }
 
+bool Ladder_isRaised(const Object *l) {
+    return l->type == OBJ_LADDER && l->is_held && l->extend_goal > 0.5f && l->placing <= 0.0f;
+}
+
+/* How steep a raised ladder is held: up with the view (more upright looking up). */
+static float raiseGoal(const Object *who) {
+    float pitch = asinf(clampf(who->body.look_dir.y, -1.0f, 1.0f));
+    if (who->type != OBJ_PLAYER) pitch = 0.0f;
+    return clampf(RAISE_ANGLE + RAISE_FOLLOW * pitch, RAISE_LOW, RAISE_HIGH);
+}
+
+/* Up the rail from the foot to where a raised ladder is held, and to the other hand. */
+Vec3 Ladder_raisedGrip(const Object *l, float along) {
+    return pointAt(l, along);
+}
+
 void Ladder_placeHeld(Object *who, int hand, Object *l) {
     Vec3 facing = Object_facing(who);
     Vec3 right = vec3_normalize(vec3_cross(facing, UP));
+    int jh = hand == HAND_LEFT ? JOINT_HAND_L : JOINT_HAND_R;
+    if (Ladder_isRaised(l)) { /* slid out: held near its foot, reaching up into the air in front */
+        l->raise_goal = raiseGoal(who); /* (eased in Ladder_update) */
+        if (l->raised <= 0.0f) l->raised = 0.15f;
+        Vec3 axis = vec3_normalize(vec3_add(vec3_scale(facing, cosf(l->raised)), vec3_scale(UP, sinf(l->raised))));
+        Vec3 z = vec3_normalize(vec3_cross(right, axis)); /* (rungs across, the rails side by side) */
+        l->rotation = euler_yxz_from_axes(axis, z);
+        Object_syncColliders(l);
+        Vec3 palm = Figure_toWorld(who->position, facing, who->skeleton.joints[jh]);
+        Vec3 foot = vec3_sub(palm, vec3_add(vec3_scale(axis, RAISE_GRIP), vec3_scale(right, (hand == HAND_LEFT ? -1.0f : 1.0f) * LADDER_WIDTH * 0.5f)));
+        l->position = foot;
+        Object_syncColliders(l);
+        l->velocity = vec3_zero();
+        return;
+    }
+    l->raised = 0.0f;
     /* on edge, along the way you walk: local Y forward, the rungs upright */
     l->rotation = euler_yxz_from_axes(facing, vec3_scale(right, -1.0f));
     Object_syncColliders(l);
-    int joint = hand == HAND_LEFT ? JOINT_HAND_L : JOINT_HAND_R;
-    Vec3 palm = Figure_toWorld(who->position, facing, who->skeleton.joints[joint]);
+    Vec3 palm = Figure_toWorld(who->position, facing, who->skeleton.joints[jh]);
     Vec3 center = vec3_sub(palm, vec3(0.0f, LADDER_WIDTH * 0.5f - 0.02f, 0.0f)); /* holding the upper rail */
     center = vec3_add(center, vec3_scale(right, hand == HAND_LEFT ? -0.05f : 0.05f));
     l->position = vec3_sub(center, l->colliders[0].offset);
diff --git a/entropie/src/world/ladder.h b/entropie/src/world/ladder.h
index 38c91e3..b4957c2 100644
--- a/entropie/src/world/ladder.h
+++ b/entropie/src/world/ladder.h
@@ -43,7 +43,10 @@ Vec3        Ladder_edge(const Object *ladder);
 /* Climbing: hands on the rungs (after Hands_update) and feet on them (after Body_update). */
 void        Ladder_poseHands(Object *who, float dt);
 void        Ladder_poseFeet(Object *who, float dt);
-/* Carried at the side, on edge. */
+/* Slid out and in hand: held near its foot, reaching up in front (steeper looking up). */
+bool        Ladder_isRaised(const Object *ladder);
+Vec3        Ladder_raisedGrip(const Object *ladder, float along);
+/* Carried at the side, on edge; slid out, raised. */
 void        Ladder_placeHeld(Object *who, int hand, Object *ladder);
 
 #endif /* WORLD_LADDER_H */
diff --git a/entropie/src/world/object.h b/entropie/src/world/object.h
index 60d7c17..0b3ab8a 100644
--- a/entropie/src/world/object.h
+++ b/entropie/src/world/object.h
@@ -202,6 +202,7 @@ typedef struct Object {
     float      bump_cool;     /* seconds until a bump counts again */
     Vec3       pee_hit;       /* characters: where the stream comes down */
     float      extend, extend_goal; /* ladders: 0 = slid together .. 1 = all the way out */
+    float      raised, raise_goal; /* ladders carried slid out: held near the foot, this steep up in front (radians, 0 = carried at the side) */
     bool       leaning;       /* ladders: leaning against something (stands still) */
     Vec3       lean_normal;   /* ladders: away from what it leans on */
     float      lean_top;      /* ladders: where along it the edge is (0 = against a face, no getting off) */
diff --git a/entropie/src/world/physics.c b/entropie/src/world/physics.c
index 4644661..757f24b 100644
--- a/entropie/src/world/physics.c
+++ b/entropie/src/world/physics.c
@@ -446,6 +446,20 @@ static Vec3 slopeAdjusted(const Object *obj, const Terrain *terrain) {
     return vec3(m.x * k, m.y, m.z * k);
 }
 
+#define CROSS_SPEED 0.6f /* m/s the body carries on over something when you stop half way */
+
+/* Half way over something and stopping (or made to): the body carries on
+   over it, after the foot that went over. Walking back takes you back. */
+static Vec3 carryOver(const Object *obj, Vec3 m) {
+    const Body *b = &obj->body;
+    if (!b->crossing || Body_isBusy(b)) return m;
+    float speed = sqrtf(m.x * m.x + m.z * m.z);
+    if (!b->cross_force && speed > 0.3f) return m; /* (walking on, or back: as you like) */
+    const float s = obj->look.height / 1.75f;
+    float go = CROSS_SPEED * s * smoothstepf(0.0f, 0.15f * s, b->cross_left); /* (easing in at the end) */
+    return vec3(b->cross_dir.x * fmaxf(go, 0.15f * s), m.y, b->cross_dir.z * fmaxf(go, 0.15f * s));
+}
+
 /* Something to step over ahead (or underfoot): slow down for it, the more the higher it is. */
 static Vec3 steppingOver(const Object *obj, Vec3 m, Object *const *objects, int count) {
     float speed = sqrtf(m.x * m.x + m.z * m.z);
@@ -581,6 +595,7 @@ 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->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);
     const Vec3 start = obj->position;
diff --git a/entropie/src/world/world.c b/entropie/src/world/world.c
index 48db74f..d266f65 100644
--- a/entropie/src/world/world.c
+++ b/entropie/src/world/world.c
@@ -331,6 +331,33 @@ static void runInto(World *world, Object *o, float into) {
     }
 }
 
+#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 */
+
+/* Standing still half over something is hard work, a leg held up over it
+   harder still: it costs energy. Worn out, too long, or the legs too far
+   apart: over you go (the body carries on); can't get over: down you go. */
+static void halfOver(Object *o, float dt) {
+    Body *b = &o->body;
+    if (!b->crossing || o->type == OBJ_DOG) return;
+    const float s = o->look.height / 1.75f;
+    const Foot *lead = &b->feet[b->cross_foot], *other = &b->feet[1 - b->cross_foot];
+    float apart = vec3_length(vec3(lead->ankle.x - other->ankle.x, 0.0f, lead->ankle.z - other->ankle.z));
+    if (o->ground_speed > 0.12f) { b->cross_held = fmaxf(0.0f, b->cross_held - 2.0f * dt); return; }
+    b->cross_held += dt;
+    float strain = (lead->stepping ? HALF_OVER_LIFTED : HALF_OVER_STRAIN) * (1.4f - 0.8f * o->fitness);
+    o->needs.stamina = fmaxf(0.0f, o->needs.stamina - strain * dt);
+    float hold = 1.0f + 5.0f * o->fitness * o->needs.stamina; /* (how long anyone keeps that up) */
+    bool broken = apart > 0.95f * s; /* (a split: nobody stands like that) */
+    if (b->cross_held > 0.4f && (o->needs.stamina < 0.03f || b->cross_held > hold || broken)) b->cross_force = true;
+    /* made to and still not getting over it: down */
+    if (b->cross_force && b->cross_held > hold + 1.5f) {
+        Body_fall(b, b->cross_dir);
+        b->crossing = b->cross_force = false;
+        b->cross_held = 0.0f;
+    }
+}
+
 static void takeBump(World *world, Object *o, float dt) {
     float into = o->bump;
     o->bump = 0.0f;
@@ -801,6 +828,7 @@ void World_update(World *world, float dt, float game_dt) {
         if (!o->is_character) continue;
         takeKnock(o);
         takeBump(world, o, dt);
+        halfOver(o, dt);
         animateCharacter(world, o, dt);
         keepCarriedClear(world, o);
         pushOffCarried(world, o);