Nimbin[12]?Games / Entropie / commits / 5f30b85

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.88.0: the others step onto logs, no invisible steps, tired days

Christian Immanuel · 2026-10-15 18:03 · 5f30b85001d4e8b2cbb8aa94df0cfbcec0fba68e

- The others step onto a long log across their way and walk on (wobbling),
  shorter things they still walk round
- Fix: an invisible step next to things - the edge of the body held you up
  in the air beside it; now you go down once your middle is past the edge
- Fix: invisible steps on slopes (a roof, a tilted box): the feet took the
  highest corner of it instead of the surface right there
- Tired: the later the day, the less energy - a lower ceiling, used up
  quicker, back slower, all a bit weaker; lively and sporty people hold up
  better, drink makes it worse
 entropie/Makefile               |   2 +-
 entropie/README.md              |   7 ++
 entropie/STATE.md               |   8 +-
 entropie/commit_message.txt     | 190 +++-------------------------------------
 entropie/src/people/ai.c        |  19 ++++
 entropie/src/people/condition.c |  10 ++-
 entropie/src/world/object.c     |  12 ++-
 entropie/src/world/object.h     |   2 +
 entropie/src/world/physics.c    |  48 ++++++++--
 entropie/src/world/physics.h    |   2 +
 entropie/src/world/world.c      |  18 ++++
 11 files changed, 128 insertions(+), 190 deletions(-)

diff --git a/entropie/Makefile b/entropie/Makefile
index ced4e8d..d7907f0 100644
--- a/entropie/Makefile
+++ b/entropie/Makefile
@@ -1,5 +1,5 @@
 APP       := entropie
-VERSION   := 1.87.0
+VERSION   := 1.88.0
 
 PREFIX    ?= /usr
 BINDIR    ?= $(PREFIX)/bin
diff --git a/entropie/README.md b/entropie/README.md
index 0c72216..7be479d 100644
--- a/entropie/README.md
+++ b/entropie/README.md
@@ -158,6 +158,10 @@ and satellite pictures (about 340 x 340 m).
   clumsy, drunk or hurrying), slower steps - and now and then a step off
 - Something you can't step over or onto (a tree, a post, a high crate, a
   wall's corner) that you'd only just catch: your body drifts past it to the side
+- The others do it too: a long log across the way they step onto and walk on
+  (and wobble), shorter things they walk round
+- Off the edge of something you go once your middle is past it - no standing
+  on air beside it; slopes (a roof, a tilted box) no longer show invisible steps
 - 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
@@ -249,6 +253,9 @@ and satellite pictures (about 340 x 340 m).
   trips and scrapes. Running, the body rises and falls with each stride:
   the sporty glide, the others bounce and nod (Tab shows how sporty you are)
 - Worn out, you can't run until you got your breath back a bit
+- The more tired the day makes you, the less energy you have: less to give,
+  used up quicker, back slower, everything a bit weaker. Lively and sporty
+  people hold up better; drink makes it worse
 - Bottles are held low in the main hand, now and then by the neck
 - Nobody is quite symmetric: one arm swings more, one shoulder sits higher,
   and now and then a hand goes to the hip, the head, the chin or a pocket
diff --git a/entropie/STATE.md b/entropie/STATE.md
index ccc319d..84a85f3 100644
--- a/entropie/STATE.md
+++ b/entropie/STATE.md
@@ -75,6 +75,10 @@ Onto, not over (`physics.c`, the player only - NPCs still step over and round):
 
 Smooth lines (`ink.frag`): edgeAt looked at in 4 spots inside the pixel (rotated grid) and averaged (`smoothEdge`); the fill averaged over 4 taps where their luminance differs 0.04-0.14+ (`smoothFill`). 4x the edge texture reads: watch it on slow graphics.
 
