Nimbin[12]?Games / Entropie / commits / 859040c

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 2.36.0: rain together, steady view on slopes

Christian Immanuel · 2026-10-26 21:44 · 859040c390c2906c1ddc913feef0ff8b2a387957

- No more shaking view walking down steep slopes (the bank by your garages)
- The pullover tied round the waist no longer goes through the legs
- Umbrellas held properly: open, up in front of the shoulder with the shaft
  leaning over the head; folded, carried by the middle, tip down
- People without an umbrella ask to come under someone's (yours too) and
  walk along side by side; the umbrella leans over both
- In hard rain the guests sit it out in your garages' kitchen (chairs, a
  new log bench, or the floor) and get up when it stops
 commit_message.txt                  |  14 ++--
 entropie/Makefile                   |   2 +-
 entropie/README.md                  |   2 +-
 entropie/STATE.md                   |  22 ++++--
 entropie/assets/shaders/figure.frag |  24 +++++-
 entropie/commit_message.txt         |  14 ++--
 entropie/src/people/ai.c            | 147 ++++++++++++++++++++++++++++++++++--
 entropie/src/people/ai.h            |   3 +
 entropie/src/people/hands.c         |  30 ++++++--
 entropie/src/world/object.h         |   2 +
 entropie/src/world/physics.c        |  27 ++++---
 entropie/src/world/site.c           |   6 ++
 entropie/src/world/site.h           |   2 +
 entropie/src/world/world.c          |  20 ++++-
 14 files changed, 269 insertions(+), 46 deletions(-)

diff --git a/commit_message.txt b/commit_message.txt
index 6a95ab0..cbec05c 100644
--- a/commit_message.txt
+++ b/commit_message.txt
@@ -1,6 +1,10 @@
-entropie 2.35.0: Alice pants
+entropie 2.36.0: rain together, steady view on slopes
 
-- Alice pants after a run (and in the midday sun): quick breaths you hear
-  close by, her chest heaving; it eases off over half a minute
-- She no longer runs on the spot against the car when called: held up by
-  something, she goes round it (legs move by how fast she really goes)
+- No more shaking view walking down steep slopes (the bank by your garages)
+- The pullover tied round the waist no longer goes through the legs
+- Umbrellas held properly: open, up in front of the shoulder with the shaft
+  leaning over the head; folded, carried by the middle, tip down
+- People without an umbrella ask to come under someone's (yours too) and
+  walk along side by side; the umbrella leans over both
+- In hard rain the guests sit it out in your garages' kitchen (chairs, a
+  new log bench, or the floor) and get up when it stops
diff --git a/entropie/Makefile b/entropie/Makefile
index b94de0f..537b19e 100644
--- a/entropie/Makefile
+++ b/entropie/Makefile
@@ -1,5 +1,5 @@
 APP       := entropie
-VERSION   := 2.35.0
+VERSION   := 2.36.0
 
 PREFIX    ?= /usr
 BINDIR    ?= $(PREFIX)/bin
