Nimbin[12]?Games / Entropie / commits / 0a89657

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.86.0: onto the log, and the ladder on the beam

Christian Immanuel · 2026-10-15 17:17 · 0a896573746262c9d354900d840f2c93a14455a9

- Something solid that holds you (a log, a fallen tree, a heavy crate) you
  step onto and stand on, instead of straddling it; walking at the end of
  something long, you go round its end
- Fix: the ladder couldn't be leaned on a beam of the unfinished building
  (aiming missed the thin beam; stood up free it fell through it). Aiming
  is forgiving now, and a falling ladder rests on what it falls against
- A ladder held up tips down flat to get under a beam or doorway and goes
  up again after (before, you got stuck)

Includes 1.85.0 (if not committed separately):
- Somebody standing in front of a pushed load leans against it (it goes
  slower) and side-steps out of its way; the slowest just complain
- Fix: a person a pushed crate ran into got bulldozed along with it (their
  feet stalled against it)

Includes 1.84.0 (if not committed separately):
- A helper standing where the pushed or pulled load wants to go notices it,
  lets go and comes round to another side; quick-witted ones soon, slow
  ones (Ben) not at all ("Why won't it move?")
- New trait: wits (0 slow .. 1 sharp), also in the dev menu

Includes 1.83.0 (if not committed separately):
- Peeing standing: one hand holds it (the Urinella to the body), the other
  stays at the belt
- A full ashtray (12 butts) gets emptied by somebody tidy: into a scrap of
  paper, off to the bin
- Fix: a tidy person could stand still for minutes, reaching again and
  again for trash just out of reach

Includes 1.82.0 (if not committed separately):
- Walking styles: feet wider or narrower, toes out more or less, bouncier
  or flatter (livelier when lively), hips swaying more or less
- The ashtray stands on the cool box by the fire (on the ground it was in
  everybody's way)

Includes 1.81.0 (if not committed separately):
- Fix: following someone on a chat break (and walking up to things) went
  walk-stop-walk every frame, the whole body shook; now they slow down
  coming up and stay with them
- Fix: an elbow could flip over when a hand went up past the shoulder
  (scratching the head); it turns round the arm now

Includes 1.80.0 (if not committed separately):
- An old tin by the fire as ashtray: you and the tidy put butts in there
- The Urinella is drawn while it's used; the dress is lifted higher
- After peeing you take up again what you put down
- The others can slip off a wobbly ladder too

Includes 1.79.0 (if not committed separately):
- Helpers (G) follow you up a leaning ladder onto a roof or wall and down
  again; without a ladder they pull themselves up after you (if they reach)
  and jump down when you're down
- Pulling up is a module for anybody now (people/climb.c), not only you

Includes 1.78.0 (if not committed separately):
- Cold nights: away from the fire a jacket goes on (each their own colour)
- Working hard in the midday sun your shirt comes off too
- Women who never tried a Urinella may try one: some like it, some don't
- Dying for a smoke makes restless; a wobbling ladder rocks
- Moving heavy wood: tested with a called helper; the player is asked when
  nobody else is around

Includes 1.77.0 (if not committed separately):
- Ladders: carried nearly upright when out; stood up on their own they
  fall over; leaned, how steady they stand depends on angle, ground, feet
  and what holds them; a wobbly one wobbles, then slips and falls with you
- Somebody holds it (hands on the rails, a foot on the bottom rung):
  helpers (G) when you climb, helpful people when it looks wobbly
- The work place: the tidy clear away what lies where they'd stand; no room
  to swing, they take the piece somewhere with room (carried, or dragged
  with somebody called to help)
- The sporty pull up and climb ladders much quicker

Includes 1.76.0 (if not committed separately):
- Smoking: packs and tobacco in the pockets (used up); smokers get a
  craving and light up in a quiet moment - rolling one first, the lighter
  to it, drags (the tip glows), smoke breathed out, the butt flicked away
- Shared: "Got a light?", bumming a cigarette (or a no); talk to ask for
  one or offer one; K lights yours / puts it out; Tab shows your pockets
- Shadows: soft, smooth edges instead of stair steps

Includes 1.75.0 (if not committed separately):
- Urinellas: one-time use, up to 4 in the pocket; a box by the toilet
  refills it (the others fetch more when they run out, you click it)
- Peeing: what's in the hands is put down first (the others take it up
  again); standing, the fly is open or the skirt or dress lifted
- Lighter: needed to light the fire, runs empty; borrowed when there's
  none, given back (or not), owners ask for it; talk: borrow, give back,
  ask for yours back
- Working hard in the midday sun the shirt comes off (women keep a top)
- Dropping from high up: knees pulled up, legs reach for the ground, deep
  into the knees, a roll or a crash
- Ladders: one-handed climbing (you may slip), a lying ladder is stepped
  over, kicked it hardly moves and your toe hurts
- Dev menu: teleport to the parkour / yard; the player spawns 1 m up

Includes 1.74.0 (if not committed separately):
- Fix: legs showed through a skirt or dress standing normally; the thighs
  under it are cloth now and it's wider over the hips
- Mila's Urinellas come in a pack of 3 (then she squats); now and then a
  mishap (on the shoes); used ones go into the bag, are dropped or flung
  away (drunk) as trash

Includes 1.73.0 (if not committed separately):
- Clothes: trousers, shorts, skirt, dress; squatting to pee trousers come
  down, a skirt or dress is gathered up

Includes 1.72.0 (if not committed separately):
- Fix: walking off the other way from something half stepped over left
  one foot planted behind, sliding along

Includes 1.71.0 (if not committed separately):
- Two-handed fidgets: rubbing the hands, hands behind the head, both
  hands on the hips

Includes 1.70.0 (if not committed separately):
- More one-hand gestures (neck, nose, ear, hair, thumb in the belt, hand
  on the chest, shading the eyes); mood and personal habits pick them

Includes 1.69.0 (if not committed separately):
- Faster with a crowd: 750 people 22 -> 14 ms a frame (kind index, cell
  grid for company, no foot checks far from you)