+NPCs onto things (1.88): the onto-step (up to STEP_OVER_HEIGHT x 1.15) and walking onto flat things work for OBJ_HUMAN too; `ai.c` longToStepOnto: standable loose things 1.5 m+ long and up to ONTO_HIGH 0.55 m are not in the way for steering (`Physics_standsOn`), so people step onto a log across their way instead of walking round; a step off says "Whoa!" sometimes. Edges (`physics.c` middleOverAny): a character lands on something only with its middle over one of its colliders (OVER_EDGE 0.12 m past an edge at most); else it slides off away from it (the body box's corner used to hold it in the air). `world.c` clutterQuery: a tilted collider (no axis within 0.98 of upright) counts with its surface at the foot (rays down there and towards it), not its highest corner (invisible steps on roofs and tilted boxes).
+
+Tiredness (`object.c` Object_tiredness: sleepiness from 0.25 up x (1 - 0.35 energy) x (1.2 - 0.4 fitness) + 0.4 drunk): the stamina ceiling TIRED_CAP 0.55 lower, effort TIRED_EFFORT 0.6 harder, rest (awake) TIRED_REST 0.5 slower (`condition.c`), Object_vigor 0.25 lower (speed, jumps, work).
+
 Balancing (`world.c` -> `body.balance_goal` from `Physics_footing`: 1 at a top 0.3 m wide or less, 0 from 0.75 m; `body.c` updateBalance): `teeter` is a sprung wobble (3.2 rad/s, damping 0.35) kicked at random (TEETER_KICK 40 x (0.25 + 0.6 clumsy + 1.6 drunk + 0.35 speed + 0.4 nerves) x (1.25 - 0.75 fitness)); past 1: Body_stagger sideways (`stepped_off` -> "Whoa - you lose your balance and step off"). Pose: arms out to the sides (the one on the tipping side lower), the hips with the wobble, the upper body against it; walking BALANCE_SLOW 0.55 slower.
 
 Chainsaw: collider and pivot fit the mesh (the motor's underside on the ground), the bar lower on the motor (it floated).
@@ -199,7 +203,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: NPCs stepping onto solid things and balancing too (now only you); thin round logs (under 0.2 m) are still stepped over; 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
+- Obstacle approach, still to do: NPCs stepping up onto higher things (now only what they'd stride over anyway); thin round logs (under 0.2 m) are still stepped over; 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)
@@ -219,4 +223,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.87.0 (build f24cb4e)`
+State of `entropie 1.88.0 (build d1db253)`
diff --git a/entropie/commit_message.txt b/entropie/commit_message.txt
index 6ff81e6..22763be 100644
--- a/entropie/commit_message.txt
+++ b/entropie/commit_message.txt
@@ -1,179 +1,11 @@
-entropie 1.87.0: walking on things, balancing
-
-- Planks, logs and crates are never straddled: flat ones you walk onto,
-  higher ones are one step up, and you walk on along the top
-- On something narrow you balance: arms out, a wobble (clumsy, drunk or
-  hurrying: bigger), slower steps, now and then a step off
-- Something you can't step over or onto (a tree, a post, a high crate) that
-  you'd only just catch: you drift past it to the side
-- Smoother lines: diagonal outlines and edges no longer step like stairs
-- Fix: standing on a log you fell through it (it counted as "stepped over")
-- Fix: the chainsaw floated above the ground, its bar in the air
-
-Includes 1.86.0 (if not committed separately):
-- 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 1.88.0: the others step onto logs, no invisible steps, tired days
+
+- The others step onto a long log across their way and walk on (wobbling),
+  shorter things they still walk round
+- Fix: an invisible step next to things - the edge of the body held you up
+  in the air beside it; now you go down once your middle is past the edge
+- Fix: invisible steps on slopes (a roof, a tilted box): the feet took the
+  highest corner of it instead of the surface right there
+- Tired: the later the day, the less energy - a lower ceiling, used up
+  quicker, back slower, all a bit weaker; lively and sporty people hold up
+  better, drink makes it worse
diff --git a/entropie/src/people/ai.c b/entropie/src/people/ai.c
index 54540f3..344412c 100644
--- a/entropie/src/people/ai.c
+++ b/entropie/src/people/ai.c
@@ -6,6 +6,7 @@
 
 #include "people/ai_internal.h"
 #include "people/pockets.h"
+#include "world/physics.h"
 #include "world/nav.h"
 #include "world/grid.h"
 #include "render/shapes.h"
@@ -65,6 +66,23 @@ void Ai_useGrid(struct Grid *grid) { g_grid = grid; }
 struct World *g_world;
 void Ai_useWorld(struct World *world) { g_world = world; }
 
+/* Something solid lying across the way, long (going round is a detour) and low
+   enough to step onto: people step up onto it and on (see physics: startStepOver). */
+#define ONTO_LONG  1.5f  /* m along at least */
+#define ONTO_HIGH  0.55f /* m above the feet at most (what they would stride over anyway) */
+
+static bool longToStepOnto(const Object *self, const Object *o) {
+    if (o->is_static || o->is_held || o->is_character || o->collider_count == 0 || !Physics_standsOn(o)) return false;
+    const Collider *c = &o->colliders[0];
+    float top = Object_colliderCenter(o, 0).y, longest = 0.0f;
+    const float half[3] = { c->half_extents.x, c->half_extents.y, c->half_extents.z };
+    for (int k = 0; k < 3; k++) {
+        top += fabsf(c->axes[k].y) * half[k];
+        longest = fmaxf(longest, 2.0f * half[k] * sqrtf(c->axes[k].x * c->axes[k].x + c->axes[k].z * c->axes[k].z));
+    }
+    return longest >= ONTO_LONG && top - self->position.y <= ONTO_HIGH;
+}
+
 bool ai_blockedBy(const Object *self, const Object *ignore, const Object *ignore2,
                       Object *const *objects, int count, Vec3 p) {
     Object *near[512]; /* (only what's near p, from the cell grid) */
@@ -75,6 +93,7 @@ bool ai_blockedBy(const Object *self, const Object *ignore, const Object *ignore
     for (int i = 0; i < count; i++) {
         const Object *o = objects[i];
         if (o == self || o == ignore || o == ignore2) continue;
+        if (longToStepOnto(self, o)) continue; /* (a log, a fallen tree: onto it and on, no long way round) */
         if (o->is_held) { /* what others carry: a ladder or a log is walked round, a bottle isn't */
             if (o->collider_count == 0 || self->hands.item[0] == o || self->hands.item[1] == o) continue;
             Vec3 e = o->colliders[0].half_extents;
diff --git a/entropie/src/people/condition.c b/entropie/src/people/condition.c
index 734dec8..a264738 100644
--- a/entropie/src/people/condition.c
+++ b/entropie/src/people/condition.c
@@ -27,6 +27,9 @@
 #define TIRE_WORKING      0.040f  /* tool strokes: a few minutes of chopping and you need a break */
 #define TIRE_PUSHING      0.016f
 #define TIRE_CARRYING     0.024f
+#define TIRED_CAP         0.55f  /* tired out: the most stamina this much lower ... */
+#define TIRED_EFFORT      0.6f   /* ... effort this much harder ... */
+#define TIRED_REST        0.5f   /* ... resting this much slower */
 #define REST_STANDING     0.030f
 #define REST_WALKING      0.008f
 #define REST_SLEEPING     0.050f
@@ -96,8 +99,13 @@ static void needs(Object *self, float dt, float game_dt) {
                  : self->ground_speed > 4.0f ? TIRE_RUNNING : 0.0f;
     float rest = asleep ? REST_SLEEPING : self->sitting ? REST_STANDING * 2.0f
                : self->ground_speed < 0.3f ? REST_STANDING : REST_WALKING;
+    /* Tired (late in the day, drunk, sluggish): less to give - a lower ceiling,
+       used up quicker, back slower */
+    float tired = Object_tiredness(self);
     float cap = clampf(1.0f - 0.6f * excess(n->hunger, 0.6f) - 0.6f * excess(n->thirst, 0.6f)
-                       - 0.5f * excess(n->sleepiness, 0.6f), 0.15f, 1.0f);
+                       - TIRED_CAP * tired, 0.15f, 1.0f);
+    effort *= 1.0f + TIRED_EFFORT * tired;
+    if (!asleep) rest *= 1.0f - TIRED_REST * tired;
     n->stamina = clampf(n->stamina + (effort > 0.0f ? -effort : rest) * dt, 0.0f, fmaxf(cap, 0.0f));
     if (n->stamina > cap) n->stamina = cap;
 
diff --git a/entropie/src/world/object.c b/entropie/src/world/object.c
index d54f55f..c69406a 100644
--- a/entropie/src/world/object.c
+++ b/entropie/src/world/object.c
@@ -499,8 +499,18 @@ Vec3 Object_colliderCenter(const Object *obj, int index) {
     return vec3_add(obj->position, obj->colliders[index].offset);
 }
 
+/* Worn down by the day: how sleepy, felt more by the sluggish, the unfit and
+   the drunk, hardly by the lively and sporty (0 = fresh .. 1 = done in). */
+float Object_tiredness(const Object *obj) {
+    if (!obj->is_character) return 0.0f;
+    float sleepy = clampf((obj->needs.sleepiness - 0.25f) / 0.75f, 0.0f, 1.0f);
+    float feels = (1.0f - 0.35f * clampf(obj->temperament.energy, -1.0f, 1.0f)) * (1.2f - 0.4f * obj->fitness)
+                + 0.4f * fminf(obj->drunk, 1.0f);
+    return clampf(sleepy * feels, 0.0f, 1.0f);
+}
+
 float Object_vigor(const Object *obj) {
-    return 0.5f + 0.5f * fminf(obj->needs.stamina / 0.3f, 1.0f);
+    return (0.5f + 0.5f * fminf(obj->needs.stamina / 0.3f, 1.0f)) * (1.0f - 0.25f * Object_tiredness(obj));
 }
 
 float Object_topSpeed(const Object *obj) {
diff --git a/entropie/src/world/object.h b/entropie/src/world/object.h
index 8457d8e..fca2c2f 100644
--- a/entropie/src/world/object.h
+++ b/entropie/src/world/object.h
@@ -317,6 +317,8 @@ Vec3    Object_colliderCenter(const Object *obj, int index);
 
 /* Characters: 1 when fresh, down to 0.5 when exhausted (speed, strength, work). */
 float   Object_vigor(const Object *obj);
+/* Worn down by the day (sleepiness, felt by personality, fitness and drink): 0 fresh .. 1 done in. */
+float   Object_tiredness(const Object *obj);
 /* Characters: how fast they can sprint and jump right now (tired or drunk: less). */
 float   Object_topSpeed(const Object *obj);
 float   Object_jumpSpeed(const Object *obj);
diff --git a/entropie/src/world/physics.c b/entropie/src/world/physics.c
index 7cfbc7c..4b99f6b 100644
--- a/entropie/src/world/physics.c
+++ b/entropie/src/world/physics.c
@@ -127,6 +127,8 @@ static bool standsOn(const Object *o) {
     return fabsf(c->axes[0].y) > 0.95f || fabsf(c->axes[1].y) > 0.95f || fabsf(c->axes[2].y) > 0.95f;
 }
 
+bool Physics_standsOn(const Object *o) { return standsOn(o); }
+
 float Physics_footing(const Object *o) {
     if (o->collider_count == 0) return 1e9f;
     const Collider *c = &o->colliders[0];
@@ -153,7 +155,7 @@ bool Physics_stepsOver(const Object *who, const Object *o) {
     float top = Object_colliderCenter(o, 0).y + projectRadius(c, WORLD_UP);
     float feet = Object_colliderCenter(who, 0).y - projectRadius(&who->colliders[0], WORLD_UP);
     if (top - feet < STOOD_ON) return false; /* (up on it: it holds you) */
-    if (who->type == OBJ_PLAYER && standsOn(o) && top - feet < STAND_LOWEST * s) return false; /* (flat, a plank: you just walk onto it) */
+    if ((who->type == OBJ_PLAYER || who->type == OBJ_HUMAN) && standsOn(o) && top - feet < STAND_LOWEST * s) return false; /* (flat, a plank: you just walk onto it) */
     return top - feet <= STEP_OVER_HEIGHT * s && 2.0f * halfAcross(c) <= STEP_OVER_WIDTH * s;
 }
 
@@ -333,12 +335,45 @@ static void noteBump(Object *obj, const Object *other, Vec3 normal) {
     }
 }
 
+/* Is p (a body's middle) over the collider's top, give or take margin? */
+#define OVER_EDGE 0.12f /* m: the middle this far past an edge still stands (the feet are wider) */
+
+static bool middleOver(const Object *o, int ic, Vec3 p, float margin) {
+    const Collider *c = &o->colliders[ic];
+    Vec3 rel = vec3_sub(p, Object_colliderCenter(o, ic));
+    rel.y = 0.0f;
+    const float half[3] = { c->half_extents.x, c->half_extents.y, c->half_extents.z };
+    for (int a = 0; a < 3; a++) {
+        Vec3 axis = vec3(c->axes[a].x, 0.0f, c->axes[a].z);
+        float len = vec3_length(axis);
+        if (len < 0.3f) continue; /* (an upright axis) */
+        if (fabsf(vec3_dot(rel, vec3_scale(axis, 1.0f / len))) > half[a] * len + margin) return false;
+    }
+    return true;
+}
+
+/* ... over any part of it (a house is walls and roofs; standing on one, touching another is fine) */
+static bool middleOverAny(const Object *o, Vec3 p, float margin) {
+    for (int c = 0; c < o->collider_count; c++)
+        if (middleOver(o, c, p, margin)) return true;
+    return false;
+}
+
 static void resolveContact(Object *obj, int io, Object *other, int ic, Vec3 normal, float depth) {
     obj->touched = other;
     if (other->is_character && !obj->is_character && touchPerson(obj, other, normal, depth)) return;
 
     /* Standing on something: lift straight up, so walkable slopes don't slide.
        (Walking into something, a person doesn't pop up onto it: it's in the way.) */
+    if (normal.y >= WALKABLE_SLOPE && obj->is_character && !other->is_character && !middleOverAny(other, obj->position, OVER_EDGE)) {
+        /* only a corner of the body on it (the middle's off its edge): no standing on air - off it you slide */
+        Vec3 away = vec3_sub(obj->position, Object_colliderCenter(other, ic));
+        away.y = 0.0f;
+        float len = vec3_length(away);
+        obj->position = vec3_add(obj->position, len > 1e-4f ? vec3_scale(away, (fminf(depth, 0.03f) + 0.01f) / len) : vec3(0.01f, 0.0f, 0.0f));
+        Object_syncColliders(obj);
+        return;
+    }
     if (normal.y >= WALKABLE_SLOPE && !(obj->is_character && depth / normal.y > LAND_ON_MOST)) {
         land(obj, depth / normal.y);
         if (obj->is_character && !other->is_character) obj->standing_on = other;
@@ -371,7 +406,7 @@ static void resolveContact(Object *obj, int io, Object *other, int ic, Vec3 norm
         noteBump(obj, other, normal);
         slideAlong(obj, normal, depth);
     } else {
-        if (obj->type == OBJ_PLAYER && standsOn(other) && tryStepUp(obj, io, other, ic, normal, STAND_LOWEST)) return; /* (a plank: onto it) */
+        if ((obj->type == OBJ_PLAYER || obj->type == OBJ_HUMAN) && standsOn(other) && tryStepUp(obj, io, other, ic, normal, STAND_LOWEST)) return; /* (a plank: onto it) */
         noteBump(obj, other, normal);
         push(obj, other, normal, depth);
     }
@@ -684,10 +719,11 @@ static bool startStepOver(Object *obj, Object *const *objects, int count, const
         float top = Object_colliderCenter(o, 0).y + projectRadius(c, WORLD_UP) - obj->position.y;
         float near = ahead - reach, far = ahead + reach;
         /* 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.12f * s && /* (a log end-on: a narrow step) */
-                  ((!over && top > STEP_OVER_HEIGHT * s && 2.0f * reach >= STEP_UP_ROOM * s) ||
-                   (top >= STAND_LOWEST * s && 2.0f * reach >= STAND_ROOM * s));
+           no straddling then, you stand up there and walk on (nobody walks up onto tables) */
+        const bool you = obj->type == OBJ_PLAYER, person = you || obj->type == OBJ_HUMAN;
+        bool up = person && standsOn(o) && top <= STEP_UP_MOST * s && side >= 0.12f * s && /* (a log end-on: a narrow step) */
+                  ((you && !over && top > STEP_OVER_HEIGHT * s && 2.0f * reach >= STEP_UP_ROOM * s) || /* (higher: only you - the others walk round) */
+                   (top >= STAND_LOWEST * s && 2.0f * reach >= STAND_ROOM * s && (you || top <= STEP_OVER_HEIGHT * 1.15f * s)));
         if (up) over = false;
         if (!over && !up) continue;
         float bottom = Object_colliderCenter(o, 0).y - projectRadius(c, WORLD_UP) - obj->position.y;
diff --git a/entropie/src/world/physics.h b/entropie/src/world/physics.h
index 060b6a7..2ad48f2 100644
--- a/entropie/src/world/physics.h
+++ b/entropie/src/world/physics.h
@@ -16,6 +16,8 @@
 
 /* Something low and narrow enough to step over: its height (0 = not that). */
 float Physics_lowObstacle(const Object *o);
+/* Solid enough to stand on and won't break (heavy or fixed, a flat top; not a bottle or a table). */
+bool  Physics_standsOn(const struct Object *o);
 /* How wide the top of o is to stand on, the narrow way (m; a log: about its diameter). */
 float Physics_footing(const Object *o);
 /* Is o something low that who steps over (no collision with the body)? */
diff --git a/entropie/src/world/world.c b/entropie/src/world/world.c
index d0e2fd9..ef40ce1 100644
--- a/entropie/src/world/world.c
+++ b/entropie/src/world/world.c
@@ -95,6 +95,20 @@ static float clutterQuery(const void *ctx, float x, float z, float radius, float
             const float half[3] = { col->half_extents.x, col->half_extents.y, col->half_extents.z };
             float top = c.y + fabsf(col->axes[0].y) * half[0] + fabsf(col->axes[1].y) * half[1] + fabsf(col->axes[2].y) * half[2];
             float bottom = 2.0f * c.y - top;
+            if (fmaxf(fabsf(col->axes[0].y), fmaxf(fabsf(col->axes[1].y), fabsf(col->axes[2].y))) < 0.98f) {
+                /* tilted (a roof, a box on its edge): its highest corner may be far off -
+                   what counts is its surface right there, or the nearest bit of it */
+                Vec3 to = vec3(c.x - x, 0.0f, c.z - z);
+                float len = vec3_length(to), surface = -1e9f;
+                for (int sIdx = 0; sIdx < 3; sIdx++) {
+                    float along = len > 1e-4f ? fminf(radius * 0.5f * (float)sIdx, len) / len : 0.0f;
+                    Vec3 from = vec3(x + to.x * along, top + 1.0f, z + to.z * along);
+                    float t = rayDownToBox(from, c, col);
+                    if (t >= 0.0f) surface = fmaxf(surface, from.y - t);
+                }
+                if (surface < -1e8f) continue;
+                top = surface;
+            }
             if (top <= feet_y + 0.05f || bottom > feet_y + 1.0f) continue; /* under the feet (standing on it), or overhead */
             /* distance from (x, z) to the collider's footprint */
             Vec3 rel = vec3(x - c.x, 0.0f, z - c.z);
@@ -1018,6 +1032,10 @@ void World_update(World *world, float dt, float game_dt) {
         if (o->type == OBJ_HUMAN) { Ai_useGrid(&world->grid); Ai_useWorld(world); Ai_update(o, world->objects, world->count, dt); }
         if (o->type == OBJ_HUMAN && o->ai.wants_bag) handOutBag(world, o);
         if (o->type == OBJ_HUMAN && o->ai.empty_tray) emptyTray(world, o);
+        if (o->type == OBJ_HUMAN && o->body.stepped_off) { /* lost the balance up on something */
+            o->body.stepped_off = false;
+            if (worldDice(world) < 0.5f) Ai_say(o, worldDice(world) < 0.5f ? "Whoa!" : "Oops - nearly...");
+        }
     }
 
     Physics_useGrid(&world->grid);