diff --git a/entropie/README.md b/entropie/README.md
index 4e59a3d..5baee17 100644
--- a/entropie/README.md
+++ b/entropie/README.md
@@ -124,5 +124,5 @@ Dependencies point downward: `main → game → world/people → render → core
 
 ## Version
 
-`make version` prints e.g. `entropie 2.35.0 (build 9450b3c)`. The build id is
+`make version` prints e.g. `entropie 2.36.0 (build 25ed4f6)`. The build id is
 an md5 of all sources and assets.
diff --git a/entropie/STATE.md b/entropie/STATE.md
index 07f3204..125b23a 100644
--- a/entropie/STATE.md
+++ b/entropie/STATE.md
@@ -284,10 +284,15 @@ Painted in the rotoscope style of *Waking Life* (2001):
 - Two umbrellas (by the main building's door, in your garages' kitchen):
   carried, one opens in the rain and keeps you dry. The others fetch one when
   it starts to rain (if they're not busy working) and stay out under it; the
-  careful put it back after
+  careful put it back after. Open, it's held up in front of the shoulder, the
+  shaft leaning in over the head; folded, carried by the middle, tip down
+- No umbrella of their own: somebody near under one gets asked ("Room under
+  there for me?") and they walk along side by side under it (yours too); it
+  leans over the two of them
 - People get out of it: Kurti and his sons into the house, the others into
-  their tent (at night, or when it's nearer) or your garages' kitchen; out
-  again when it stops
+  their tent (at night, or when it's nearer) or your garages' kitchen. Raining
+  hard, they sit it out in there (the two chairs, a log bench by the back
+  wall, else the floor); out again when it stops
 - Out in the rain your clothes get wet (darker; Tab: Wet) and you get cold.
   They dry by a fire quickest, in the day slower, at night slowest. Wet
   through, the others go and sit by a burning fire
@@ -1206,6 +1211,13 @@ Round 2.35 (Alice pants, goes round things):
 - Panting (`dog.c` pant): `dog.pant` 0..1 rises running (PANT_RISE 0.1/s flat out, from 1.8 m/s), eases resting (PANT_EASE 0.035/s, slower in the sun); the midday sun alone (10:30-19, no rain) keeps it at PANT_SUN 0.35. Over 0.15 she breathes 2-4.5 times a second (`pant_t`): NOISE_PANT each breath (a breath out, a quicker one in, `sound.c`), the chest heaving with it (Dog_draw); not with something in her mouth.
 - Her real speed (`dog.was` / `speed`): legs and panting go by how fast she really moves (it was the speed she wanted: running into the car she trotted on the spot for ever, never arriving when called). Held up for DETOUR_AFTER 1 s, she goes off to one side for DETOUR_TIME 1.2 s (`detour`), then on. Checked: called from 25 m behind the car - round it, arrived, panting 1.0 easing to 0.4 in 30 s, about 4 breaths a second.
 
+Round 2.36 (rain together, view on the slope):
+- The view shook walking down a steep slope (the bank behind your garages): people stood on the highest ground under their footprint, but walking down they were kept on the ground under their middle - one frame up onto the uphill corner, the next down again (8-25 cm). Both now the same ground (`physics.c` footprintGround). Checked: tents to the main building's door walking (1 jump of 3 cm left, was 50+ of up to 30 cm), running and from behind.
+- The jacket tied round the waist (`figure.frag`): the back hangs in two halves, each pushed out behind by its thigh going back; the sleeve ends in front pushed out by a thigh coming up (MAX_PARTS 42). No leg through it any more (side and back views, walking and running).
+- Umbrellas (`hands.c`): open, the hand in front of its shoulder (UMBRELLA_HAND_OUT 0.16, UP 1.16, AHEAD 0.17), the shaft leaning in to UMBRELLA_TOP 2.05 m over the head's middle (it went straight up in front of the face); folded, by the middle, tip ahead and down (it was held sideways like a bat).
+- Sharing one (`ai.c` umbrellaToShare / walkUnder): out in the rain without one, somebody within SHARE_SEE 12 m holding one up with nobody under it: asked, then along at their side (SHARE_GAP 0.53 m, the umbrella side), keeping pace (close: stepping straight onto the spot, no circling), for up to SHARE_MAX 4 min; the holder's `umbrella_guest` leans the canopy over both; `world.c` underUmbrella counts a head within 0.9 x its radius under anybody's open umbrella as dry. Checked: under yours, standing and walking: 0.6 m beside you, dry.
+- Sitting out heavy rain (`ai.c` rainRoom, `Site_rainRoom`): the guests go to the middle of your garages' kitchen; from RAIN_SIT 0.45 they sit down on a free chair or the new log bench by its back wall (else the floor) and stay till the rain is nearly over (half of SHELTER_RAIN). Checked: two on the chairs at the table for 100 s of hard rain, up again when it stopped.
+
 ## Open tasks
 
 Most important first.
@@ -1228,7 +1240,7 @@ Most important first.
 - "No shirt no service" policy (decision open)
 - Clothing, still to do: more outfits for extra people (only the performance test has any: they all wear trousers)
 - Tents, still to do: NPCs taking theirs down on their own (leaving)
-- Weather, still to do: hats (nobody wears one yet) blown off; umbrellas while working (now only idle people fetch one); an umbrella blown inside out
+- Weather, still to do: hats (nobody wears one yet) blown off; umbrellas while working (now only idle people fetch one); an umbrella blown inside out; more roofed places to sit out the rain together (outdoors: a shelter, a pavilion - `Site_rainRoom` is the one place now)
 - Sound, still to do: the house's doors (they have no doors that open yet)
 
 **Saves, still to do**
@@ -1241,4 +1253,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 2.35.0 (build 9450b3c)`
+State of `entropie 2.36.0 (build 25ed4f6)`
diff --git a/entropie/assets/shaders/figure.frag b/entropie/assets/shaders/figure.frag
index 7859d55..3debd18 100644
--- a/entropie/assets/shaders/figure.frag
+++ b/entropie/assets/shaders/figure.frag
@@ -73,7 +73,7 @@ const float SELF_SHADOW_SKIP  = 0.10; // meters
 
 // ---------- Body parts ----------
 
-const int MAX_PARTS = 41; // (a person with everything: a skirt, a jacket tied round, a cigarette, a Urinella, a stream)
+const int MAX_PARTS = 42; // (a person with everything: a skirt, a jacket tied round, a cigarette, a Urinella, a stream)
 vec3  partA[MAX_PARTS], partB[MAX_PARTS];
 float partR1[MAX_PARTS], partR2[MAX_PARTS];
 float partBlend[MAX_PARTS];   // smooth-union radius with the rest of the body