Includes 1.68.0 (if not committed separately):
- The walking map is updated in place when fixed things change
  (0.3 ms instead of 10 ms)

Includes 1.67.0 (if not committed separately):
- Pour your drink on the fire (C): a small fire goes out, a big one
  hisses, vodka flares up

Includes 1.66.0 (if not committed separately):
- Careful people sort the woodpile by length (front even) and put the
  branches lying about onto a heap at the end of the woodpile

Includes 1.65.0 (if not committed separately):
- Smooth off the ladder (a step back, one foot after the other) and
  taking it down (the hand reaches for the rail at chest height)

Includes 1.64.0 (if not committed separately):
- Smooth ladder and climbs: getting on, over the edge at the top, feet
  from where they are; the easy 1 m climb no longer drops you off again

Includes 1.63.0 (if not committed separately):
- The hose melts in the fire (leaks, burns through); duct tape and the
  shears mend it

Includes 1.62.0 (if not committed separately):
- Smooth pull-ups: no hop at the end, feet and knees ease over the edge,
  elbows and hands taking hold blend in (audit walk: joint jumps 161 -> 58)

Includes 1.61.0 (if not committed separately):
- A step ladder in the tool room: stands on its own, at a wall with its
  back to it; climb onto the wall from its top

Includes 1.60.0 (if not committed separately):
- A folding ladder (two 2.5 m halves on a hinge, 5 m open) beside the
  telescopic one; one click at a wall unfolds it and leans it there

Includes 1.59.0 (if not committed separately):
- Stepping up onto blocks above knee high; set moves and jumps only for
  things standing on the ground in front of you

Includes 1.58.0 (if not committed separately):
- Walking over knee-high things is one smooth set move that always
  finishes; at a run: jump up onto blocks, catch climbable edges
 entropie/Makefile            |  2 +-
 entropie/README.md           | 10 +++++-
 entropie/STATE.md            | 10 ++++--
 entropie/commit_message.txt  | 20 ++++++++++--
 entropie/src/game/game.c     |  8 +++++
 entropie/src/people/ai.c     | 36 ++++++++++++++++++++++
 entropie/src/people/ai.h     |  3 ++
 entropie/src/people/hands.c  | 37 ++++++++++++++++++++++
 entropie/src/world/ladder.c  | 73 +++++++++++++++++++++++++++++++++++++++-----
 entropie/src/world/ladder.h  |  2 +-
 entropie/src/world/object.h  |  4 +++
 entropie/src/world/physics.c | 72 ++++++++++++++++++++++++++++++++++++++-----
 entropie/src/world/world.c   | 25 ++++++++++++++-
 13 files changed, 279 insertions(+), 23 deletions(-)

diff --git a/entropie/Makefile b/entropie/Makefile
index 5eb61c0..57f525c 100644
--- a/entropie/Makefile
+++ b/entropie/Makefile
@@ -1,5 +1,5 @@
 APP       := entropie
-VERSION   := 1.84.0
+VERSION   := 1.86.0
 
 PREFIX    ?= /usr
 BINDIR    ?= $(PREFIX)/bin
diff --git a/entropie/README.md b/entropie/README.md
index 8e29525..02e41b3 100644
--- a/entropie/README.md
+++ b/entropie/README.md
@@ -150,6 +150,9 @@ and satellite pictures (about 340 x 340 m).
   stepping over a fence) or the foot comes up; sprinting, you leap
 - Stepping over, you lean into it and the leg swings round a little, out to
   the side and back in to land, while the momentum carries you on
+- Something solid that holds you (a log, a fallen tree, a heavy crate, from
+  about knee-low up) you step onto instead: you stand up there and walk on
+- Walking at the end of something long, you go round the end instead
 - The sporty fit their steps to what's coming, so a foot lands at a good
   take-off spot
 - Jogging or faster, low things (up to about the knee) are jumped on their
@@ -227,6 +230,8 @@ and satellite pictures (about 340 x 340 m).
   at what you push and pushes along. Press again for more helpers, H sends them off
 - A helper standing where the load wants to go notices it, lets go and comes
   round to another side (the quick-witted soon, the slow never)
+- Anybody else in the way leans against the load (it goes slower) and side-steps
+  out of it; the slowest just say "Hey! Watch it!"
 - Not sure what you're up to, helpers copy you: they take something like what
   you carry, walk along at your side and do the same with it (put it down
   next to yours, into the fire, into the bin)
@@ -354,7 +359,10 @@ A hand that can't reach its grip lets go and takes hold again.
   heavy to carry). 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. Nothing to lean on: you stand it up on its
-  own - and it falls over
+  own - and it falls over, or against something (a beam): there it rests
+- A thin beam counts even if you look just past it
+- Held up, it runs into a beam or a doorway: you tip it down flat, carry it
+  under and it goes up again once there's room
 - It leans on whatever is there. How steady it stands depends on the angle
   (about 75 degrees is best), soft or muddy ground, its feet not level and
   what holds it (a bush, something light). The message says when it's wobbly