@@ -158,10 +158,26 @@ void buildBody() {
         vec3 knot = belt + chestForward * (garment >= 2 ? 0.168 : 0.125); // (over a skirt's top)
         addPart(knot, knot, 0.026, 0.026, 0.01, 1.0, M_JACKET);
         vec3 across = normalize(joints[J_HIP_R] - joints[J_HIP_L]);
-        addPart(knot, knot - spineDir * 0.17 + chestForward * 0.02 - across * 0.03, 0.022, 0.018, 0.01, 1.0, M_JACKET); // (the sleeves' ends)
-        addPart(knot, knot - spineDir * 0.14 + chestForward * 0.02 + across * 0.03, 0.022, 0.018, 0.01, 1.0, M_JACKET);
+        for (int s = 0; s < 2; s++) { // (the sleeves' ends, pushed out in front by a thigh coming up)
+            float len = s == 0 ? 0.17 : 0.14;
+            vec3 end = knot - spineDir * len + chestForward * 0.02 + across * (s == 0 ? -0.03 : 0.03);
+            vec3 hip = joints[J_HIP_L + s * 4], knee = joints[J_KNEE_L + s * 4], leg = knee - hip;
+            float t = clamp(dot(end - hip, leg) / max(dot(leg, leg), 1e-4), 0.0, 1.0);
+            float ahead = dot(mix(hip, knee, t) - end, chestForward) + mix(0.085, 0.058, t) + 0.02;
+            if (ahead > 0.0) end = knot + normalize(end + chestForward * ahead - knot) * len;
+            addPart(knot, end, 0.022, 0.018, 0.01, 1.0, M_JACKET);
+        }
+        // the back hangs in two halves, each pushed out behind by its thigh going back (no leg through it)
         vec3 back = belt - chestForward * 0.085;
-        addPart(back, back - spineDir * 0.27 - chestForward * 0.02, 0.125, 0.135, 0.015, 0.25, M_JACKET);
+        for (int s = 0; s < 2; s++) {
+            vec3 top = back + across * (s == 0 ? -0.055 : 0.055);
+            vec3 hang = top - spineDir * 0.27 - chestForward * 0.02;
+            vec3 hip = joints[J_HIP_L + s * 4], knee = joints[J_KNEE_L + s * 4], leg = knee - hip;
+            float t = clamp(dot(hang - hip, leg) / max(dot(leg, leg), 1e-4), 0.0, 1.0);
+            float behind = dot(mix(hip, knee, t) - hang, -chestForward) + mix(0.085, 0.058, t) + 0.03;
+            if (behind > 0.0) hang = top + normalize(hang - chestForward * behind - top) * 0.27;
+            addPart(top, hang, 0.072, 0.078, 0.015, 0.25, M_JACKET);
+        }
     }
 
     // What's on the legs: trousers, shorts, a skirt or a dress (squatting to pee:
diff --git a/entropie/commit_message.txt b/entropie/commit_message.txt
index 6a95ab0..cbec05c 100644
--- a/entropie/commit_message.txt
+++ b/entropie/commit_message.txt
@@ -1,6 +1,10 @@
-entropie 2.35.0: Alice pants
+entropie 2.36.0: rain together, steady view on slopes
 
-- Alice pants after a run (and in the midday sun): quick breaths you hear
-  close by, her chest heaving; it eases off over half a minute
-- She no longer runs on the spot against the car when called: held up by
-  something, she goes round it (legs move by how fast she really goes)
+- No more shaking view walking down steep slopes (the bank by your garages)
+- The pullover tied round the waist no longer goes through the legs
+- Umbrellas held properly: open, up in front of the shoulder with the shaft
+  leaning over the head; folded, carried by the middle, tip down
+- People without an umbrella ask to come under someone's (yours too) and
+  walk along side by side; the umbrella leans over both
+- In hard rain the guests sit it out in your garages' kitchen (chairs, a
+  new log bench, or the floor) and get up when it stops
diff --git a/entropie/src/people/ai.c b/entropie/src/people/ai.c
index 1fa504f..2b0ca3f 100644
--- a/entropie/src/people/ai.c
+++ b/entropie/src/people/ai.c
@@ -2066,9 +2066,126 @@ static bool holdsUmbrella(const Object *self) {
     return false;
 }
 
+/* The open umbrella this one holds up, if any. */
+static const Object *openUmbrella(const Object *o) {
+    for (int h = 0; h < 2; h++) {
+        const Object *u = o->hands.item[h];
+        if (u && u->type == OBJ_UMBRELLA && u->umbrella_open && o->hands.state[h] == HAND_HOLDING) return u;
+    }
+    return NULL;
+}
+
+#define SHARE_SEE   12.0f  /* m: somebody under an umbrella this near, nobody with them: "can I come under?" */
+#define SHARE_MAX  240.0f  /* s at most along with them (then off under a roof after all) */
+#define SHARE_GAP    0.53f /* m beside them (shoulder to shoulder: their bodies just clear) */
+
+/* Somebody near holding an umbrella up, nobody under it with them yet. */
+static Object *umbrellaToShare(const Object *self, Object *const *objects, int count) {
+    Object *best = NULL;
+    float best_d = SHARE_SEE;
+    for (int i = 0; i < count; i++) {
+        Object *o = objects[i];
+        if (o == self || !o->is_character || o->type == OBJ_DOG || !openUmbrella(o) || Body_isDown(&o->body)) continue;
+        if (o->umbrella_guest && o->umbrella_guest != self && o->umbrella_guest_t > 0.0f) continue;
+        float d = flatDistance(o->position, self->position);
+        if (d < best_d) { best_d = d; best = o; }
+    }
+    return best;
+}
+
+/* Under somebody's umbrella: at their side (the side they hold it on), keeping up with them. */
+static bool walkUnder(Object *self, Object *const *objects, int count, float dt) {
+    Ai *ai = &self->ai;
+    Object *h = ai->share;
+    const Object *u = h ? openUmbrella(h) : NULL;
+    if (!h || !u || !g_world || g_world->rain < SHELTER_RAIN * 0.5f || (ai->share_time += dt) > SHARE_MAX || self->indoors > 0.0f ||
+        self->in_tent || Body_isDown(&self->body) || Body_isDown(&h->body)) {
+        if (h && h->umbrella_guest == self) h->umbrella_guest = NULL;
+        ai->share = NULL;
+        return false;
+    }
+    h->umbrella_guest = self;
+    h->umbrella_guest_t = 0.5f;
+    if (self->sitting) stand(self);
+    Vec3 facing = Object_facing(h), right = vec3_normalize(vec3_cross(facing, UP));
+    float side = h->hands.item[1] == u ? 1.0f : -1.0f; /* (the umbrella between them) */
+    Vec3 spot = vec3_add(h->position, vec3_add(vec3_scale(right, side * SHARE_GAP * h->look.height / 1.75f), vec3_scale(facing, -0.05f)));
+    float d = flatDistance(spot, self->position);
+    float with = vec3_length(vec3(h->body.velocity.x, 0.0f, h->body.velocity.z)); /* (keeping up with them) */
+    if (d < (with > 0.3f ? 0.12f : 0.3f)) { stand(self); turnTowards(self, facing, dt); return true; } /* (they stand: so do we - no circling round the spot) */
+    float speed = fminf(d > 3.0f ? runSpeed(self) * 0.8f : WALK_SPEED * 1.3f, with + d * 1.5f + 0.1f);
+    if (d < 1.5f) { /* close: straight onto the spot, a step aside or along, facing the way they go (no circling round it) */
+        Vec3 to = vec3_scale(flat(vec3_sub(spot, self->position)), 1.0f / d);
+        self->movement = vec3_scale(to, speed);
+        turnTowards(self, with > 0.3f ? flat(h->body.velocity) : facing, dt);
+        Vec3 f = Object_facing(self), r = vec3_normalize(vec3_cross(f, UP));
+        self->move_local = vec3(vec3_dot(to, r), 0.0f, vec3_dot(to, f));
+        ai->walk_dir = vec3_zero();
+        return true;
+    }
+    Vec3 dir = ai_steer(self, h, objects, count, spot);
+    if (vec3_length(dir) < 0.5f) { stand(self); return true; }
+    ai_walk(self, dir, speed, dt);
+    return true;
+}
+
+#define RAIN_SIT    0.45f /* rain this hard: down on a seat in there to wait it out (lighter: they stand about) */
+#define SEAT_LOOK   6.5f  /* m round the middle of the room: the seats in it */
+
+/* A chair nobody sits on, or a bench with room on it, in the room: the nearest. */
+static Object *freeSeatIn(const Object *self, Object *const *objects, int count, Vec3 room) {
+    Object *best = NULL;
+    float best_d = 1e9f;
+    for (int i = 0; i < count; i++) {
+        Object *o = objects[i];
+        if (o->type != OBJ_FURNITURE || (o->variant != FURN_CHAIR && o->variant != FURN_BENCH) || flatDistance(o->position, room) > SEAT_LOOK) continue;
+        if (o->variant == FURN_CHAIR) {
+            bool taken = false;
+            for (int j = 0; j < count && !taken; j++) taken = objects[j] != self && objects[j]->is_character && objects[j]->seat == o;
+            if (taken) continue;
+        } else if (Object_benchRoom(self, o, self->position, false) == ROOM_NONE) continue;
+        float d = flatDistance(o->position, self->position);
+        if (d < best_d) { best_d = d; best = o; }
+    }
+    return best;
+}
+
+/* In under the roof of your garages' kitchen; raining hard, down on a free seat (or on the floor) till it's over. */
+static bool rainRoom(Object *self, Object *const *objects, int count, float dt) {
+    Ai *ai = &self->ai;
+    if (self->sitting && ai->rain_seated) { rest(ai, 3.0f, 6.0f); return true; } /* (sitting it out) */
+    ai->rain_seated = false;
+    if (self->sitting) self->sitting = false;
+    Vec3 room = Site_rainRoom();
+    Vec3 spot = vec3_add(room, vec3(0.8f * ai->side, 0.0f, 0.8f * ai->side));
+    if (flatDistance(room, self->position) > 3.5f || g_world->rain < RAIN_SIT) {
+        if (goToPoint(self, spot, 0.7f, objects, count, dt)) rest(ai, 3.0f, 6.0f);
+        return true;
+    }
+    Object *seat = freeSeatIn(self, objects, count, room);
+    if (seat) {
+        float near = 0.6f + 0.5f * seat->size.x;
+        if (flatDistance(seat->position, self->position) > near && !goToPoint(self, seat->position, near, objects, count, dt)) return true;
+        stand(self);
+        if (Object_sit(self, seat, room)) {
+            ai->rain_seated = true;
+            if (random01(ai) < 0.3f) Ai_say(self, random01(ai) < 0.5f ? "We'll sit this one out." : "Might as well sit down.");
+            rest(ai, 3.0f, 6.0f);
+            return true;
+        }
+    }
+    /* no seat free: down on the floor where they stand (near the middle) */
+    if (!goToPoint(self, spot, 0.7f, objects, count, dt)) return true;
+    Object_sit(self, NULL, room);
+    ai->rain_seated = true;
+    rest(ai, 3.0f, 6.0f);
+    return true;
+}
+
 static bool shelter(Object *self, Object *const *objects, int count, float dt) {
     Ai *ai = &self->ai;
-    if (holdsUmbrella(self)) { ai->sheltering = false; ai->umbrella = NULL; return false; } /* (out in it under that) */
+    if (holdsUmbrella(self)) { ai->sheltering = false; ai->umbrella = NULL; ai->share = NULL; return false; } /* (out in it under that) */
+    if (ai->share && walkUnder(self, objects, count, dt)) return true;
     Object *u = ai->umbrella;
     if (u && (u->is_held || !g_world || g_world->rain < SHELTER_RAIN * 0.5f || Hands_freeHand(self) < 0)) u = ai->umbrella = NULL;
     if (u) { /* over to it and up with it */
@@ -2084,12 +2201,30 @@ static bool shelter(Object *self, Object *const *objects, int count, float dt) {
         goToPoint(self, u->position, 0.3f, objects, count, dt);
         return true;
     }
+    if (ai->rain_seated && self->sitting && g_world && g_world->rain >= SHELTER_RAIN * 0.5f) { rest(ai, 3.0f, 6.0f); return true; } /* (sat down: till it's nearly over) */
     if (!g_world || g_world->rain < SHELTER_RAIN || self->indoors > 0.0f || self->in_tent || self->body.asleep || self->riding) {
-        if (!g_world || g_world->rain < SHELTER_RAIN * 0.5f) ai->sheltering = false;
+        if (!g_world || g_world->rain < SHELTER_RAIN * 0.5f) {
+            ai->sheltering = false;
+            if (ai->rain_seated) { /* (over: up again) */
+                ai->rain_seated = false;
+                if (self->sitting && random01(ai) < 0.4f) Ai_say(self, random01(ai) < 0.5f ? "It's stopped raining." : "Right, the rain's over.");
+                self->sitting = false;
+            }
+        }
         return false;
     }
     if (!ai->sheltering) {
         if (!self->rain_out) return false;
+        Object *holder = umbrellaToShare(self, objects, count);
+        if (holder && !self->lives_here && !Body_isDown(&self->body)) { /* "can I come under?" - "sure, come here" */
+            ai->share = holder;
+            ai->share_time = 0.0f;
+            holder->umbrella_guest = self;
+            holder->umbrella_guest_t = 2.0f;
+            Ai_say(self, random01(ai) < 0.5f ? "Room under there for me?" : "Can I come under your umbrella?");
+            if (holder->type != OBJ_PLAYER && holder->ai.speech_time <= 0.0f) Ai_say(holder, random01(ai) < 0.5f ? "Sure, come here." : "Come on, squeeze in.");
+            return walkUnder(self, objects, count, dt);
+        }
         if (Hands_freeHand(self) >= 0 && ai->mode != AI_WORK && !self->lives_here) { /* an umbrella near: that instead */
             Object *best = NULL;
             float best_d = UMBRELLA_FETCH;
@@ -2110,12 +2245,12 @@ static bool shelter(Object *self, Object *const *objects, int count, float dt) {
             if (it && (it->type == OBJ_CRATE || it->type == OBJ_GEAR)) Hands_drop(self, h);
         }
     }
-    if (self->sitting) self->sitting = false;
+    if (self->sitting && !ai->rain_seated) self->sitting = false;
     if (!self->lives_here) { /* (the house is Kurti's: you lot wait it out in your tent, or in your garages' kitchen) */
         Object *tent = Tent_of(objects, count, self);
         bool night = g_world->hour >= 21.0f || g_world->hour < 7.0f;
         if (tent && tent->tent_stage >= TENT_UP && self->body.pose == POSE_UP
-            && (night || flatDistance(tent->position, self->position) < flatDistance(Site_drinksSpot(), self->position))) {
+            && (night || flatDistance(tent->position, self->position) < flatDistance(Site_rainRoom(), self->position))) {
             if (!goToPoint(self, Tent_door(tent), 0.4f, objects, count, dt)) return true;
             freeHands(self, NULL);
             Tent_crawlIn(tent, self);
@@ -2123,9 +2258,7 @@ static bool shelter(Object *self, Object *const *objects, int count, float dt) {
             rest(ai, 2.0f, 3.0f);
             return true;
         }
-        Vec3 spot = vec3_add(Site_drinksSpot(), vec3(0.7f * ai->side, 0.0f, 0.7f * ai->side));
-        if (goToPoint(self, spot, 0.7f, objects, count, dt)) rest(ai, 3.0f, 6.0f);
-        return true;
+        return rainRoom(self, objects, count, dt);
     }
     if (!goToPoint(self, Site_houseDoor(), 0.6f, objects, count, dt)) return true;
     self->indoors = 30.0f; /* (kept while it rains: see world.c) */
diff --git a/entropie/src/people/ai.h b/entropie/src/people/ai.h
index 0d20c8c..553e18f 100644
--- a/entropie/src/people/ai.h
+++ b/entropie/src/people/ai.h
@@ -182,6 +182,7 @@ typedef struct {
     bool           asked_you;   /* asked the player for one (nothing came of it: the machine next time) */
     struct Object *take_from;   /* a bottle from this crate, or the next thing out of this car's boot: please (see car.c Car_serve) */
     bool           sheltering;  /* in out of the rain (ai.c shelter) */
+    bool           rain_seated; /* ... sat down to wait it out (in the garages' kitchen) */
     bool           greet;       /* just arrived: over to the fire, saying hello, sitting there till the work starts ... */
     bool           greet_stand; /* ... no room on the benches: standing with the others here instead ... */
     Vec3           greet_at;
@@ -191,6 +192,8 @@ typedef struct {
     Vec3           dodge_to;    /* warned off (a tree coming down): out to here ... */
     float          dodge_t;     /* ... for this long */
     struct Object *umbrella;    /* rain: off to get this umbrella instead of going under a roof (ai.c shelter) */
+    struct Object *share;       /* rain: walking under this one's umbrella, at their side ... */
+    float share_time;           /* ... this long so far */
     struct Object *blown;       /* the wind took this out of their hand: after it (ai.c fetchBlown) ... */
     float          blown_t;     /* ... this long already */
     struct Object *hang_on;     /* warm, the pullover round the waist: over this (a railing, a chair) it goes ... */
diff --git a/entropie/src/people/hands.c b/entropie/src/people/hands.c
index 55b6170..f24ec7a 100644
--- a/entropie/src/people/hands.c
+++ b/entropie/src/people/hands.c
@@ -3,6 +3,11 @@
 #include "world/ladder.h"
 #include "world/physics.h"
 
+#define UMBRELLA_HAND_OUT   0.16f /* an open umbrella: the hand this far out to its side ... */
+#define UMBRELLA_HAND_UP    1.16f /* ... this high ... */
+#define UMBRELLA_HAND_AHEAD 0.17f /* ... and in front (all at 1.75 m tall) */
+#define UMBRELLA_TOP        2.05f /* the shaft points at this high over the head's middle */
+
 #define ONE_HAND_MAX_WEIGHT  10.0f
 #define ONE_HAND_MAX_SIZE    0.6f
 #define TWO_HAND_MAX_WEIGHT  35.0f
@@ -1020,8 +1025,8 @@ void Hands_update(Object *who, float dt) {
                     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_UMBRELLA && item->umbrella_open) /* held up over the head, the hand in front of the shoulder */
-                local = vec3(sign * 0.10f * s, 1.18f * s, 0.24f * s);
+            if (item->type == OBJ_UMBRELLA && item->umbrella_open) /* the hand up in front of its shoulder, the forearm up (the shaft leans in over the head) */
+                local = vec3(sign * UMBRELLA_HAND_OUT * s, UMBRELLA_HAND_UP * s, UMBRELLA_HAND_AHEAD * s);
             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));
@@ -1199,10 +1204,25 @@ void Hands_placeHeld(Object *who) {
                 item->rotation.x = item->rotation.z = 0.0f;
                 Object_syncColliders(item);
             }
-            if (item->type == OBJ_UMBRELLA && item->umbrella_open) { /* upright, the handle in the fist */
-                item->rotation = vec3(0.0f, item->rotation.y, 0.0f);
+            if (item->type == OBJ_UMBRELLA && item->umbrella_open) { /* the handle in the fist, the shaft leaning in so the canopy is over the head (not the shaft in the face) */
+                const float s = who->look.height / 1.75f;
+                Vec3 over = Figure_toWorld(who->position, facing, vec3(0.0f, UMBRELLA_TOP * s, 0.03f * s));
+                const Object *g = who->umbrella_guest;
+                if (g && who->umbrella_guest_t > 0.0f && hypotf(g->position.x - who->position.x, g->position.z - who->position.z) < 1.2f) { /* somebody under it too: over the two of them */
+                    over.x = 0.5f * (over.x + g->position.x);
+                    over.z = 0.5f * (over.z + g->position.z);
+                }
+                Vec3 up = vec3_normalize(vec3_sub(over, hand));
+                Vec3 side = vec3_cross(up, facing);
+                Vec3 ahead = vec3_length(side) > 1e-3f ? vec3_normalize(vec3_cross(vec3_normalize(side), up)) : facing;
+                item->rotation = euler_yxz_from_axes(up, ahead);
+                Object_syncColliders(item);
+                center = vec3_add(hand, vec3_scale(up, UMBRELLA_LENGTH * 0.5f - 0.07f));
+            } else if (item->type == OBJ_UMBRELLA && h->drink_hand != i) { /* folded: in the fist by the middle, the tip ahead and down, the crook behind */
+                Vec3 along = vec3_normalize(vec3_add(vec3_scale(facing, 0.9f), vec3(0.0f, -0.42f, 0.0f)));
+                item->rotation = euler_yxz_from_axes(along, vec3_normalize(vec3_cross(vec3_cross(along, facing), along)));
                 Object_syncColliders(item);
-                center = vec3_add(hand, vec3(0.0f, UMBRELLA_LENGTH * 0.5f - 0.07f, 0.0f));
+                center = vec3_add(hand, vec3_scale(along, 0.06f));
             } else if (Object_isBottle(item)) {
                 /* The hand around the bottle where it grips it */
                 center = vec3_sub(hand, vec3_scale(item->colliders[0].axes[1], bottleGrip(who, i, item)));
diff --git a/entropie/src/world/object.h b/entropie/src/world/object.h
index dabb0da..e6e58cd 100644
--- a/entropie/src/world/object.h
+++ b/entropie/src/world/object.h
@@ -187,6 +187,8 @@ typedef struct Object {
     struct Object *owner2;    /* ... tents: and who shares it */
     int        crate_empties; /* crates: of the bottles in it (stored), this many are empties put back ... */
     unsigned char crate_slot[CRATE_SLOTS];
+    struct Object *umbrella_guest; /* people: somebody under their umbrella with them (the canopy leans their way) ... */
+    float      umbrella_guest_t;   /* ... kept up by them this long */
     bool       umbrella_open; /* umbrellas: open (held up in the rain) */ /* ... and what's in each slot (CRATE_SLOT: kind and sips; see car.c Crate_*) */
     int        tent_stage;    /* tents: TentStage ... */
     float      tent_work;     /* ... how far the stage is (0..1) ... */
diff --git a/entropie/src/world/physics.c b/entropie/src/world/physics.c
index e451e55..ff75df4 100644
--- a/entropie/src/world/physics.c
+++ b/entropie/src/world/physics.c
@@ -575,6 +575,20 @@ static void corners(const Object *obj, int i, Vec3 out[8]) {
     }
 }
 
+/* People: the highest ground under the middle and the corners of their footprint
+   (also where they're kept down to walking down a slope: the same ground, or on
+   a steep one they'd be pushed up onto the uphill corner one frame and snapped
+   down to the middle the next - the view shook). */
+static float footprintGround(const Collider *c, Vec3 center, const Terrain *terrain) {
+    float ex = projectRadius(c, vec3(1, 0, 0)) * 0.7f;
+    float ez = projectRadius(c, vec3(0, 0, 1)) * 0.7f;
+    float ground = Terrain_height(terrain, center.x, center.z);
+    ground = fmaxf(ground, Terrain_height(terrain, center.x - ex, center.z - ez));
+    ground = fmaxf(ground, Terrain_height(terrain, center.x + ex, center.z - ez));
+    ground = fmaxf(ground, Terrain_height(terrain, center.x - ex, center.z + ez));
+    return fmaxf(ground, Terrain_height(terrain, center.x + ex, center.z + ez));
+}
+
 /* Keeps every collider above the terrain surface. Things: each corner over
    the ground right under it (they rest on their real contact points, not on
    the highest ground anywhere under them). People: their feet do the rest. */
@@ -607,16 +621,7 @@ static void resolveTerrain(Object *obj, const Terrain *terrain) {
         const Collider *c = &obj->colliders[i];
         Vec3  center = Object_colliderCenter(obj, i);
         float bottom = center.y - projectRadius(c, WORLD_UP);
-
-        /* Highest ground under the center and the footprint corners */
-        float ex = projectRadius(c, vec3(1, 0, 0)) * 0.7f;
-        float ez = projectRadius(c, vec3(0, 0, 1)) * 0.7f;
-        float ground = Terrain_height(terrain, center.x, center.z);
-        ground = fmaxf(ground, Terrain_height(terrain, center.x - ex, center.z - ez));
-        ground = fmaxf(ground, Terrain_height(terrain, center.x + ex, center.z - ez));
-        ground = fmaxf(ground, Terrain_height(terrain, center.x - ex, center.z + ez));
-        ground = fmaxf(ground, Terrain_height(terrain, center.x + ex, center.z + ez));
-
+        float ground = footprintGround(c, center, terrain);
         if (bottom < ground) {
             land(obj, ground - bottom);
             obj->on_terrain = true;
@@ -690,7 +695,7 @@ static void snapToTerrain(Object *obj, const Terrain *terrain) {
     const Collider *c = &obj->colliders[0];
     Vec3 center = Object_colliderCenter(obj, 0);
     float bottom = center.y - projectRadius(c, WORLD_UP);
-    float gap = bottom - Terrain_height(terrain, center.x, center.z);
+    float gap = bottom - footprintGround(c, center, terrain);
     if (gap > 0.0f && gap < GROUND_SNAP) {
         land(obj, -gap);
         obj->on_terrain = true;
diff --git a/entropie/src/world/site.c b/entropie/src/world/site.c
index 5ffc5ac..f00574a 100644
--- a/entropie/src/world/site.c
+++ b/entropie/src/world/site.c
@@ -573,6 +573,11 @@ Vec3 Site_drinksSpot(void) { /* in the kitchen of your garages, along its front
     return placed(vec3(20.1f, 0.0f, 6.0f), WALLED_CENTER, g_garages.origin, g_garages.yaw, g_garages.zsign);
 }
 
+Vec3 Site_rainRoom(void) { /* the middle of the kitchen in your garages: where the guests wait out the rain */
+    if (!g_garages.set) return Site_drinksSpot();
+    return placed(vec3(23.6f, 0.0f, 1.2f), WALLED_CENTER, g_garages.origin, g_garages.yaw, g_garages.zsign);
+}
+
 Vec3 Site_houseDoor(void) { return court(MANOR_A + 6.0f, MANOR_B - 7.6f); } /* (out in front of the main building's left door) */
 
 bool Site_inHouse(float x, float z) {
@@ -787,6 +792,7 @@ static const RoomPiece ROOMS[] = {
     { FURN_STOVE,     { 26.65f, 0, 3.2f }, { 0.6f, 0.9f, 0.6f }, 0.0f },
     { FURN_SHELF,     { 26.75f, 0, 7.8f }, { 1.2f, 1.8f, 0.4f }, 0.0f },
     { FURN_TABLE,     { 23.3f, 0, 4.0f }, { 1.6f, 0.75f, 0.9f }, 0.0f },
+    { FURN_BENCH,     { 26.7f, 0, -1.6f }, { 2.2f, SEAT_HEIGHT, 0.36f }, 0.0f }, /* (along the back wall: waiting out the rain) */
     { FURN_CHAIR,     { 22.5f, 0, 4.0f }, { 0.45f, 0.9f, 0.45f }, PI_F },
     { FURN_CHAIR,     { 24.1f, 0, 4.0f }, { 0.45f, 0.9f, 0.45f }, 0.0f },
     /* 3: car repairs (the wide door) */
diff --git a/entropie/src/world/site.h b/entropie/src/world/site.h
index 0dab2af..e6f4af5 100644
--- a/entropie/src/world/site.h
+++ b/entropie/src/world/site.h
@@ -27,6 +27,8 @@
    along: the way things are laid out side by side there; door: the way the
    tents' doors face. */
 Vec3  Site_drinksSpot(void);
+/* The middle of the kitchen in your garages: a roof to wait out the rain under, seats round it. */
+Vec3 Site_rainRoom(void);
 Vec3  Site_campSpot(int k);
 Vec3  Site_campAlong(void);
 Vec3  Site_campDoor(void);
diff --git a/entropie/src/world/world.c b/entropie/src/world/world.c
index 0736334..425f2a6 100644
--- a/entropie/src/world/world.c
+++ b/entropie/src/world/world.c
@@ -768,8 +768,11 @@ static void clothesWet(World *world, Object *o, bool by_fire, bool night) {
    the waist). Put down somewhere: people go and get it, you are told where it
    is. You: put on for you once a cold spell (then it's up to you, B). */
 /* An umbrella in hand: open while it rains (out in it), folded otherwise. Is one open over them? */
-static bool underUmbrella(World *world, Object *o) {
+#define UMBRELLA_COVERS 0.9f /* of its radius: a head this near under the middle of an open umbrella stays dry */
+
+static bool underUmbrella(World *world, Object *o, float dt) {
     bool over = false;
+    if ((o->umbrella_guest_t -= dt) <= 0.0f) { o->umbrella_guest = NULL; o->umbrella_guest_t = 0.0f; } /* (nobody with them any more) */
     for (int h = 0; h < 2; h++) {
         Object *u = o->hands.item[h];
         if (!u || u->type != OBJ_UMBRELLA || o->hands.state[h] != HAND_HOLDING) continue;
@@ -777,12 +780,25 @@ static bool underUmbrella(World *world, Object *o) {
                            !Weather_covered(world, vec3_add(o->position, vec3(0.0f, o->look.height + 0.3f, 0.0f)));
         over = over || u->umbrella_open;
     }
+    if (over || o->is_held) return over;
+    /* under somebody else's (walking along with them) */
+    for (int i = 0; i < world->count && !over; i++) {
+        const Object *p = world->objects[i];
+        if (p == o || !p->is_character) continue;
+        for (int h = 0; h < 2 && !over; h++) {
+            const Object *u = p->hands.item[h];
+            if (!u || u->type != OBJ_UMBRELLA || !u->umbrella_open || p->hands.state[h] != HAND_HOLDING) continue;
+            Vec3 top = vec3_add(Object_colliderCenter(u, 0), vec3_scale(u->colliders[0].axes[1], UMBRELLA_LENGTH * 0.5f));
+            float dx = top.x - o->position.x, dz = top.z - o->position.z;
+            over = dx * dx + dz * dz < (UMBRELLA_COVERS * UMBRELLA_RADIUS) * (UMBRELLA_COVERS * UMBRELLA_RADIUS) && top.y > o->position.y + o->look.height;
+        }
+    }
     return over;
 }
 
 static void dressForNight(World *world, Object *o, float dt) {
     if (o->type == OBJ_DOG) return;
-    bool umbrella = underUmbrella(world, o);
+    bool umbrella = underUmbrella(world, o, dt);
     bool night = world->hour >= 21.0f || world->hour < 7.0f;
     bool by_fire = Camp_fireNear(world->objects, world->count, o->position, 4.0f, true) != NULL;
     float cold = !night ? -2.0f : by_fire || (o->in_tent && Tent_hasBed(o->in_tent, o)) ? -1.0f : 1.0f - 0.8f * fminf(o->drunk, 1.0f);