diff --git a/entropie/STATE.md b/entropie/STATE.md
index 55341dd..04dc9bd 100644
--- a/entropie/STATE.md
+++ b/entropie/STATE.md
@@ -69,6 +69,12 @@ Shadows: 3x3 PCF with each tap blended bilinearly between its 4 texels (`common.
 
 Making room (`ai_help.c` makeRoom, every frame in AI_HELP): the leader wants to go (`movement` 0.2+ m/s) and the helper stands in the load's way (`inLoadsWay`: in front of it along that direction, within its width + 0.35 m). After 0.8 s + 2.5 x (1 - wits) the load's progress is checked (from `jam_from`): holding on and it goes 35 %+ of the speed: fine; else (or not holding on: shoved along) wits under 0.35 (minus 0.6 x drunk): "Why won't it move?" and nothing; else let go, "Wait - I'm in the way", and for 6 s (`clear_time`, `clear_dir`) spots in the load's way are no good spots. `wits` (object.h, 0.5 by default; table in scene.c; dev menu row).
 
+Bystanders (`hands.c` inTheWay, in Hands_solveGrabs): people not holding a pushed load that it would go into (moved a step ahead) count against it with their weight (BYSTANDER_HOLD 1.0) and get `shoved` (the way it goes, `shove_load` its middle; cleared every frame). `ai.c` stepAside (not helpers, not sitting): after 0.25 + 1.5 x (1 - wits) s a quick side step to the side they're already off to (0.8 s at 1.6 m/s, no turning first); wits under 0.2: "Hey! Watch it!" and 4 s not minding it. While shoved, a standing leg against something doesn't hold the body back (`physics.c` steppingOver: their feet stalled against the load before, they got bulldozed along).
+
+Onto, not over (`physics.c`): `standsOn` (static or ONTO_HOLDS 40 kg+, a side facing straight up - boxes, logs and fallen trees lying; not bottles, furniture, bins, supplies, ladders, the fire): the player steps up onto it (startStepOver `up`, STAND_UP_TIME 0.4 s + 0.9 s/m) when it's STAND_LOWEST 0.22+ high with STAND_ROOM 0.28 m on top, instead of straddling it; lands in its middle. A lying tree (toppled, topple 1) is no longer neverOver. `pastTheEnd` (all characters): something 1.4 m+ long across your way that you'd only catch with up to 0.55 m of you, within 1.6 m: the walk bends past its end. NPCs still step over (and round) as before.
+
+Ladders and things in the way (`ladder.c`): aiming with the view also tries 0.05 / 0.1 rad above and below (thin beams); hitting a beam from below takes the side towards you. Falling over (`fallOver`, now with the world: `Ladder_update(world, l, dt)`) it stops against anything solid it goes into (`caught`: leaning there, CAUGHT_STEADY 0.45, `lean_top` if its top is over the thing; `just_caught` -> the game says so). Carried held up (`world.c` dipLadder): running into something -> `dipped` (raise goal RAISE_DIP 0.12, nearly flat); down and clear, up again once it would stand up at 1.36 without going into anything.
+
 Climbing for anybody (`people/climb.c`, moved out of the game): `Climb_ledgeAhead`, `Climb_start` (says why not: hands, strength, drunk), `Climb_update` (the pull up, its feet and hands), `Climb_overLadderTop`; the move lives in the person (`climb_move`). Helpers (`ai_help.c` ai_followByLadder): the leader 1.2 m higher (standing, not climbing): a leaning ladder within 25 m from their level to the leader's (top within 1.3 m) - to its foot, up (`ai_climbing`: on a ladder or pulling over its top this runs first, whatever the mode), over the edge; none, and the leader within reach (CLIMB_REACH): walk at it and pull up where the edge ahead is at the leader's level ("Hup!"). The leader 1.2 m lower: to the top of a ladder from up there and down it; none: they just walk off (the landing does the rest). A ladder leading nowhere (no top to get off at): down again after 8 s. `Ai_useWorld` gives the AI the world for this.
 
 Walking styles (`Body.style[4]`, -1..1 per person, made from the quirks - no extra dice, so nothing else changes): feet apart x (1 + 0.15 style0), toes out + 0.04 rad x style1, the bob x (1 + 0.35 style2 + 0.25 lively), the hips swaying over the standing foot x (1 + 0.35 style3). (The audit swings both ways with it - it changes where everybody goes; overall the player's numbers are lower.)
@@ -187,7 +193,7 @@ Most important first.
 - Joint jumps left in the audit: jog+stop 171, sprint 297 (worst 26 cm) - running into things: a hurried catch step swings through in a few frames (step 0.07 -> 0.34, the ankle up 20 cm). Capping the swing speed helped sprint (278) but made jumps, drunk walk and the NPCs worse - tried and reverted in 1.83; ladder 45: toes on the sloping roof (a landing step's toes snap onto the slope, 16 cm; blending them in made all walking worse), toes turning onto the rungs getting on from the top (14 cm)
 
 **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; NPCs: pull up on their own for their own reasons (now only following a leader), jumping onto things
+- Obstacle approach, still to do: NPCs stepping onto solid things too (now only you); balancing on a log (arms out, a wobble); a step in between on top of something unstable (if clumsy); NPCs: pull up on their own for their own reasons (now only following a leader), jumping onto things
 - 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, still to do: gesture variants; stepping over and climbing their own way; sporty ones more elegant (walking styles are in)
@@ -207,4 +213,4 @@ Most important first.
 - Drawing many figures on a real graphics card: not measured (software GL: 300 people cost 3x the rest of the scene)
 
 ---
-State of `entropie 1.84.0 (build 0f5b1e3)`
+State of `entropie 1.86.0 (build 92de65a)`
diff --git a/entropie/commit_message.txt b/entropie/commit_message.txt
index 1c8e7a3..9888792 100644
--- a/entropie/commit_message.txt
+++ b/entropie/commit_message.txt
@@ -1,5 +1,21 @@
-entropie 1.84.0: helpers get out of the way
-
+entropie 1.86.0: onto the log, and the ladder on the beam
+
+- Something solid that holds you (a log, a fallen tree, a heavy crate) you
+  step onto and stand on, instead of straddling it; walking at the end of
+  something long, you go round its end
+- Fix: the ladder couldn't be leaned on a beam of the unfinished building
+  (aiming missed the thin beam; stood up free it fell through it). Aiming
+  is forgiving now, and a falling ladder rests on what it falls against
+- A ladder held up tips down flat to get under a beam or doorway and goes
+  up again after (before, you got stuck)
+
+Includes 1.85.0 (if not committed separately):
+- Somebody standing in front of a pushed load leans against it (it goes
+  slower) and side-steps out of its way; the slowest just complain
+- Fix: a person a pushed crate ran into got bulldozed along with it (their
+  feet stalled against it)
+
+Includes 1.84.0 (if not committed separately):
 - A helper standing where the pushed or pulled load wants to go notices it,
   lets go and comes round to another side; quick-witted ones soon, slow
   ones (Ben) not at all ("Why won't it move?")
diff --git a/entropie/src/game/game.c b/entropie/src/game/game.c
index d330724..e608a3e 100644
--- a/entropie/src/game/game.c
+++ b/entropie/src/game/game.c
@@ -249,6 +249,14 @@ void Game_update(Game *game, const Input *input, float dt, float aspect) {
     game_climbLadder(game, input, dt);
     game_climb(game, input, dt);
     World_update(&game->world, dt, dt * game->daycycle.time_scale * speed);
+    for (int i = 0; i < game->world.count; i++) { /* a ladder that fell against something near you */
+        Object *l = game->world.objects[i];
+        if (l->type != OBJ_LADDER || !l->just_caught) continue;
+        l->just_caught = false;
+        if (vec3_length(vec3_sub(l->position, game->player->position)) < 15.0f)
+            say(game, l->lean_top > 0.0f ? "It falls against it and rests there - not too steady (G: somebody holds it)"
+                                         : "It falls against it and stays leaning there - not too steady");
+    }
     if (game->player->carry_blocked) { /* what you carry hit something: the view doesn't turn on either */
         game->camera.yaw = (game->player->rotation.y - PI_F / 2.0f) * 180.0f / PI_F;
         Camera_rotate(&game->camera, 0.0f, 0.0f);
diff --git a/entropie/src/people/ai.c b/entropie/src/people/ai.c
index b99f9ee..54540f3 100644
--- a/entropie/src/people/ai.c
+++ b/entropie/src/people/ai.c
@@ -1248,6 +1248,41 @@ static void smokeBreak(Object *self, Object *const *objects, int count, float dt
     }
 }
 
+/* A pushed load shoving us along: out of its way to one side, after a moment
+   to see what's going on (the slower, the longer; the slowest just get shoved).
+   Helpers sort it out their own way (ai_help.c makeRoom). */
+#define DODGE_TIME  0.8f
+#define DODGE_SPEED 1.6f /* m/s */
+
+static bool stepAside(Object *self, float dt) {
+    Ai *ai = &self->ai;
+    if (ai->dodge > 0.0f) {
+        ai->dodge -= dt; /* (a quick side step, no turning round first) */
+        self->movement = vec3_scale(ai->dodge_dir, DODGE_SPEED * Object_vigor(self));
+        Vec3 f = Object_facing(self), r = vec3_normalize(vec3_cross(f, UP));
+        self->move_local = vec3(vec3_dot(ai->dodge_dir, r), 0.0f, vec3_dot(ai->dodge_dir, f));
+        ai->walk_dir = vec3_zero();
+        return true;
+    }
+    if (ai->shove_time < 0.0f) { ai->shove_time = fminf(0.0f, ai->shove_time + dt); return false; }
+    if (vec3_length(self->shoved) < 0.5f || ai->mode == AI_HELP || self->sitting) { ai->shove_time = 0.0f; return false; }
+    float wits = clampf(self->wits - 0.6f * fminf(self->drunk, 1.0f), 0.0f, 1.0f);
+    if ((ai->shove_time += dt) < 0.25f + 1.5f * (1.0f - wits)) return false;
+    if (wits < 0.2f) { /* doesn't get it: stands against it */
+        Ai_say(self, "Hey! Watch it!");
+        ai->shove_time = -4.0f;
+        return false;
+    }
+    Vec3 across = vec3_cross(self->shoved, UP);
+    float off = vec3_dot(vec3_sub(self->position, self->shove_load), across); /* (off to the side it's nearer) */
+    float side = fabsf(off) > 0.05f ? (off > 0.0f ? 1.0f : -1.0f) : (random01(ai) < 0.5f ? -1.0f : 1.0f);
+    ai->dodge_dir = vec3_scale(across, side);
+    ai->dodge = DODGE_TIME;
+    ai->shove_time = 0.0f;
+    if (random01(ai) < 0.6f) Ai_say(self, random01(ai) < 0.5f ? "Whoa - careful!" : "Oops, I'm in the way.");
+    return true;
+}
+
 void Ai_update(Object *self, Object *const *objects, int count, float dt) {
     ai_ensureReady(self);
     Ai *ai = &self->ai;
@@ -1283,6 +1318,7 @@ void Ai_update(Object *self, Object *const *objects, int count, float dt) {
     lighterManners(self, objects, count, dt);
     smokeBreak(self, objects, count, dt);
     if (ai_climbing(self, dt)) return; /* (up or down a ladder, over an edge: that first) */
+    if (stepAside(self, dt)) return;   /* (a pushed load coming at us) */
     if (ai->mode != AI_IDLE && !(ai->mode == AI_WORK && ai->order == ORDER_FIRE)) self->sitting = false; /* up again to do anything */
     smell(self, objects, count, dt);
     if (ai->talking) {
diff --git a/entropie/src/people/ai.h b/entropie/src/people/ai.h
index 057f858..008c50a 100644
--- a/entropie/src/people/ai.h
+++ b/entropie/src/people/ai.h
@@ -148,6 +148,9 @@ typedef struct {
     float          clear_time;  /* ... so we keep off that side a while */
     Vec3           clear_dir;   /* ... (the way the load wants to go) */
     Vec3           jam_from;    /* (where the load was when we started to wonder) */
+    float          shove_time;  /* a pushed load has been shoving us this long (negative: a while not minding it) */
+    float          dodge;       /* stepping out of its way (seconds left) ... */
+    Vec3           dodge_dir;   /* ... this way */
 
     /* Talking */
     Topic          topic;       /* AI_APPROACH: what they want to talk about */
diff --git a/entropie/src/people/hands.c b/entropie/src/people/hands.c
index dbb66a6..6a13fdc 100644
--- a/entropie/src/people/hands.c
+++ b/entropie/src/people/hands.c
@@ -759,7 +759,37 @@ static float levelLift(Object *o, Object *const *team, int n, float dt) {
     return base - o->colliders[0].half_extents.x; /* its underside at the middle */
 }
 
+/* People (not holding it) the load would go into, moved by move: their
+   weight (they stand against it), and each gets told it shoves them. */
+#define BYSTANDER_HOLD 1.0f /* x their weight against the load */
+
+static float inTheWay(Object *o, Object *const *team, int n, Object *const *objects, int count, Vec3 move) {
+    Vec3 dir = vec3(move.x, 0.0f, move.z);
+    float len = vec3_length(dir);
+    if (len < 1e-5f) return 0.0f;
+    dir = vec3_scale(dir, 1.0f / len);
+    Vec3 was = o->position;
+    o->position = vec3_add(was, vec3_scale(dir, fmaxf(len, 0.02f))); /* (a little ahead) */
+    Object_syncColliders(o);
+    float weight = 0.0f;
+    for (int k = 0; k < count; k++) {
+        Object *c = objects[k];
+        if (!c->is_character || c->hands.grabbed == o || c->on_ladder || c->climbing) continue;
+        bool mine = false;
+        for (int t = 0; t < n; t++) mine = mine || team[t] == c;
+        if (mine || fabsf(c->position.x - was.x) > 4.0f || fabsf(c->position.z - was.z) > 4.0f) continue;
+        if (vec3_dot(vec3_sub(c->position, was), dir) <= 0.0f || Physics_penetration(o, c) <= 0.0f) continue;
+        weight += c->weight;
+        c->shoved = dir;
+        c->shove_load = was;
+    }
+    o->position = was;
+    Object_syncColliders(o);
+    return weight;
+}
+
 void Hands_solveGrabs(Object *const *objects, int count, float dt) {
+    for (int i = 0; i < count; i++) objects[i]->shoved = vec3_zero(); /* (told again below while it goes on) */
     for (int i = 0; i < count; i++) {
         Object *o = objects[i];
         if (o->is_static || o->is_character) continue;
@@ -797,6 +827,13 @@ void Hands_solveGrabs(Object *const *objects, int count, float dt) {
            strength allows; whatever it doesn't follow holds each of them back. */
         float share = o->end_up ? END_DRAG : o->lifted ? 1.0f : teamShare(capacity, o->weight);
         Vec3 move = vec3_scale(sum, share / (float)n);
+        if (!o->lifted && !o->end_up && share > 0.0f) { /* somebody in the way: they lean against it (and get told) */
+            float in_way = inTheWay(o, team, n, objects, count, move);
+            if (in_way > 0.0f) {
+                share = teamShare(capacity, o->weight + BYSTANDER_HOLD * in_way);
+                move = vec3_scale(sum, share / (float)n);
+            }
+        }
         if (o->end_up) {
             move.y = 0.0f;
         } else if (o->lifted) {
diff --git a/entropie/src/world/ladder.c b/entropie/src/world/ladder.c
index 51a3b8c..1d52e0b 100644
--- a/entropie/src/world/ladder.c
+++ b/entropie/src/world/ladder.c
@@ -22,6 +22,7 @@
 #define RAISE_LOW     1.15f
 #define RAISE_HIGH    1.52f
 #define RAISE_SPEED   1.6f    /* rad/s it tips up or down */
+#define RAISE_DIP     0.12f   /* tipped down to get under something: nearly flat */
 #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 */
@@ -122,8 +123,55 @@ static void putUp(Object *l, float dt) {
     }
 }
 
+static float topOf(const Object *o, float x, float z, float from_y);
+
+/* How deep the ladder is in solid things (not people, the dog, what's carried), and the deepest one. */
+static float solidIn(const World *world, const Object *l, Object **deepest) {
+    float most = 0.0f;
+    if (deepest) *deepest = NULL;
+    for (int i = 0; world && i < world->count; i++) {
+        Object *o = world->objects[i];
+        if (o == l || 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 || o->type == OBJ_BUSH) continue;
+        if (!o->is_static && o->weight < HOLDS_WEIGHT) continue; /* (light things it just knocks aside) */
+        if (flatDist(o->position, l->position) > 30.0f && !(o->is_static && vec3_length(o->colliders[0].half_extents) > 4.0f)) continue;
+        float d = Physics_penetration(l, o);
+        if (d > most) { most = d; if (deepest) *deepest = o; }
+    }
+    return most;
+}
+
+/* Falling against something on the way down (a beam, a wall, a tree): it stops
+   there and leans on it - not as steady as one put up properly. */
+#define CAUGHT_STEADY 0.45f
+
+static bool caught(World *world, Object *l, Vec3 axis_was, Vec3 face_was, float before) {
+    Object *thing;
+    if (solidIn(world, l, &thing) <= before + 0.01f || !thing) return false;
+    l->rotation = euler_yxz_from_axes(axis_was, face_was);
+    Object_syncColliders(l);
+    l->tip = l->tip_v = 0.0f;
+    l->leaning = true;
+    l->lean_normal = vec3_normalize(vec3_scale(l->tip_dir, -1.0f));
+    l->place_axis[1] = axis_was;
+    l->place_face[1] = face_was;
+    /* its top over what caught it (far enough past it to get off there)? */
+    float top = -1.0f;
+    for (float along = lengthOf(l) - 0.1f; along > 0.5f; along -= 0.1f) {
+        Vec3 p = pointAt(l, along);
+        float t = topOf(thing, p.x, p.z, p.y + 0.5f);
+        if (t > -1e8f && t > p.y - 0.25f) { top = along; break; }
+    }
+    l->lean_top = top > 0.0f && lengthOf(l) - top > 0.3f ? top : 0.0f;
+    l->stability = CAUGHT_STEADY;
+    l->just_caught = true;
+    return true;
+}
+
 /* Falling over about its foot, faster and faster, then it lies there. */
-static void fallOver(Object *l, float dt) {
+static void fallOver(World *world, Object *l, float dt) {
+    Vec3 axis_was = upAlong(l), face_was = l->colliders[0].axes[2];
+    float before = world ? solidIn(world, l, NULL) : 0.0f;
     float len = fmaxf(lengthOf(l), 1.0f);
     float from = acosf(clampf(vec3_dot(l->tip_from, UP), -1.0f, 1.0f)); /* (how far it leaned already) */
     float theta = from + l->tip;
@@ -147,6 +195,7 @@ static void fallOver(Object *l, float dt) {
     face = vec3_length(face) > 1e-3f ? vec3_normalize(face) : vec3_normalize(vec3_cross(axis, vec3(axis.z, 0.0f, -axis.x)));
     l->rotation = euler_yxz_from_axes(axis, face);
     Object_syncColliders(l);
+    if (world) caught(world, l, axis_was, face_was, before);
 }
 
 void Ladder_topple(World *world, Object *l, Vec3 dir) {
@@ -187,9 +236,9 @@ Vec3 Ladder_holdSpot(const Object *l, Vec3 *facing) {
     return at;
 }
 
-void Ladder_update(Object *l, float dt) {
+void Ladder_update(World *world, Object *l, float dt) {
     if (l->placing > 0.0f) { putUp(l, dt); return; }
-    if (l->tip_v > 0.0f) { fallOver(l, dt); return; }
+    if (l->tip_v > 0.0f) { fallOver(world, l, dt); return; }
     if (l->shake > 0.0f && l->leaning) { /* wobbling: the top rocks from side to side, dying away */
         l->shake = fmaxf(0.0f, l->shake - dt);
         Vec3 axis = l->place_axis[1], across = vec3_normalize(vec3_cross(l->lean_normal, UP));
@@ -296,9 +345,16 @@ static Object *rayHit(const World *world, const Object *who, const Object *ladde
    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 */
+    if (who->type == OBJ_PLAYER && vec3_length(who->body.look_dir) > 0.5f) { /* where you look (a thin beam: a little above or below it does too) */
         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);
+        Vec3 look = vec3_normalize(who->body.look_dir), level = flatv(look);
+        float pitch = asinf(clampf(look.y, -1.0f, 1.0f));
+        static const float NEAR_MISS[] = { 0.0f, 0.05f, -0.05f, 0.1f, -0.1f };
+        for (int k = 0; k < 5 && !thing && vec3_length(level) > 1e-3f; k++) {
+            float a = pitch + NEAR_MISS[k];
+            Vec3 dir = vec3_add(vec3_scale(vec3_normalize(level), cosf(a)), vec3_scale(UP, sinf(a)));
+            thing = rayHit(world, who, l, eye, dir, lengthOf(l) + AIM_VIEW, face, n);
+        }
     }
     if (!thing) { /* else where its top points */
         Vec3 axis = upAlong(l), from = pointAt(l, RAISE_GRIP);
@@ -306,11 +362,11 @@ static Object *aimedAt(const World *world, const Object *who, const Object *l, V
     }
     if (!thing) return NULL;
     *aim_y = face->y;
-    if (n->y > 0.5f) { /* on top of it: the edge towards you */
+    if (fabsf(n->y) > 0.5f) { /* on top of it (or under it: a beam from below): the side 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 o = vec3(who->position.x, face->y + (n->y > 0.0f ? -0.08f : 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;
@@ -794,7 +850,7 @@ void Ladder_placeHeld(Object *who, int hand, Object *l) {
     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) */
+        l->raise_goal = l->dipped ? RAISE_DIP : 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) */
@@ -808,6 +864,7 @@ void Ladder_placeHeld(Object *who, int hand, Object *l) {
         return;
     }
     l->raised = 0.0f;
+    l->dipped = false;
     /* 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);
diff --git a/entropie/src/world/ladder.h b/entropie/src/world/ladder.h
index 5913ae6..4a07f5b 100644
--- a/entropie/src/world/ladder.h
+++ b/entropie/src/world/ladder.h
@@ -21,7 +21,7 @@ void        Ladder_slide(Object *ladder);
 /* Slid out this far (0..1) right away: mesh and collider too. */
 void        Ladder_setExtend(Object *ladder, float extend);
 /* Eases the sliding along, rebuilding mesh and collider as it goes. */
-void        Ladder_update(Object *ladder, float dt);
+void        Ladder_update(World *world, Object *ladder, float dt); /* (world: what it falls against; may be NULL) */
 
 #define LADDER_FOLDING 1 /* variant: two sections on a hinge (unfolds); 0: telescopic (slides out) */
 #define LADDER_STEP    2 /* variant: a step ladder (opens and stands on its own) */
diff --git a/entropie/src/world/object.h b/entropie/src/world/object.h
index aaffa42..39c69d2 100644
--- a/entropie/src/world/object.h
+++ b/entropie/src/world/object.h
@@ -158,6 +158,8 @@ typedef struct Object {
     bool       lifted;        /* carried by a team: no gravity, moved by their hands */
     bool       end_up;        /* ... only one end lifted (long and too heavy for them): the other drags on the ground */
     Vec3       knock;         /* characters: momentum of things that hit them this step */
+    Vec3       shoved;        /* characters: a pushed load is shoving them this way (zero: not) ... */
+    Vec3       shove_load;    /* ... the load's middle */
     bool       toppled;       /* tipped over (bottles) */
     float      topple;        /* 0..1 while falling over */
     struct Object *thrown_by; /* set while flying after a throw */
@@ -243,6 +245,8 @@ typedef struct Object {
     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       dipped;        /* ... tipped down flat to get under something (a beam, a doorway) */
+    bool       just_caught;   /* ladders: fell against something just now and rests there (the game says so) */
     bool       leaning;       /* ladders: leaning against something (stands still) */
     float      air_fall;      /* characters: in the air, falling this fast at most (m/s; for the landing) */
     float      ladder_skill;  /* characters: practice climbing ladders one-handed (0..1) ... */
diff --git a/entropie/src/world/physics.c b/entropie/src/world/physics.c
index 9d4c26a..28a5151 100644
--- a/entropie/src/world/physics.c
+++ b/entropie/src/world/physics.c
@@ -52,6 +52,10 @@
 #define STEP_UP_MOST        0.75f  /* walking up onto something this high at most (scaled; higher: climb) ... */
 #define STEP_UP_ROOM        0.45f  /* ... with this much room on top ... */
 #define STEP_UP_IN          0.2f   /* ... the first foot this far in from its edge */
+#define STAND_LOWEST        0.22f  /* solid things this high (scaled) you step onto rather than over ... */
+#define STAND_ROOM          0.28f  /* ... a foot's width on top will do (a log: balancing) ... */
+#define STAND_UP_TIME       0.4f   /* ... one step up taking this long ... */
+#define STAND_UP_HIGH       0.9f   /* ... and this much more per meter high */
 #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) ... */
@@ -91,9 +95,11 @@ static float halfAcross(const Collider *c) {
    the fire; a ladder only lying on the ground. */
 static bool neverOver(const Object *o) {
     switch (o->type) {
-    case OBJ_HOUSE: case OBJ_BARN: case OBJ_TREE: case OBJ_BUSH: case OBJ_FIREPLACE: case OBJ_DOG:
+    case OBJ_HOUSE: case OBJ_BARN: case OBJ_BUSH: case OBJ_FIREPLACE: case OBJ_DOG:
     case OBJ_HUMAN: case OBJ_PLAYER: case OBJ_DECAL: case OBJ_PUDDLE: case OBJ_STINK:
         return true;
+    case OBJ_TREE:
+        return !(o->toppled && o->topple >= 1.0f); /* (one lying on the ground: over it, or onto it) */
     case OBJ_LADDER:
         return o->leaning;
     default:
@@ -101,6 +107,22 @@ static bool neverOver(const Object *o) {
     }
 }
 
+/* Solid enough to stand on, and won't break under you: heavy or fixed, its
+   top flat (a box or a log lying: one of its sides faces straight up), not a
+   bottle, a table, a ladder or a bin. */
+static bool standsOn(const Object *o) {
+    if (o->collider_count == 0 || neverOver(o) || o->is_held || o->lifted || o->broken) return false;
+    if (!o->is_static && o->weight < ONTO_HOLDS) return false;
+    switch (o->type) {
+    case OBJ_BOTTLE: case OBJ_VODKA: case OBJ_FURNITURE: case OBJ_BIN: case OBJ_SUPPLY: case OBJ_LADDER: case OBJ_FIREPLACE:
+        return false;
+    default:
+        break;
+    }
+    const Collider *c = &o->colliders[0];
+    return fabsf(c->axes[0].y) > 0.95f || fabsf(c->axes[1].y) > 0.95f || fabsf(c->axes[2].y) > 0.95f;
+}
+
 float Physics_lowObstacle(const Object *o) {
     if (neverOver(o) || o->is_held || o->lifted || o->collider_count == 0 || o->broken || o->thrown_by) return 0.0f;
     const Collider *c = &o->colliders[0];
@@ -494,7 +516,7 @@ static Vec3 steppingOver(const Object *obj, Vec3 m, Object *const *objects, int
     float speed = sqrtf(m.x * m.x + m.z * m.z);
     /* A standing leg the body would drag through what it stands behind: the
        body waits there for the other foot to come down, then that leg comes up */
-    for (int k = 0; k < 2; k++) {
+    for (int k = 0; k < 2 && vec3_length(obj->shoved) < 0.5f; k++) { /* (a load shoving into us: the legs get out of its way anyhow) */
         const Foot *ft = &obj->body.feet[k];
         if (!ft->stepping && ft->into > LEG_WAIT && obj->body.feet[1 - k].stepping && speed > LEG_WAIT_SPEED &&
             speed <= STEP_OVER_RUNNING) { /* (running, the foot leaps off instead) */
@@ -645,9 +667,12 @@ static bool startStepOver(Object *obj, Object *const *objects, int count, const
         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;
-        bool up = !over && !neverOver(o) && obj->type == OBJ_PLAYER && /* (people walk round; and nobody walks up onto tables) */
-                  o->type != OBJ_FURNITURE && o->type != OBJ_BIN && o->type != OBJ_SUPPLY && (o->is_static || o->weight >= ONTO_HOLDS) && c->axes[1].y > 0.95f &&
-                  top > STEP_OVER_HEIGHT * s && top <= STEP_UP_MOST * s && 2.0f * reach >= STEP_UP_ROOM * s && side >= 0.3f * s;
+        /* up onto it: higher than a stride over, or something solid you can just as well stand on (a log, a crate) -
+           no straddling then, you stand up there and walk on (people walk round; nobody walks up onto tables) */
+        bool up = obj->type == OBJ_PLAYER && standsOn(o) && top <= STEP_UP_MOST * s && side >= 0.3f * s &&
+                  ((!over && top > STEP_OVER_HEIGHT * s && 2.0f * reach >= STEP_UP_ROOM * s) ||
+                   (top >= STAND_LOWEST * s && 2.0f * reach >= STAND_ROOM * s));
+        if (up) over = false;
         if (!over && !up) continue;
         float bottom = Object_colliderCenter(o, 0).y - projectRadius(c, WORLD_UP) - obj->position.y;
         if (bottom > (0.2f + 0.15f * fmaxf(ahead, 0.0f)) * s || bottom < -0.5f * s - 0.15f * fmaxf(ahead, 0.0f)) continue; /* (up on something, or down in a hole: not in your way) */
@@ -655,7 +680,7 @@ static bool startStepOver(Object *obj, Object *const *objects, int count, const
         top = fmaxf(top, 0.05f * s); /* (downhill it may be lower than your feet) */
         /* where the feet come down: free? (on top of it, going up) */
         Vec3 right = vec3(-dir.z, 0.0f, dir.x);
-        float land_at = up ? near + STEP_UP_IN * s : far + OVER_LAND * s;
+        float land_at = up ? near + fminf(STEP_UP_IN * s, reach) : far + OVER_LAND * s; /* (a log: its middle) */
         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)));
@@ -667,7 +692,8 @@ static bool startStepOver(Object *obj, Object *const *objects, int count, const
         if (!free) continue;
         obj->over_t = 1e-4f;
         obj->over_up = up;
-        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_time = (up ? STAND_UP_TIME + STAND_UP_HIGH * top / s /* (onto something low: one step up) */
+                                                            : 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;
@@ -743,6 +769,37 @@ static void hurdle(Object *obj, Object *const *objects, int count) {
     }
 }
 
+/* Walking at the end of something long lying there (a log, a fallen tree) so
+   that you'd only just catch it: a little to the side, past its end, instead
+   of over it or up onto it. */
+#define END_CLIP   0.55f /* m of you that would still catch it at most */
+#define END_LONG   1.4f  /* m: only things at least this long across your way */
+#define END_LOOK   1.6f  /* m ahead */
+#define END_BODY   0.3f  /* (your half width) */
+
+static Vec3 pastTheEnd(const Object *obj, Vec3 m, Object *const *objects, int count) {
+    float speed = sqrtf(m.x * m.x + m.z * m.z);
+    if (speed < 0.3f || obj->sitting || obj->climbing || obj->on_ladder || obj->hands.grabbed || obj->type == OBJ_DOG) return m;
+    Vec3 dir = vec3(m.x / speed, 0.0f, m.z / speed), right = vec3(-dir.z, 0.0f, dir.x);
+    for (int i = 0; i < count; i++) {
+        const Object *o = objects[i];
+        if (o->collider_count == 0 || o->is_character || o->is_held || (Physics_lowObstacle(o) <= 0.0f && !standsOn(o))) continue;
+        const Collider *c = &o->colliders[0];
+        float side = projectRadius(c, right), reach = projectRadius(c, dir);
+        if (2.0f * side < END_LONG) continue;
+        float top = Object_colliderCenter(o, 0).y + projectRadius(c, WORLD_UP) - obj->position.y;
+        if (top > STEP_UP_MOST || top < 0.1f) continue;
+        Vec3 rel = vec3_sub(Object_colliderCenter(o, 0), obj->position);
+        float ahead = vec3_dot(vec3(rel.x, 0.0f, rel.z), dir) - reach, across = vec3_dot(vec3(rel.x, 0.0f, rel.z), right);
+        float clip = side + END_BODY - fabsf(across);
+        if (ahead < -0.05f || ahead > END_LOOK || clip <= 0.0f || clip > END_CLIP) continue;
+        /* away from its middle, more the closer it gets */
+        float push = fminf(1.0f, clip / fmaxf(ahead, 0.25f)) * speed;
+        return vec3_add(m, vec3_scale(right, across > 0.0f ? -push : push));
+    }
+    return m;
+}
+
 /* ---------- Public ---------- */
 
 #define REST_SPEED  0.03f  /* things lying this still ... */
@@ -795,6 +852,7 @@ void Physics_step(Object *obj, Object *const *objects, int count, const Terrain
         if (n >= 0) { objects = near; count = n; }
     }
     if (obj->is_character && obj->on_terrain) obj->movement = slopeAdjusted(obj, terrain);
+    if (obj->is_character && obj->is_grounded) obj->movement = pastTheEnd(obj, obj->movement, objects, count);
     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);
diff --git a/entropie/src/world/world.c b/entropie/src/world/world.c
index f9feb2d..b485727 100644
--- a/entropie/src/world/world.c
+++ b/entropie/src/world/world.c
@@ -769,6 +769,9 @@ static bool solidFor(const Object *o, const Object *who) {
     return o->is_static || o->weight >= CARRY_SOLID;
 }
 
+#define RAISE_DIP_DONE 0.2f  /* rad: tipped down far enough to look whether it goes up again */
+#define RAISE_UP_TEST  1.36f /* (how steep it goes back up) */
+
 /* Long enough to be in the way (a ladder, a log, a branch; not a bag or a bottle). */
 static bool carriedLong(const Object *item) {
     if (!item || item->collider_count == 0) return false;
@@ -808,6 +811,25 @@ static void placeAt(Object *who, Vec3 pos, float yaw) {
     Hands_placeHeld(who);
 }
 
+/* A ladder held up that runs into something (a beam, a doorway): tipped down
+   flat, carried under it, and up again once there's room up there. */
+static void dipLadder(World *world, Object *who, float overlap) {
+    Object *l = NULL;
+    for (int h = 0; h < 2; h++)
+        if (who->hands.state[h] == HAND_HOLDING && Ladder_isRaised(who->hands.item[h])) l = who->hands.item[h];
+    if (!l) return;
+    if (overlap > 0.002f) { l->dipped = true; return; }
+    if (!l->dipped || l->raised > RAISE_DIP_DONE) return; /* (still going down: stays down) */
+    /* down and clear: up again if it would stand up there without going into anything */
+    float was = l->raised;
+    l->raised = RAISE_UP_TEST;
+    Hands_placeHeld(who);
+    bool room = carriedOverlap(world, who) <= 0.002f;
+    l->raised = was;
+    Hands_placeHeld(who);
+    if (room) l->dipped = false;
+}
+
 /* A carried ladder, log or axe doesn't go through walls or trees: if turning
    (or walking) this frame pushed it into something, they don't turn (or move). */
 static void keepCarriedClear(World *world, Object *who) {
@@ -815,6 +837,7 @@ static void keepCarriedClear(World *world, Object *who) {
     bool carrying = who->hands.state[0] == HAND_HOLDING || who->hands.state[1] == HAND_HOLDING;
     if (carrying && who->carry_ok && !who->on_ladder && !Tools_isWorking(who)) {
         float now = carriedOverlap(world, who);
+        dipLadder(world, who, now);
         if (now > 0.002f) {
             Vec3 pos = who->position;
             float yaw = who->rotation.y;
@@ -972,7 +995,7 @@ void World_update(World *world, float dt, float game_dt) {
     for (int i = 0; i < world->count; i++) {
         Object *o = world->objects[i];
         if (o->type == OBJ_DOG) Dog_update(o, world->objects, world->count, dt);
-        if (o->type == OBJ_LADDER) Ladder_update(o, dt);
+        if (o->type == OBJ_LADDER) Ladder_update(world, o, dt);
         if (!o->is_character) continue;
         /* How work goes: fresh and sober, and liking the job, is quickest */
         o->work_rate = Object_vigor(o) * (1.0f - 0.45f * fminf(o->drunk, 1.0f)) * Ai_workRate(o);