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

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.9.0: rain, soda and Mate crates, dev menu time/weather

Christian Immanuel · 2026-10-20 15:18 · 58998112d26126ae906853b0469519841a54fde6

- Weather: dry spells and showers; drops round the camera, grey sky,
  wetter look, real water on the ground, fires burn down faster
- People shelter: Kurti and sons into the house, the others into the
  garage kitchen; rain makes you colder
- Two more crates in the boot: soda (cola and orange) and Mate (wakes you up)
- No job offers until the tents stand and everything is unloaded
- Dev menu: time of day, game speed, weather, skip the start
 build.sh                           |   2 +-
 commit_message.txt                 |   9 +++
 entropie/ARCHITECTURE.md           |   2 +-
 entropie/Makefile                  |   2 +-
 entropie/README.md                 |  17 +++++-
 entropie/STATE.md                  |  11 +++-
 entropie/assets/shaders/scene.frag |   4 ++
 entropie/src/game/game.c           |  20 +++++++
 entropie/src/game/game.h           |   1 +
 entropie/src/game/game_arrival.c   |  62 +++++++++++++++++---
 entropie/src/game/game_dev.c       |  38 ++++++++++++-
 entropie/src/game/game_internal.h  |   1 +
 entropie/src/people/ai.c           |  40 ++++++++++++-
 entropie/src/people/ai.h           |   3 +
 entropie/src/people/hands.c        |   1 +
 entropie/src/render/renderer.c     |   2 +
 entropie/src/render/renderer.h     |   2 +
 entropie/src/render/shapes.h       |   6 +-
 entropie/src/render/shapes_props.c |  45 ++++++++++++---
 entropie/src/world/car.c           |  23 ++++++--
 entropie/src/world/car.h           |   9 ++-
 entropie/src/world/object.c        |  30 +++++++---
 entropie/src/world/object.h        |   4 ++
 entropie/src/world/weather.c       | 114 +++++++++++++++++++++++++++++++++++++
 entropie/src/world/weather.h       |  32 +++++++++++
 entropie/src/world/world.c         |  12 +++-
 entropie/src/world/world.h         |   6 ++
 27 files changed, 452 insertions(+), 46 deletions(-)

diff --git a/build.sh b/build.sh
index 914c409..c062845 100644
--- a/build.sh
+++ b/build.sh
@@ -1 +1 @@
-cd .. && rm -rf entropie; tar -xvf $SHARED_CLAUDE/entropie/entropie.tar.gz && echo "$(git add . && git commit -m "$(cat entropie/commit_message.txt)")" && cd entropie && make clean && make && cat commit_message.txt
+cd .. && rm -rf entropie; mv $SHARED_CLAUDE/entropie/commit_message.txt . && tar -xvf $SHARED_CLAUDE/entropie/entropie.tar.gz && echo "$(git add . && git commit -m "$(cat commit_message.txt)")" && cd entropie && make clean && make && cat commit_message.txt
diff --git a/commit_message.txt b/commit_message.txt
new file mode 100644
index 0000000..cc6fd6e
--- /dev/null
+++ b/commit_message.txt
@@ -0,0 +1,9 @@
+entropie 2.9.0: rain, soda and Mate crates, dev menu time/weather
+
+- Weather: dry spells and showers; drops round the camera, grey sky,
+  wetter look, real water on the ground, fires burn down faster
+- People shelter: Kurti and sons into the house, the others into the
+  garage kitchen; rain makes you colder
+- Two more crates in the boot: soda (cola and orange) and Mate (wakes you up)
+- No job offers until the tents stand and everything is unloaded
+- Dev menu: time of day, game speed, weather, skip the start
diff --git a/entropie/ARCHITECTURE.md b/entropie/ARCHITECTURE.md
index e4ab575..f196f39 100644
--- a/entropie/ARCHITECTURE.md
+++ b/entropie/ARCHITECTURE.md
@@ -10,7 +10,7 @@ same version when the structure changes (rules for working on it: [RULES.md](RUL
 | `src/core/` | window, GL loader (`gl.h`: every GL call is listed there), input | core |
 | `src/math/` | vectors, matrices, frustum, yaw conventions (`linalg.h`) | math |
 | `src/render/` | renderer and its passes, shaders' uniforms, meshes, procedural shapes (`shapes*.c`), figures, camera, sky, UI | core, math, render |
-| `src/world/` | `Object`, `World`, physics, terrain, the map (`site.c`, `scene.c`, `manor.c`, `course.c`), tools, forestry, ladder, hose, camp, clothes, car, water, grass, grid, day | everything but game |
+| `src/world/` | `Object`, `World`, physics, terrain, the map (`site.c`, `scene.c`, `manor.c`, `course.c`), tools, forestry, ladder, hose, camp, clothes, car, water, weather, grass, grid, day | everything but game |
 | `src/people/` | bodies (animation), hands, AI, talk, condition, dog, climbing | everything but game |
 | `src/game/` | the frame: input to actions, player, camera follow, talk and dev UI, the arrival by car at the start | all |
 
diff --git a/entropie/Makefile b/entropie/Makefile
index 0fd9a8b..93ca4b0 100644
--- a/entropie/Makefile
+++ b/entropie/Makefile
@@ -1,5 +1,5 @@
 APP       := entropie
-VERSION   := 2.8.0
+VERSION   := 2.9.0
 
 PREFIX    ?= /usr
 BINDIR    ?= $(PREFIX)/bin
diff --git a/entropie/README.md b/entropie/README.md
index b6bcb35..67bb8ef 100644
--- a/entropie/README.md
+++ b/entropie/README.md
@@ -33,14 +33,25 @@ Painted in the rotoscope style of *Waking Life* (2001):
 - Then the work: the boot opens, the others unload. Crates go into the
   kitchen in your garages, the camping things up to the meadow behind them.
   Help: left click at the boot takes out the next thing (crates: both hands)
-- In the boot: 2 crates of beer, 2 of water, everybody's sleeping bag and
-  mat, the big tent (Volkram and Jana) and two small ones (Toni, you)
+- In the boot: 2 crates of beer, 2 of water, one of soda (cola and orange),
+  one of Mate, everybody's sleeping bag and mat, the big tent (Volkram and Jana) and two small ones (Toni, you)
 - A crate is heavy: carrying it tires you (the weaker sooner). Worn out, you
   put it down for a moment; or two carry it (F to hold on, E to lift together).
   The others put theirs down for a breather too
 - Crates: left click takes a bottle out (the others help themselves too),
   E / Q carries the whole crate
 - The hint line says what's to do next
+- Nobody asks you for other jobs (wood and so on) until the tents stand and
+  everything is unloaded
+
+## Weather
+
+- Dry spells of hours, then showers now and then (the first few hours stay dry)
+- Rain falls round you, the sky greys over, everything looks wetter; the
+  ground gets real water (puddles, mud), a fire burns down faster
+- People get out of it: Kurti and his sons into the house, the others into
+  your garages' kitchen; out again when it stops
+- Out in the rain you get cold sooner (pullover)
 
 ## Tents
 
@@ -682,7 +693,7 @@ say no more often and don't come to you for a bit.
 | V             | Third / first person |
 | I             | Show all keys in game |
 | Tab           | Your stats (Left / Right: other page) |
-| F1            | Dev menu: pick anyone, change body, needs, temperament, likes and skills (Up / Down, Left / Right); teleport you to the parkour or the yard |
+| F1            | Dev menu: pick anyone, change body, needs, temperament, likes and skills (Up / Down, Left / Right); teleport you to the parkour or the yard; time of day, game speed, weather, skip the start |
 | Esc           | Menu: resume, keys, quit (the game waits meanwhile) |
 
 The game starts in third person. Close to a wall the view moves in, never through it.
diff --git a/entropie/STATE.md b/entropie/STATE.md
index fe921b5..774138f 100644
--- a/entropie/STATE.md
+++ b/entropie/STATE.md
@@ -208,6 +208,13 @@ Round 2.8:
 - On a break (`ai_work.c` putToolsDown): the orderly (stack care over 0.5) lay their tools down side by side, square, a step back; the others drop them any old way. An axe or shears put down or come to rest lies on its side (`Hands_drop`; `physics.c` settleOnGround tips a blade-up one over).
 - Default things thinned out: no bottles or vodka lying about, 4 pieces of litter in the yard (was 26), 2 in each open garage (was 12).
 
+Round 2.9:
+- Settled first (`Ai_setSettled`, from `game.c`): nobody comes up asking for jobs (`maybeApproach`) until the arrival's work is done (boot empty, tents up). Kurti and his sons never ask.
+- Two more crates (`car.c` CARGO_SODA / CARGO_MATE; crate variant from weight: 0 beer, 1 water, 2 soda, 3 mate): soda 12 x 1.2 kg (cola and orange taking turns, `BOTTLE_COLA` / `BOTTLE_ORANGE`), Mate 20 x 0.85 kg (`BOTTLE_MATE`). `Object_setDrink`: per style quench / refresh / nourish / `wake` (Mate 0.04, cola 0.01 off sleepiness a sip, `hands.c`). Boot: Mate, soda, water x2, beer x2 under the tents, mats, bags.
+- Dev menu (`game_dev.c`): time of day (Left / Right: an hour), game speed (1, 3, 10, 30, 60, 120x), weather (on its own, dry, a shower, pouring), skip the start (`game_skipArrival`: parked, out, welcome and chat done, everything unloaded, the tents up).
+- Weather (`world/weather.c`): `world.rain` eases (RAIN_EASE 150 s) to `rain_goal`: dry FIRST_DRY 3 h, then dry spells 2-8 h and showers 12-52 min (0.25..1). `world.wet` soaks in 25 min of hard rain, dries in 3 h (3x at night). 7 mm an hour poured onto the ground within 16 m (`Wetness_pour`, 8 splashes a frame, not under a roof: `Weather_covered`, a ray up 14 m). Fires lose 0.004 kg wood a second x rain. Drops (1400 x rain) within 9 m of the camera, 8.5 m/s, a little wind; `Shapes_rain` two crossed two-sided streaks each; none under a roof. Light (`game.c` rainLight): sun x (1 - 0.45 rain), the sky to grey, shadows x (1 - 0.85 rain); `rainWet` in scene.frag: the ground damper, the rest darker.
+- Shelter (`ai.c` shelter, from rain 0.3, out in it: `rain_out` in world.c): crates and gear down, Kurti and his sons into the house (`indoors`, kept while rain > 0.1), the others into your garages' kitchen (at `Site_drinksSpot`). Rain makes you colder outside; a hint the first time.
+
 Unfinished building (Shell_build, at SHELL_AT in the frame's frame): brick shell 6.4 x 4.7, walls 2.9, open front (2 m) to the frame (now only the front half: pillars at z -13.8 / -9.2), window holes, bare floor, tiled roof with slabs. Barn: brick courses on BUILDING_BRICK walls (`addBrickCourses`: mortar a shade lighter, so the ink doesn't trace it), ivy (`Shapes_ivy`, OBJ_STRUCTUREs) on both long sides; the woodpile (OBJ_WOODPILE "Woodpile", WOODPILE_A/B) left of the toilet, everybody's woodpile from the start (`scene.c` Scene_finish). Fireplace tripod: wooden poles, feet TRIPOD_OUT 0.9 m outside the stones, TRIPOD_PAST 0.4 m past the top, a lashing; each pole an OBJ_WALL along it ("Tripod pole"). Gates (FURN_GATE, green boards): in your garages' doorways (GATES: hinge, end), `gate_open` / `gate_t`, `hinge`, `gate_shut` / `gate_out`; Object_swingGate (1.2 s), driven by showProps; a click within 1.2 m opens / shuts both leaves.
 
 Props that show (1.89, `world.c` showProps every frame): the tin's mesh is built again when its butts change (`Shapes_ashtray`: up to 12 show, the ash rises, a heap from 14; `shown` = what's built).
@@ -356,7 +363,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); the pullover over a railing NPCs never do on their own
 - Tents, still to do: taking them down again; wind blowing an unpegged one about; people busy (work) when sleepy go to bed late
-- Rain: showers; people go into their tents (the house is Kurti's), or under the porch / into your garages
+- Rain, still to do: people sheltering in their tents; thunder; wet clothes
 - Main building: "the left building's roof peak is too far left" - to be clarified
 
 **Tools for us**
@@ -366,4 +373,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.8.0 (build 56bae88)`
+State of `entropie 2.9.0 (build 56bba45)`
diff --git a/entropie/assets/shaders/scene.frag b/entropie/assets/shaders/scene.frag
index 3ba5a6d..83ff6c9 100644
--- a/entropie/assets/shaders/scene.frag
+++ b/entropie/assets/shaders/scene.frag
@@ -21,6 +21,7 @@ uniform float boilTime;
 uniform float terrainSize;
 uniform float decalSize;
 uniform float grass;
+uniform float rainWet; // 0..1 everything wet from rain: darker, the ground damp
 uniform sampler2D shadowMap;
 uniform sampler2D waterMap;
 uniform sampler2D surfaceMap; // asphalt, concrete, gravel, bare earth (Site_surface)
@@ -108,6 +109,9 @@ void main() {
     vec4  some   = step(vec4(0.004), wet); // dry ground stays exactly as it was
     float puddle = smoothstep(0.9, 1.4, depth + edge * 1.2 * some.g);
     float damp   = smoothstep(0.06, 0.3, wet.r + edge * 0.15 * some.r) * (0.55 + 0.45 * wet.r);
+    bool  ground = terrainSize > 0.0 || grass > 0.0 || (decalSize > 0.0 && n.y > 0.7);
+    damp = max(damp, rainWet * (ground ? 0.8 : 0.0));
+    if (!ground) albedo *= 1.0 - 0.18 * rainWet; // (walls, roofs, people: a little darker wet)
     float mud    = smoothstep(0.15, 0.35, wet.a + edge * 0.25 * some.a) * (0.6 + 0.4 * wet.a);
     float stain  = smoothstep(0.1, 0.4, wet.b + edge * 0.2 * some.b);
     albedo *= mix(1.0, 0.58, damp);
diff --git a/entropie/src/game/game.c b/entropie/src/game/game.c
index 0cff241..344a407 100644
--- a/entropie/src/game/game.c
+++ b/entropie/src/game/game.c
@@ -273,6 +273,19 @@ static void puttingUp(Game *game) {
     Hands_holdEdge(p, grip, across, 0.0f);
 }
 
+/* Rain: the light duller, the sky grey, no sharp shadows; everything wet. */
+static void rainLight(Game *game) {
+    Lighting *l = &game->daycycle.light;
+    float r = game->world.rain;
+    const Vec3 grey = { 0.56f, 0.58f, 0.62f };
+    float bright = 0.35f + 0.65f * l->day_factor;
+    l->light_color  = vec3_scale(l->light_color, 1.0f - 0.45f * r);
+    l->sky_top      = vec3_lerp(l->sky_top, vec3_scale(grey, 0.8f * bright), fminf(1.0f, 1.3f * r));
+    l->sky_horizon  = vec3_lerp(l->sky_horizon, vec3_scale(grey, bright), fminf(1.0f, 1.2f * r));
+    l->shadow_strength *= 1.0f - 0.85f * r;
+    l->wet = game->world.wet;
+}
+
 /* Worn out with something heavy in your arms (a crate): down it goes for a moment. */
 static void tooHeavy(Game *game) {
     Object *p = game->player;
@@ -287,6 +300,7 @@ void Game_update(Game *game, const Input *input, float dt, float aspect) {
     game->fast_sleep = pb->asleep && pb->pose == POSE_DOWN && input->jump;
     const float speed = game->fast_sleep ? SLEEP_FAST_FORWARD : 1.0f;
     DayCycle_update(&game->daycycle, dt * speed);
+    rainLight(game);
 
     {   /* worn out: shown until a bit rested again */
         float stamina = game->player->needs.stamina;
@@ -302,7 +316,13 @@ void Game_update(Game *game, const Input *input, float dt, float aspect) {
     puttingUp(game);
     game_climbLadder(game, input, dt);
     game_climb(game, input, dt);
+    game->world.eye = game->camera.position;
+    if (game->world.rain > 0.3f && !game->rain_told) {
+        game->rain_told = true;
+        say(game, "It's starting to rain - under a roof (the house, your garages) or into your tent");
+    } else if (game->world.rain < 0.05f) game->rain_told = false;
     game_arrival(game, dt);
+    Ai_setSettled(game->arrival.task == TASK_NONE);
     tooHeavy(game);
     bool warm = game->player->look.top == TOP_JACKET;
     World_update(&game->world, dt, dt * game->daycycle.time_scale * speed);
diff --git a/entropie/src/game/game.h b/entropie/src/game/game.h
index 8f0858b..0c133f6 100644
--- a/entropie/src/game/game.h
+++ b/entropie/src/game/game.h
@@ -75,6 +75,7 @@ typedef struct {
     Arrival  arrival;       /* the start: in by car */
     struct Object *tent;    /* the tent you're working on (left button held) */
     float    dt;            /* this frame's step */
+    bool     rain_told;     /* told it's raining (this shower) */
 } Game;
 
 /* A line of speech above someone's head, in screen pixels (y up from the bottom). */
diff --git a/entropie/src/game/game_arrival.c b/entropie/src/game/game_arrival.c
index 5091f7d..9cf16b9 100644
--- a/entropie/src/game/game_arrival.c
+++ b/entropie/src/game/game_arrival.c
@@ -43,6 +43,7 @@ static Object *person(Game *game, const char *name) {
 static const char *const FRIENDS[] = { "Volkram", "Jana", "Toni" };
 
 static void chat(Game *game, float dt);
+static void settled(Game *game);
 
 /* While you're still to say hello and have a chat: Kurti and his sons stay at the fire. */
 static void hostsStay(Game *game) {
@@ -78,6 +79,8 @@ void game_arrivalStart(Game *game) {
     Object *friends[3];
     for (int k = 0; k < 3; k++) friends[k] = person(game, FRIENDS[k]);
     /* the boot, loaded last first: the camping things on top, the crates under them */
+    Car_load(car, CARGO_MATE, NULL);
+    Car_load(car, CARGO_SODA, NULL);
     Car_load(car, CARGO_WATER, NULL);
     Car_load(car, CARGO_WATER, NULL);
     Car_load(car, CARGO_BEER, NULL);
@@ -150,6 +153,7 @@ void game_arrival(Game *game, float dt) {
     hostsStay(game);
     welcome(game, dt);
     chat(game, dt);
+    settled(game);
     if (a->stage == ARRIVAL_NONE || a->stage == ARRIVAL_DONE || !car) return;
     a->time += dt;
     if (a->stage == ARRIVAL_DRIVING) {
@@ -240,16 +244,20 @@ static void chat(Game *game, float dt) {
 }
 
 /* What to do now (the hint line when nothing else is said there), or NULL. */
-const char *game_task(Game *game) {
+/* The car empty and all the tents up: settled in. */
+static void settled(Game *game) {
     Arrival *a = &game->arrival;
-    if (a->task == TASK_UNLOAD) { /* done once the boot is empty and all the tents stand */
-        bool done = a->car && a->car->load && a->car->load->n == 0;
-        for (int i = 0; i < game->world.count && done; i++) {
-            const Object *o = game->world.objects[i];
-            if ((o->type == OBJ_TENT && o->tent_stage < TENT_UP) || (o->type == OBJ_GEAR && o->variant >= GEAR_TENT_SMALL && !o->broken)) done = false;
-        }
-        if (done) a->task = TASK_NONE;
+    if (a->task != TASK_UNLOAD) return;
+    bool done = a->car && a->car->load && a->car->load->n == 0;
+    for (int i = 0; i < game->world.count && done; i++) {
+        const Object *o = game->world.objects[i];
+        if ((o->type == OBJ_TENT && o->tent_stage < TENT_UP) || (o->type == OBJ_GEAR && o->variant >= GEAR_TENT_SMALL && !o->broken)) done = false;
     }
+    if (done) a->task = TASK_NONE;
+}
+
+const char *game_task(Game *game) {
+    Arrival *a = &game->arrival;
     switch (a->task) {
     case TASK_HELLO:  return "To do: say hello to Kurti and his sons at the fire";
     case TASK_CHAT:   return "To do: sit down with them (C) and have a chat";
@@ -258,6 +266,44 @@ const char *game_task(Game *game) {
     }
 }
 
+/* Dev: the start skipped - parked, everybody out, hello and chat said, the car unloaded and the tents up. */
+void game_skipArrival(Game *game) {
+    Arrival *a = &game->arrival;
+    if (!a->car) return;
+    Car_place(&game->world, a->car, a->path[ARRIVAL_SAMPLES - 1], a->park_yaw);
+    for (int i = 0; i < game->world.count; i++) if (game->world.objects[i]->riding) Car_getOut(game->world.objects[i]);
+    a->stage = ARRIVAL_DONE;
+    a->step = 4;
+    a->greeted = true;
+    a->welcome = 99;
+    a->chat = 99;
+    game->third_person = true;
+    Car_openBoot(a->car, true);
+    Object *drop[CAR_LOAD_MAX];
+    int n = 0;
+    while (a->car->load && a->car->load->n > 0 && n < CAR_LOAD_MAX) {
+        Object *it = Car_takeOut(&game->world, a->car);
+        if (!it) break;
+        drop[n++] = it;
+    }
+    for (int k = 0; k < n; k++) { /* (each where it goes; the tents up there) */
+        Object *it = drop[k];
+        Vec3 at = Car_dropSpot(&game->world, it);
+        it->position = vec3(at.x, World_groundHeight(&game->world, at.x, at.z, at.y + 3.0f) + 0.3f, at.z);
+        Object_syncColliders(it);
+        if (it->type == OBJ_GEAR && it->variant >= GEAR_TENT_SMALL) {
+            Object *tent = Tent_unpack(&game->world, it, Car_campDoor());
+            while (tent && tent->tent_stage < TENT_UP) Tent_work(tent, game->player, 1.0f);
+        }
+    }
+    for (int k = 0; k < 3; k++) {
+        Object *o = person(game, FRIENDS[k]);
+        if (o) { o->ai.greet = false; o->ai.unload = false; o->sitting = false; }
+    }
+    a->task = TASK_NONE;
+    say(game, "Dev: the start skipped");
+}
+
 bool game_arriving(const Game *game) {
     return game->arrival.stage == ARRIVAL_DRIVING || (game->arrival.stage == ARRIVAL_PARKED && game->player->riding);
 }
diff --git a/entropie/src/game/game_dev.c b/entropie/src/game/game_dev.c
index 0cb7d6d..ddcc009 100644
--- a/entropie/src/game/game_dev.c
+++ b/entropie/src/game/game_dev.c
@@ -7,9 +7,10 @@
 #include <stdio.h>
 
 #include "game/game_internal.h"
+#include "world/weather.h"
 
 typedef enum {
-    DEV_WHO, DEV_TELEPORT, DEV_FEMALE, DEV_URINELLA, DEV_SPORTY, DEV_DRUNK, DEV_ENERGY,
+    DEV_WHO, DEV_TELEPORT, DEV_TIME, DEV_SPEED, DEV_WEATHER, DEV_START, DEV_FEMALE, DEV_URINELLA, DEV_SPORTY, DEV_DRUNK, DEV_ENERGY,
     DEV_BLADDER, DEV_HUNGER, DEV_THIRST, DEV_SLEEPY, DEV_STRENGTH, DEV_ANNOYED,
     DEV_PRIDE, DEV_LIVELY, DEV_NERVES, DEV_CLUMSY, DEV_WITS, DEV_HEIGHTS, DEV_WILLING,
     DEV_LIKES,                       /* likes for each job ... */
@@ -90,6 +91,28 @@ void game_dev(Game *game, const Input *input) {
         teleport(game, dir);
         return;
     }
+    if (game->dev_row == DEV_TIME) { /* an hour on or back */
+        float day = 24.0f * 3600.0f;
+        game->daycycle.seconds = fmodf(game->daycycle.seconds + (float)dir * 3600.0f + day, day);
+        return;
+    }
+    if (game->dev_row == DEV_SPEED) {
+        static const float SPEEDS[] = { 1.0f, 3.0f, 10.0f, 30.0f, 60.0f, 120.0f };
+        int k = 0;
+        for (int i = 0; i < 6; i++) if (fabsf(game->daycycle.time_scale - SPEEDS[i]) < fabsf(game->daycycle.time_scale - SPEEDS[k])) k = i;
+        k = k + dir < 0 ? 0 : k + dir > 5 ? 5 : k + dir;
+        game->daycycle.time_scale = SPEEDS[k];
+        return;
+    }
+    if (game->dev_row == DEV_WEATHER) {
+        game->world.weather = (game->world.weather + dir + WEATHER_KINDS) % WEATHER_KINDS;
+        if (game->world.weather == WEATHER_AUTO) game->world.rain_left = 0.0f; /* (on its own again: from now) */
+        return;
+    }
+    if (game->dev_row == DEV_START) {
+        if (dir > 0) game_skipArrival(game);
+        return;
+    }
     Object *o = picked(game);
     if (!o) return;
     Needs *n = &o->needs;
@@ -129,7 +152,7 @@ void game_dev(Game *game, const Input *input) {
 }
 
 /* One line of the menu. */
-static void devLine(const Object *o, int row, char *out) {
+static void devLine(const Game *game, const Object *o, int row, char *out) {
     const Needs *n = &o->needs;
     const Character *c = &o->ai.character;
     if (row >= DEV_LIKES && row < DEV_SKILLS) {
@@ -143,6 +166,15 @@ static void devLine(const Object *o, int row, char *out) {
     switch ((DevRow)row) {
     case DEV_WHO:      snprintf(out, 96, "Who:  %s%s", o->name, o->type == OBJ_PLAYER ? "  (you)" : ""); break;
     case DEV_TELEPORT: snprintf(out, 96, "Teleport you:  <- yard   parkour ->"); break;
+    case DEV_TIME: {
+        int m = (int)(game->daycycle.seconds / 60.0f);
+        snprintf(out, 96, "Time:  %02d:%02d  (an hour back / on)", (m / 60) % 24, m % 60);
+        break;
+    }
+    case DEV_SPEED:    snprintf(out, 96, "Time runs:  %.0fx", game->daycycle.time_scale); break;
+    case DEV_WEATHER:  snprintf(out, 96, "Weather:  %s  (raining %.0f%%, wet %.0f%%)", Weather_name((WeatherKind)game->world.weather),
+                                game->world.rain * 100.0f, game->world.wet * 100.0f); break;
+    case DEV_START:    snprintf(out, 96, "The start:  %s", game->arrival.task == TASK_NONE && game->arrival.stage != ARRIVAL_DRIVING ? "done" : "skip it ->"); break;
     case DEV_FEMALE:   snprintf(out, 96, "Female:  %s", o->female ? "yes" : "no"); break;
     case DEV_URINELLA: snprintf(out, 96, "Urinella:  %s (%d left)", o->urinella ? "yes" : "no", o->urinellas); break;
     case DEV_SPORTY:   snprintf(out, 96, "Sporty:  %.2f", o->fitness); break;
@@ -175,7 +207,7 @@ int Game_devLines(const Game *game, char lines[][96], int max, int *chosen) {
     snprintf(lines[0], 96, "Dev  (F1 closes;  Up / Down: line %d/%d,  Left / Right: change)", game->dev_row + 1, DEV_ROWS);
     int n = 1;
     for (int row = first; row < DEV_ROWS && n < max; row++) {
-        devLine(o, row, lines[n]);
+        devLine(game, o, row, lines[n]);
         if (row == game->dev_row) *chosen = n;
         n++;
     }
diff --git a/entropie/src/game/game_internal.h b/entropie/src/game/game_internal.h
index d5b1825..be9bda5 100644
--- a/entropie/src/game/game_internal.h
+++ b/entropie/src/game/game_internal.h
@@ -27,6 +27,7 @@ void game_arrival(Game *game, float dt);
 bool game_arriving(const Game *game);
 bool game_unloadCar(Game *game);
 const char *game_task(Game *game);
+void game_skipArrival(Game *game);
 
 void game_callHelp(Game *game);
 void game_callOver(Game *game);
diff --git a/entropie/src/people/ai.c b/entropie/src/people/ai.c
index 47b4fac..27b06ca 100644
--- a/entropie/src/people/ai.c
+++ b/entropie/src/people/ai.c
@@ -497,7 +497,7 @@ static Object *findConsumable(const Object *self, Object *const *objects, int co
         bool crate = o->type == OBJ_CRATE; /* (a crate: a bottle out of it) */
         bool fits = want == WANT_FOOD ? Object_isFood(o) : Object_isBottle(o);
         if (want == WANT_BEER && o->alcohol <= 0.0f) fits = false;
-        if (crate) fits = want != WANT_FOOD && o->stored > 0 && (want != WANT_BEER || o->variant == 0);
+        if (crate) fits = want != WANT_FOOD && o->stored > 0 && (want != WANT_BEER || o->variant == 0); /* (thirsty: water, soda or mate too) */
         if (!fits || o->is_held || (!crate && o->sips <= 0) || o->broken) continue;
         if (ai_skipped(&self->ai, o)) continue; /* couldn't get at it */
         bool taken = false;
@@ -1134,8 +1134,12 @@ static void updateApproach(Object *self, Object *const *objects, int count, floa
 
 /* Now and then, depending on who they are: offer to take over the player's
    wood work, or suggest a job that needs doing and that they like. */
+static bool g_settled = true; /* (just arrived: no jobs offered till the car is empty and the tents stand) */
+void Ai_setSettled(bool settled) { g_settled = settled; }
+
 static bool maybeApproach(Object *self, Object *const *objects, int count, const Object *player, float dt) {
     Ai *ai = &self->ai;
+    if (!g_settled || self->lives_here) return false; /* (settling in first; the owners don't do your jobs) */
     if (!player || ai->talk_cooldown > 0.0f || Body_isBusy(&player->body) || ai->annoyed > 0.3f) return false; /* (annoyed: not now) */
     const Mood *m = &self->temperament;
     const Character *c = &ai->character;
@@ -1910,6 +1914,37 @@ static bool goInside(Object *self, Object *const *objects, int count, float dt)
     return true;
 }
 
+/* Rain (out in it): into the house till it's over (people camp in tents,
+   but sit out a shower inside). */
+#define SHELTER_RAIN 0.3f
+
+static bool shelter(Object *self, Object *const *objects, int count, float dt) {
+    Ai *ai = &self->ai;
+    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;
+        return false;
+    }
+    if (!ai->sheltering) {
+        if (!self->rain_out) return false;
+        ai->sheltering = true;
+        if (random01(ai) < 0.6f) Ai_say(self, random01(ai) < 0.5f ? "It's pouring - inside, quick!" : self->lives_here ? "Rain! Into the house." : "Rain! Into the garages.");
+        for (int h = 0; h < 2; h++) { /* (crates and gear down where they are; tools along) */
+            const Object *it = self->hands.item[h];
+            if (it && (it->type == OBJ_CRATE || it->type == OBJ_GEAR)) Hands_drop(self, h);
+        }
+    }
+    if (self->sitting) self->sitting = false;
+    if (!self->lives_here) { /* (the house is Kurti's: you lot wait it out in your garages' kitchen) */
+        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;
+    }
+    if (!goToPoint(self, Site_houseDoor(), 0.6f, objects, count, dt)) return true;
+    self->indoors = 30.0f; /* (kept while it rains: see world.c) */
+    rest(ai, 1.0f, 2.0f);
+    return true;
+}
+
 /* Just arrived: over to the fire to say hello to Kurti and his sons, and sit
    down with them till the work starts (game_arrival.c). */
 static bool greetHosts(Object *self, Object *const *objects, int count, float dt) {
@@ -2078,6 +2113,9 @@ void Ai_update(Object *self, Object *const *objects, int count, float dt) {
     if ((ai->mode == AI_IDLE || ai->mode == AI_WANDER || ai->mode == AI_HELP) && ai_holdLadder(self, objects, count, dt)) return;
     /* Done with a tool: careful people put it back where it belongs, the others leave it lying */
     if ((ai->mode == AI_IDLE || ai->mode == AI_WANDER) && ai_putAway(self, objects, count, dt)) return;
+    /* Rain: inside */
+    if ((ai->mode == AI_IDLE || ai->mode == AI_WANDER || ai->mode == AI_TABLE || ai->mode == AI_WORK || ai->mode == AI_FETCH) &&
+        shelter(self, objects, count, dt)) return;
     /* Unloading the car */
     if ((ai->mode == AI_IDLE || ai->mode == AI_WANDER) && unloadCar(self, objects, count, dt)) return;
     /* Just arrived: hello at the fire */
diff --git a/entropie/src/people/ai.h b/entropie/src/people/ai.h
index e67c949..e68f421 100644
--- a/entropie/src/people/ai.h
+++ b/entropie/src/people/ai.h
@@ -177,6 +177,7 @@ typedef struct {
     float          smokes_time; /* ... on the way this long */
     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           greet;       /* just arrived: over to the fire, saying hello, sitting there till the work starts */
     bool           going_in;    /* a resident on the way into the house (ai.c goInside) */
     struct Object *tent_job;    /* lending a hand with this tent (ai.c pitchTents) ... */
@@ -286,6 +287,8 @@ struct World;
 void Ai_useGrid(struct Grid *grid);
 /* The world (for climbing: ladders, edges). */
 void Ai_useWorld(struct World *world);
+/* Settled in (the car empty, the tents up): from now on they come to you with jobs (firewood, trees...). */
+void Ai_setSettled(bool settled);
 
 /* Before physics: chooses what to do and sets movement and facing. */
 void Ai_update(struct Object *self, struct Object *const *objects, int count, float dt);
diff --git a/entropie/src/people/hands.c b/entropie/src/people/hands.c
index 1bff9f5..9396297 100644
--- a/entropie/src/people/hands.c
+++ b/entropie/src/people/hands.c
@@ -637,6 +637,7 @@ static void advanceDrink(Object *who, float dt) {
             n->thirst  = fmaxf(0.0f, n->thirst - bottle->quench);
             n->hunger  = fmaxf(0.0f, n->hunger - bottle->nourish);
             n->stamina = fminf(1.0f, n->stamina + bottle->refresh);
+            n->sleepiness = fmaxf(0.0f, n->sleepiness - bottle->wake);
             n->bladder = fminf(1.0f, n->bladder + (bottle->quench * 0.25f + bottle->alcohol * 0.3f) / Object_bladder(who));
         }
     }
diff --git a/entropie/src/render/renderer.c b/entropie/src/render/renderer.c
index a12b05b..5b13af6 100644
--- a/entropie/src/render/renderer.c
+++ b/entropie/src/render/renderer.c
@@ -37,6 +37,7 @@ static bool loadShaders(Renderer *r) {
     s->fire_light      = glGetUniformLocation(s->program, "fireLight");
     s->tint            = glGetUniformLocation(s->program, "tint");
     s->grass           = glGetUniformLocation(s->program, "grass");
+    s->rain_wet        = glGetUniformLocation(s->program, "rainWet");
     s->shadow_map      = glGetUniformLocation(s->program, "shadowMap");
     s->terrain_size    = glGetUniformLocation(s->program, "terrainSize");
     s->grass_depth     = glGetUniformLocation(s->program, "grassDepth");
@@ -340,6 +341,7 @@ void Renderer_beginScenePass(Renderer *r, const Camera *camera, const Lighting *
     setVec3(s->sky_top,      light->sky_top);
     setVec3(s->sky_horizon,  light->sky_horizon);
     glUniform1f(s->shadow_strength, light->shadow_strength);
+    glUniform1f(s->rain_wet, light->wet);
     glUniform4f(s->fire_light, light->fire_position.x, light->fire_position.y, light->fire_position.z, light->fire_strength);
 
     glUniform1f(s->terrain_size, 0.0f); /* 0 = regular object */
diff --git a/entropie/src/render/renderer.h b/entropie/src/render/renderer.h
index 849b00b..734bcb9 100644
--- a/entropie/src/render/renderer.h
+++ b/entropie/src/render/renderer.h
@@ -37,6 +37,7 @@ typedef struct {
     float day_factor;       /* 0 = night, 1 = day */
     Vec3  fire_position;    /* a campfire's light ... */
     float fire_strength;    /* ... and how bright it is (0 = none) */
+    float wet;              /* 0..1 everything wet from rain (darker, the ground damp) */
 } Lighting;
 
 typedef struct {
@@ -48,6 +49,7 @@ typedef struct {
     GLint  decal_size;                           /* things lying on the ground: sink with it */
     GLint  tint;                                 /* color multiplier and ember glow */
     GLint  grass;                                /* grass blades: terrain size while drawing them */
+    GLint  rain_wet;                             /* everything wet from rain (0..1) */
     GLint  holes, hole_size, hole_count;         /* holes in the ground (basements): not drawn there */
 } SceneShader;
 
diff --git a/entropie/src/render/shapes.h b/entropie/src/render/shapes.h
index dadc590..8539427 100644
--- a/entropie/src/render/shapes.h
+++ b/entropie/src/render/shapes.h
@@ -64,7 +64,7 @@ typedef struct {
     float door_x;
 } BuildingSize;
 
-typedef enum { BOTTLE_BEER, BOTTLE_SPIRITS, BOTTLE_WATER } BottleStyle;
+typedef enum { BOTTLE_BEER, BOTTLE_SPIRITS, BOTTLE_WATER, BOTTLE_COLA, BOTTLE_ORANGE, BOTTLE_MATE } BottleStyle;
 
 /* Bottle centered on the origin: glass, label, neck and cap.
    Brown beer bottle, clear with a red cap for spirits, light blue for water. */
@@ -133,7 +133,9 @@ Mesh  Shapes_ivy(float width, float height, unsigned seed);
 Mesh  Shapes_car(Vec3 color, bool boot_open);
 /* ... seen from the driver's seat: the bonnet, the dashboard, the wheel, the pillars, the roof. */
 Mesh  Shapes_carInside(Vec3 color);
-/* A crate of bottles (centred): variant 0 beer, 1 water; this many bottles in it. */
+/* Rain: a thin light streak at each drop (world space), len long. */
+Mesh  Shapes_rain(const Vec3 *drops, int n, float len);
+/* A crate of bottles (centred): variant 0 beer, 1 water, 2 soda (cola and orange), 3 mate; this many bottles in it. */
 #define CRATE_SIZE_X 0.40f
 #define CRATE_SIZE_Y 0.30f
 #define CRATE_SIZE_Z 0.30f
diff --git a/entropie/src/render/shapes_props.c b/entropie/src/render/shapes_props.c
index 0ba242c..086ea63 100644
--- a/entropie/src/render/shapes_props.c
+++ b/entropie/src/render/shapes_props.c
@@ -27,13 +27,16 @@ Mesh Shapes_bottle(float diameter, float height, BottleStyle style) {
     const float base = -height * 0.5f;
     const float body_h = height * (spirits ? 0.66f : 0.58f), shoulder_h = height * (spirits ? 0.08f : 0.14f);
     const float neck_h = height * (spirits ? 0.20f : 0.24f);
-    const Vec3 glass = water ? COLOR_WATER : (spirits ? COLOR_CLEAR : COLOR_GLASS);
-    const Vec3 cap = water ? COLOR_BLUE : (spirits ? COLOR_RED : COLOR_CAP);
+    Vec3 glass = water ? COLOR_WATER : (spirits ? COLOR_CLEAR : COLOR_GLASS);
+    Vec3 cap = water ? COLOR_BLUE : (spirits ? COLOR_RED : COLOR_CAP), label = COLOR_LABEL;
+    if (style == BOTTLE_COLA)   { glass = (Vec3){ 0.20f, 0.11f, 0.07f }; cap = COLOR_RED;  label = (Vec3){ 0.78f, 0.12f, 0.10f }; }
+    if (style == BOTTLE_ORANGE) { glass = (Vec3){ 0.96f, 0.56f, 0.14f }; cap = COLOR_ORANGE; label = (Vec3){ 0.20f, 0.36f, 0.72f }; }
+    if (style == BOTTLE_MATE)   { glass = (Vec3){ 0.55f, 0.33f, 0.10f }; cap = COLOR_BLUE; label = (Vec3){ 0.93f, 0.84f, 0.50f }; }
 
     MeshBuilder b;
     MeshBuilder_init(&b);
     shapes_addTube(&b, base, r, r, body_h * 0.25f, seg, glass, true);                         /* bottom */
-    shapes_addTube(&b, base + body_h * 0.25f, r * 1.02f, r * 1.02f, body_h * 0.5f, seg, COLOR_LABEL, false);
+    shapes_addTube(&b, base + body_h * 0.25f, r * 1.02f, r * 1.02f, body_h * 0.5f, seg, label, false);
     shapes_addTube(&b, base + body_h * 0.75f, r, r, body_h * 0.25f, seg, glass, false);
     shapes_addTube(&b, base + body_h, r, neck, shoulder_h, seg, glass, false);                 /* shoulder */
     shapes_addTube(&b, base + body_h + shoulder_h, neck, neck, neck_h, seg, glass, false);
@@ -951,15 +954,40 @@ Mesh Shapes_carInside(Vec3 color) {
     return Mesh_fromBuilder(&b);
 }
 
+Mesh Shapes_rain(const Vec3 *drops, int n, float len) {
+    MeshBuilder b;
+    MeshBuilder_init(&b);
+    const Vec3 color = { 0.78f, 0.82f, 0.88f };
+    for (int k = 0; k < n; k++) {
+        Vec3 p = drops[k], d = vec3(0.07f * len, len, 0.0f); /* (leaning a little with the wind) */
+        Vec3 side = vec3(0.0f, 0.0f, 0.014f), across = vec3(0.014f, 0.0f, 0.0f);
+        GLuint i0 = MeshBuilder_vertex(&b, vec3_sub(p, side), vec3(0, 0, 1), color);
+        GLuint i1 = MeshBuilder_vertex(&b, vec3_add(p, side), vec3(0, 0, 1), color);
+        GLuint i2 = MeshBuilder_vertex(&b, vec3_add(vec3_add(p, d), side), vec3(0, 0, 1), color);
+        GLuint i3 = MeshBuilder_vertex(&b, vec3_sub(vec3_add(p, d), side), vec3(0, 0, 1), color);
+        MeshBuilder_quad(&b, i0, i1, i2, i3);
+        MeshBuilder_quad(&b, i3, i2, i1, i0); /* (seen from both sides) */
+        GLuint j0 = MeshBuilder_vertex(&b, vec3_sub(p, across), vec3(1, 0, 0), color);
+        GLuint j1 = MeshBuilder_vertex(&b, vec3_add(p, across), vec3(1, 0, 0), color);
+        GLuint j2 = MeshBuilder_vertex(&b, vec3_add(vec3_add(p, d), across), vec3(1, 0, 0), color);
+        GLuint j3 = MeshBuilder_vertex(&b, vec3_sub(vec3_add(p, d), across), vec3(1, 0, 0), color);
+        MeshBuilder_quad(&b, j0, j1, j2, j3);
+        MeshBuilder_quad(&b, j3, j2, j1, j0);
+    }
+    return Mesh_fromBuilder(&b);
+}
+
 /* A crate of bottles, centred: beer (brown bottles, gold caps, a red crate)
    or water (green glass, blue caps, a blue crate); the first `bottles` slots full. */
 Mesh Shapes_crate(int variant, int bottles) {
     MeshBuilder b;
     MeshBuilder_init(&b);
     const float x = CRATE_SIZE_X, h = CRATE_SIZE_Y, z = CRATE_SIZE_Z, wall = 0.015f;
-    const Vec3 crate = variant == 0 ? (Vec3){ 0.62f, 0.16f, 0.12f } : (Vec3){ 0.22f, 0.42f, 0.72f };
-    const Vec3 glass = variant == 0 ? (Vec3){ 0.38f, 0.22f, 0.08f } : (Vec3){ 0.55f, 0.75f, 0.62f };
-    const Vec3 cap   = variant == 0 ? (Vec3){ 0.85f, 0.70f, 0.25f } : (Vec3){ 0.20f, 0.35f, 0.75f };
+    static const Vec3 CRATE[4] = { { 0.62f, 0.16f, 0.12f }, { 0.22f, 0.42f, 0.72f }, { 0.20f, 0.48f, 0.28f }, { 0.90f, 0.62f, 0.15f } };
+    static const Vec3 GLASS[4] = { { 0.38f, 0.22f, 0.08f }, { 0.55f, 0.75f, 0.62f }, { 0.20f, 0.11f, 0.07f }, { 0.55f, 0.33f, 0.10f } };
+    static const Vec3 CAP[4]   = { { 0.85f, 0.70f, 0.25f }, { 0.20f, 0.35f, 0.75f }, { 0.78f, 0.12f, 0.10f }, { 0.20f, 0.35f, 0.75f } };
+    variant = variant < 0 ? 0 : variant > 3 ? 3 : variant;
+    const Vec3 crate = CRATE[variant];
     const Vec3 hole  = vec3_scale(crate, 0.45f);
     addBlock(&b, vec3(0.0f, -h * 0.5f + 0.01f, 0.0f), vec3(x, 0.02f, z), crate);
     addBlock(&b, vec3(0.0f, 0.0f, z * 0.5f - wall * 0.5f), vec3(x, h, wall), crate);
@@ -968,8 +996,11 @@ Mesh Shapes_crate(int variant, int bottles) {
     addBlock(&b, vec3(-x * 0.5f + wall * 0.5f, 0.0f, 0.0f), vec3(wall, h, z), crate);
     for (int s = -1; s <= 1; s += 2) /* the handle holes in the ends */
         addBlock(&b, vec3((float)s * (x * 0.5f + 0.001f), h * 0.32f, 0.0f), vec3(0.004f, 0.035f, 0.11f), hole);
-    const int cols = variant == 0 ? 5 : 4, rows = variant == 0 ? 4 : 3;
+    const bool big = variant == 1 || variant == 2; /* (water and soda: 12 big bottles; beer and mate: 20 small) */
+    const int cols = big ? 4 : 5, rows = big ? 3 : 4;
     for (int k = 0; k < bottles && k < cols * rows; k++) {
+        Vec3 glass = GLASS[variant], cap = CAP[variant];
+        if (variant == 2 && k % 2) { glass = (Vec3){ 0.96f, 0.56f, 0.14f }; cap = (Vec3){ 0.95f, 0.50f, 0.15f }; } /* (soda: cola and orange) */
         float cx = -x * 0.5f + x * ((float)(k % cols) + 0.5f) / (float)cols, cz = -z * 0.5f + z * ((float)(k / cols) + 0.5f) / (float)rows;
         size_t first = b.vertex_count;
         shapes_addTube(&b, h * 0.5f - 0.07f, 0.024f, 0.012f, 0.1f, 6, glass, false); /* the neck out of the top */
diff --git a/entropie/src/world/car.c b/entropie/src/world/car.c
index 07a1e2a..4082104 100644
--- a/entropie/src/world/car.c
+++ b/entropie/src/world/car.c
@@ -44,8 +44,19 @@ bool Car_load(Object *car, CargoKind kind, Object *owner) {
 static const char *const CARGO_NAME[] = {
     [CARGO_BEER] = "a crate of beer", [CARGO_WATER] = "a crate of water", [CARGO_SLEEPING_BAG] = "a sleeping bag",
     [CARGO_MAT] = "a sleeping mat", [CARGO_TENT_SMALL] = "a tent", [CARGO_TENT_BIG] = "the big tent",
+    [CARGO_SODA] = "a crate of soda", [CARGO_MATE] = "a crate of mate",
 };
 
+int Crate_bottles(int variant) { return variant == 1 || variant == 2 ? 12 : 20; }
+float Crate_bottleKg(int variant) { return variant == 1 || variant == 2 ? 1.2f : 0.85f; }
+const char *Crate_name(int variant) {
+    static const char *const NAME[] = { "beer", "water", "soda", "mate" };
+    return NAME[variant & 3];
+}
+
+static bool isCrate(CargoKind k) { return k == CARGO_BEER || k == CARGO_WATER || k == CARGO_SODA || k == CARGO_MATE; }
+static int crateOf(CargoKind k) { return k == CARGO_WATER ? 1 : k == CARGO_SODA ? 2 : k == CARGO_MATE ? 3 : 0; }
+
 const char *Car_nextName(const Object *car) {
     const CarLoad *l = car->load;
     return l && l->n > 0 ? CARGO_NAME[l->kind[l->n - 1]] : NULL;
@@ -53,7 +64,7 @@ const char *Car_nextName(const Object *car) {
 
 bool Car_nextHeavy(const Object *car) {
     const CarLoad *l = car->load;
-    return l && l->n > 0 && (l->kind[l->n - 1] == CARGO_BEER || l->kind[l->n - 1] == CARGO_WATER || l->kind[l->n - 1] == CARGO_TENT_BIG);
+    return l && l->n > 0 && (isCrate(l->kind[l->n - 1]) || l->kind[l->n - 1] == CARGO_TENT_BIG);
 }
 
 /* Everybody's own colours for their things (by name: you, Volkram, Jana, Toni, anybody else). */
@@ -85,8 +96,8 @@ Object *Car_takeOut(World *world, Object *car) {
     Vec3 at = local(car, vec3(0.0f, 0.0f, -CAR_LENGTH * 0.5f - SET_DOWN));
     at.y = World_groundHeight(world, at.x, at.z, at.y + 1.0f) + 0.02f;
     Object *item;
-    if (kind == CARGO_BEER || kind == CARGO_WATER) {
-        item = Object_create(OBJ_CRATE, PALETTE_STANDARD, at, vec3_zero(), kind == CARGO_WATER ? 1.0f : 0.0f);
+    if (isCrate(kind)) {
+        item = Object_create(OBJ_CRATE, PALETTE_STANDARD, at, vec3_zero(), (float)crateOf(kind));
     } else {
         GearKind g = kind == CARGO_SLEEPING_BAG ? GEAR_SLEEPING_BAG : kind == CARGO_MAT ? GEAR_MAT
                    : kind == CARGO_TENT_SMALL ? GEAR_TENT_SMALL : GEAR_TENT_BIG;
@@ -205,9 +216,11 @@ Object *Crate_takeBottle(World *world, Object *crate) {
     if (crate->type != OBJ_CRATE || crate->stored <= 0) return NULL;
     Vec3 top = vec3_add(Object_colliderCenter(crate, 0), vec3(0.0f, CRATE_SIZE_Y * 0.5f + 0.16f, 0.0f));
     Object *b = Object_create(crate->variant ? OBJ_WATER : OBJ_BOTTLE, PALETTE_WARM, top, CRATE_BOTTLE, BOTTLE_KG);
-    if (!b || !World_add(world, b)) return NULL;
+    if (!b) return NULL;
+    if (crate->variant >= 2) Object_setDrink(b, crate->variant == 3 ? 3 : 1 + ((crate->stored + 1) % 2)); /* (soda: cola and orange in turn) */
+    if (!World_add(world, b)) return NULL;
     crate->stored--;
-    crate->weight = fmaxf(2.0f, crate->weight - (crate->variant ? 1.2f : 0.85f));
+    crate->weight = fmaxf(2.0f, crate->weight - Crate_bottleKg(crate->variant));
     return b;
 }
 
diff --git a/entropie/src/world/car.h b/entropie/src/world/car.h
index 83a6ac6..c106c79 100644
--- a/entropie/src/world/car.h
+++ b/entropie/src/world/car.h
@@ -12,11 +12,14 @@
 
 #include "world/world.h"
 
-#define CRATE_BEER  20  /* bottles in a crate of beer */
-#define CRATE_WATER 12  /* ... of water */
 
 typedef enum { GEAR_SLEEPING_BAG, GEAR_MAT, GEAR_TENT_SMALL, GEAR_TENT_BIG } GearKind;
-typedef enum { CARGO_BEER, CARGO_WATER, CARGO_SLEEPING_BAG, CARGO_MAT, CARGO_TENT_SMALL, CARGO_TENT_BIG } CargoKind;
+typedef enum { CARGO_BEER, CARGO_WATER, CARGO_SLEEPING_BAG, CARGO_MAT, CARGO_TENT_SMALL, CARGO_TENT_BIG, CARGO_SODA, CARGO_MATE } CargoKind;
+
+/* Crates: 0 beer, 1 water, 2 soda (cola and orange), 3 mate - how many bottles, how heavy each, its name. */
+int         Crate_bottles(int variant);
+float       Crate_bottleKg(int variant);
+const char *Crate_name(int variant);
 
 #define CAR_LOAD_MAX 20
 typedef struct CarLoad {
diff --git a/entropie/src/world/object.c b/entropie/src/world/object.c
index f87de98..f40f8f2 100644
--- a/entropie/src/world/object.c
+++ b/entropie/src/world/object.c
@@ -363,10 +363,10 @@ Object *Object_create(ObjectType type, Palette palette, Vec3 position, Vec3 size
     }
 
     case OBJ_CRATE:
-        obj->variant = weight > 0.5f ? 1 : 0;
-        obj->stored  = obj->variant ? CRATE_WATER : CRATE_BEER;
+        obj->variant = (int)(weight + 0.5f) < 0 ? 0 : (int)(weight + 0.5f) > 3 ? 3 : (int)(weight + 0.5f);
+        obj->stored  = Crate_bottles(obj->variant);
         obj->size    = vec3(CRATE_SIZE_X, CRATE_SIZE_Y, CRATE_SIZE_Z);
-        obj->weight  = 2.0f + (float)obj->stored * (obj->variant ? 1.2f : 0.85f);
+        obj->weight  = 2.0f + (float)obj->stored * Crate_bottleKg(obj->variant);
         obj->mesh    = Shapes_crate(obj->variant, obj->stored);
         obj->shown   = obj->stored;
         obj->pivot   = vec3(0.0f, CRATE_SIZE_Y * 0.5f, 0.0f);
@@ -1190,8 +1190,22 @@ float Object_bottom(const Object *obj) {
     return Object_colliderCenter(obj, 0).y - extent;
 }
 
-static const char *bottleName(ObjectType type) {
-    return type == OBJ_VODKA ? "Vodka" : type == OBJ_WATER ? "Water" : "Beer";
+static const char *bottleName(const Object *b) {
+    static const char *const DRINK[] = { "Water", "Cola", "Orange soda", "Mate" };
+    return b->type == OBJ_VODKA ? "Vodka" : b->type == OBJ_WATER ? DRINK[b->variant & 3] : "Beer";
+}
+
+void Object_setDrink(Object *b, int drink) {
+    static const BottleStyle STYLE[] = { BOTTLE_WATER, BOTTLE_COLA, BOTTLE_ORANGE, BOTTLE_MATE };
+    if (b->type != OBJ_WATER) return;
+    drink &= 3;
+    b->variant = drink;
+    Mesh_destroy(&b->mesh);
+    b->mesh = Shapes_bottle(2.0f * b->colliders[0].half_extents.x, 2.0f * b->colliders[0].half_extents.y, STYLE[drink]);
+    b->quench  = drink == 0 ? 0.2f : 0.15f;
+    b->refresh = drink == 0 ? 0.15f : drink == 3 ? 0.12f : 0.1f;  /* (sugar, or the mate's kick) */
+    b->nourish = drink == 1 || drink == 2 ? 0.02f : 0.0f;
+    b->wake    = drink == 3 ? 0.04f : drink == 1 ? 0.01f : 0.0f;   /* (caffeine: less sleepy) */
 }
 
 bool Object_isHard(const Object *o) {
@@ -1211,8 +1225,8 @@ void Object_describe(const Object *obj, char *out, size_t size) {
     case OBJ_BOTTLE:
     case OBJ_VODKA:
     case OBJ_WATER:
-        if (obj->sips > 0) snprintf(out, size, "%s - %d sip%s left", bottleName(obj->type), obj->sips, obj->sips == 1 ? "" : "s");
-        else snprintf(out, size, "%s - empty", bottleName(obj->type));
+        if (obj->sips > 0) snprintf(out, size, "%s - %d sip%s left", bottleName(obj), obj->sips, obj->sips == 1 ? "" : "s");
+        else snprintf(out, size, "%s - empty", bottleName(obj));
         break;
     case OBJ_APPLE:
     case OBJ_BREAD:
@@ -1276,7 +1290,7 @@ void Object_describe(const Object *obj, char *out, size_t size) {
         else snprintf(out, size, "Your car - left click at the boot: take out %s", Car_nextName(obj));
         break;
     case OBJ_CRATE:
-        snprintf(out, size, "Crate of %s - %d bottle%s left%s", obj->variant ? "water" : "beer", obj->stored, obj->stored == 1 ? "" : "s",
+        snprintf(out, size, "Crate of %s - %d bottle%s left%s", Crate_name(obj->variant), obj->stored, obj->stored == 1 ? "" : "s",
                  obj->stored > 0 && !obj->is_held ? " (left click: take one)" : "");
         break;
     case OBJ_TENT: {
diff --git a/entropie/src/world/object.h b/entropie/src/world/object.h
index 4c93460..6da5790 100644
--- a/entropie/src/world/object.h
+++ b/entropie/src/world/object.h
@@ -162,6 +162,8 @@ typedef struct Object {
     bool       boot_open;     /* cars: the boot lid up */
     Mesh       inside;        /* cars: what you see of it from the driver's seat */
     bool       keep;          /* trees: may not be cut down (round the meadow behind your garages) */
+    float      wake;          /* drinks: sleepiness off per sip (mate, cola) */
+    bool       rain_out;      /* characters: out in the rain (no roof over them) */
     bool       lives_here;    /* characters: the house is theirs (Kurti and his sons): in and out of it */
     float      indoors;       /* characters: inside the house this many more seconds (hidden: see ai.c goInside) */
     struct Object *owner;     /* camping gear, tents: whose ... */
@@ -509,6 +511,8 @@ void    Object_describe(const Object *obj, char *out, size_t size);
 
 /* Advances a fall started by Object_topple. */
 void    Object_advanceTopple(Object *obj, float dt);
+/* A bottle of water (OBJ_WATER) made a soft drink: 0 water, 1 cola, 2 orange soda, 3 mate. */
+void    Object_setDrink(Object *b, int drink);
 /* Gone (eaten): no mesh, no collision. */
 void    Object_useUp(Object *obj);
 /* Bottles: turns into shards lying on the ground at ground_y. */
diff --git a/entropie/src/world/weather.c b/entropie/src/world/weather.c
new file mode 100644
index 0000000..a546f6c
--- /dev/null
+++ b/entropie/src/world/weather.c
@@ -0,0 +1,114 @@
+#include "world/weather.h"
+#include "world/wetness.h"
+#include "render/shapes.h"
+
+#include <stdlib.h>
+
+#define FIRST_DRY     (3.0f * 3600.0f)  /* game s: dry at first (you arrive, the tents go up) */
+#define DRY_MIN       (2.0f * 3600.0f)  /* a dry spell: this long ... */
+#define DRY_MORE      (6.0f * 3600.0f)  /* ... up to this much longer */
+#define SHOWER_MIN    (12.0f * 60.0f)   /* a shower: this long ... */
+#define SHOWER_MORE   (40.0f * 60.0f)
+#define RAIN_EASE     150.0f            /* game s: it sets in / eases off over about this */
+#define SOAK_TIME     (25.0f * 60.0f)   /* game s of hard rain: everything soaked */
+#define DRY_TIME      (3.0f * 3600.0f)  /* ... and dry again (in the day; slower at night) */
+#define RAIN_MM_HOUR  7.0f              /* mm an hour in hard rain */
+#define POUR_RADIUS   16.0f             /* m round you the ground gets the water (puddles, mud) */
+#define POURS         8                 /* ... in this many splashes a frame */
+#define DROP_RADIUS   9.0f              /* m round the camera the drops fall */
+#define DROP_SPEED    8.5f
+#define DROP_LEN      0.6f
+#define FIRE_DOUSE    0.004f            /* kg of a fire's wood a game second, in hard rain */
+
+static float dice(unsigned *rng) {
+    *rng = *rng * 1103515245u + 12345u;
+    return (float)((*rng >> 8) & 0xFFFFu) / 65536.0f;
+}
+
+void Weather_init(World *world) {
+    world->rain = world->rain_goal = 0.0f;
+    world->rain_left = FIRST_DRY;
+    world->wet = 0.0f;
+    world->weather = WEATHER_AUTO;
+    world->drops = calloc(1, sizeof(*world->drops));
+    if (world->drops) world->drops->rng = 0x5eed1234u;
+}
+
+const char *Weather_name(WeatherKind kind) {
+    static const char *const NAME[WEATHER_KINDS] = { "on its own", "dry", "a shower", "pouring" };
+    return NAME[(unsigned)kind < WEATHER_KINDS ? kind : 0];
+}
+
+bool Weather_covered(const World *world, Vec3 p) {
+    return World_rayCast(world, vec3_add(p, vec3(0.0f, 0.3f, 0.0f)), vec3(0.0f, 1.0f, 0.0f), 14.0f, NULL, NULL, NULL, NULL) >= 0.0f;
+}
+
+static void respawn(World *world, int k, bool anywhere) {
+    RainDrops *d = world->drops;
+    float a = dice(&d->rng) * 2.0f * PI_F, r = DROP_RADIUS * sqrtf(dice(&d->rng));
+    Vec3 p = vec3_add(world->eye, vec3(cosf(a) * r, 0.0f, sinf(a) * r));
+    float ground = world->has_terrain ? Terrain_height(&world->terrain, p.x, p.z) : 0.0f;
+    p.y = anywhere ? ground + 7.0f * dice(&d->rng) : fmaxf(world->eye.y, ground) + 4.0f + 3.0f * dice(&d->rng);
+    d->hidden[k] = Weather_covered(world, vec3(p.x, fminf(world->eye.y, ground + 1.0f), p.z));
+    d->p[k] = p;
+}
+
+void Weather_update(World *world, float dt, float game_dt) {
+    /* where it's going: forced (dev), or showers and dry spells taking turns */
+    if (world->weather == WEATHER_DRY) world->rain_goal = 0.0f;
+    else if (world->weather == WEATHER_SHOWER) world->rain_goal = 0.4f;
+    else if (world->weather == WEATHER_RAIN) world->rain_goal = 1.0f;
+    else if ((world->rain_left -= game_dt) <= 0.0f) {
+        unsigned *rng = world->drops ? &world->drops->rng : &world->rng;
+        if (world->rain_goal > 0.0f) {
+            world->rain_goal = 0.0f;
+            world->rain_left = DRY_MIN + DRY_MORE * dice(rng);
+        } else {
+            world->rain_goal = 0.25f + 0.75f * dice(rng);
+            world->rain_left = SHOWER_MIN + SHOWER_MORE * dice(rng);
+        }
+    }
+    world->rain += (world->rain_goal - world->rain) * fminf(1.0f, game_dt / RAIN_EASE);
+    if (world->rain < 0.002f && world->rain_goal <= 0.0f) world->rain = 0.0f;
+    bool day = world->hour >= 7.0f && world->hour < 20.0f;
+    world->wet = clampf(world->wet + world->rain * game_dt / SOAK_TIME - (1.0f - world->rain) * game_dt / (DRY_TIME * (day ? 1.0f : 3.0f)), 0.0f, 1.0f);
+    if (world->rain <= 0.0f || !world->drops) return;
+
+    /* the ground near you gets the water: puddles, mud (not under a roof) */
+    RainDrops *d = world->drops;
+    float liters = RAIN_MM_HOUR * world->rain / 3600.0f * game_dt * PI_F * POUR_RADIUS * POUR_RADIUS / (float)POURS;
+    for (int k = 0; k < POURS && world->has_terrain; k++) {
+        float a = dice(&d->rng) * 2.0f * PI_F, r = POUR_RADIUS * sqrtf(dice(&d->rng));
+        Vec3 p = vec3_add(world->eye, vec3(cosf(a) * r, 0.0f, sinf(a) * r));
+        p.y = Terrain_height(&world->terrain, p.x, p.z);
+        if (!Weather_covered(world, p)) Wetness_pour(&world->terrain, p.x, p.z, liters, 2.2f, 0.0f);
+    }
+    /* a fire burns down faster in it */
+    for (int i = 0; i < world->count; i++) {
+        Object *f = world->objects[i];
+        if (f->type == OBJ_FIREPLACE && f->burning) f->fuel = fmaxf(0.0f, f->fuel - FIRE_DOUSE * world->rain * game_dt);
+    }
+    /* the drops round the camera */
+    int n = (int)(RAIN_DROPS * world->rain);
+    for (int k = 0; k < n; k++) {
+        Vec3 *p = &d->p[k];
+        float dx = p->x - world->eye.x, dz = p->z - world->eye.z;
+        if (p->y == 0.0f && p->x == 0.0f) { respawn(world, k, true); continue; }
+        p->y -= DROP_SPEED * dt;
+        p->x += 0.6f * dt; /* (a little wind) */
+        float ground = world->has_terrain ? Terrain_height(&world->terrain, p->x, p->z) : 0.0f;
+        if (p->y < ground || dx * dx + dz * dz > DROP_RADIUS * DROP_RADIUS * 1.3f) respawn(world, k, p->y >= ground);
+    }
+}
+
+void Weather_draw(World *world, Renderer *renderer) {
+    if (world->rain <= 0.01f || !world->drops) return;
+    Vec3 shown[RAIN_DROPS];
+    int n = 0, live = (int)(RAIN_DROPS * world->rain);
+    for (int k = 0; k < live; k++)
+        if (!world->drops->hidden[k]) shown[n++] = world->drops->p[k];
+    if (n == 0) return;
+    Mesh mesh = Shapes_rain(shown, n, DROP_LEN);
+    Renderer_drawSmoke(renderer, &mesh, mat4_identity(), vec3(1.0f, 1.0f, 1.0f));
+    Mesh_destroy(&mesh);
+}
diff --git a/entropie/src/world/weather.h b/entropie/src/world/weather.h
new file mode 100644
index 0000000..a29601c
--- /dev/null
+++ b/entropie/src/world/weather.h
@@ -0,0 +1,32 @@
+#ifndef WORLD_WEATHER_H
+#define WORLD_WEATHER_H
+
+/*
+ * Weather: dry spells and showers (on their own, or set in the dev menu).
+ * While it rains, drops fall round the camera (none under a roof), the
+ * ground near you gets wet (puddles, mud: see wetness.c), everything looks
+ * darker and wetter, the sky greys over, a fire burns down faster. People go
+ * in out of it (ai.c shelter).
+ */
+
+#include "world/world.h"
+
+typedef enum { WEATHER_AUTO, WEATHER_DRY, WEATHER_SHOWER, WEATHER_RAIN, WEATHER_KINDS } WeatherKind;
+
+#define RAIN_DROPS 1400
+
+typedef struct RainDrops {
+    Vec3 p[RAIN_DROPS];
+    bool hidden[RAIN_DROPS]; /* (under a roof: not drawn) */
+    unsigned rng;
+} RainDrops;
+
+void        Weather_init(World *world);
+/* dt real, game_dt game seconds. */
+void        Weather_update(World *world, float dt, float game_dt);
+void        Weather_draw(World *world, Renderer *renderer);
+/* Is there a roof (anything) over this spot? */
+bool        Weather_covered(const World *world, Vec3 p);
+const char *Weather_name(WeatherKind kind);
+
+#endif /* WORLD_WEATHER_H */
diff --git a/entropie/src/world/world.c b/entropie/src/world/world.c
index 903b2aa..9e9bee6 100644
--- a/entropie/src/world/world.c
+++ b/entropie/src/world/world.c
@@ -3,6 +3,7 @@
 #include "world/clothes.h"
 #include "world/car.h"
 #include "world/tent.h"
+#include "world/weather.h"
 #include "people/pockets.h"
 #include "people/condition.h"
 #include "people/dog.h"
@@ -734,6 +735,10 @@ static void dressForNight(World *world, Object *o, float 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);
+    if (world->rain > 0.2f && !o->in_tent && o->indoors <= 0.0f && !o->riding && ((int)(world->hour * 3600.0f) + (int)(o->position.x * 7.0f)) % 30 == 0)
+        o->rain_out = !Weather_covered(world, vec3_add(o->position, vec3(0.0f, o->look.height, 0.0f))); /* (out in it: now and then looked up) */
+    if (world->rain <= 0.2f || o->in_tent || o->indoors > 0.0f) o->rain_out = false;
+    if (o->rain_out) cold = fmaxf(cold, 0.0f) + 1.2f * world->rain; /* (wet through: cold, even in the day) */
     o->chill = clampf(o->chill + cold * dt / 60.0f, 0.0f, 1.0f); /* (about a minute to feel it) */
     bool player = o->type == OBJ_PLAYER;
     if (o->look.top != TOP_JACKET && o->chill > 0.6f && !(player && o->jacket_told)) {
@@ -1136,6 +1141,7 @@ void World_init(World *world) {
     memset(world, 0, sizeof(*world));
     world->rng = 0x2545F491u;
     world->hour = 12.0f;
+    Weather_init(world);
 }
 
 void World_destroy(World *world) {
@@ -1148,6 +1154,7 @@ void World_destroy(World *world) {
     Grid_free(&world->grid);
     Mesh_destroy(&world->hose_piece);
     Mesh_destroy(&world->hose_tape);
+    free(world->drops);
     memset(world, 0, sizeof(*world));
 }
 
@@ -1232,6 +1239,7 @@ void World_update(World *world, float dt, float game_dt) {
     Object_indexKinds(world->objects, world->count);
     world->dt_props = dt;
     Clothes_update(world);
+    Weather_update(world, dt, game_dt);
     Car_serve(world);
     Tent_update(world, dt);
     showProps(world); /* (who's a person, where the fires are: looked up again and again) */
@@ -1251,12 +1259,13 @@ void World_update(World *world, float dt, float game_dt) {
         Condition_useGrid(&world->grid);
         Condition_update(o, world->objects, world->count, dt, game_dt);
         if (o->indoors > 0.0f) { /* in the house: out of sight a while, then out at the door */
+            if (o->ai.sheltering && world->rain > 0.1f) o->indoors = fmaxf(o->indoors, 25.0f + 10.0f * (float)(i % 4)); /* (till the rain's over) */
             o->indoors -= dt;
             Vec3 door = Site_houseDoor();
             o->position = vec3(door.x, Terrain_height(&world->terrain, door.x, door.z), door.z);
             o->velocity = o->movement = vec3_zero();
             Object_syncColliders(o);
-            if (o->indoors <= 0.0f) { o->indoors = 0.0f; o->body.replant = true; }
+            if (o->indoors <= 0.0f) { o->indoors = 0.0f; o->body.replant = true; o->ai.sheltering = false; }
             continue;
         }
         if (o->type == OBJ_HUMAN && !o->riding) { Ai_useGrid(&world->grid); Ai_useWorld(world); Ai_update(o, world->objects, world->count, dt); }
@@ -1503,4 +1512,5 @@ void World_draw(World *world, Renderer *renderer, bool draw_player) {
         }
     }
     Chips_draw(&world->chips, renderer);
+    Weather_draw(world, renderer);
 }
diff --git a/entropie/src/world/world.h b/entropie/src/world/world.h
index 37ea5ef..deb62ca 100644
--- a/entropie/src/world/world.h
+++ b/entropie/src/world/world.h
@@ -28,6 +28,12 @@ typedef struct World {
     Chips    chips;        /* wood chips and sawdust flying off work */
     Grid     grid;         /* where things are, by ground cell (built each frame) */
     float    hour;         /* time of day (set by the game) */
+    float    rain;         /* 0..1 how hard it rains now (see weather.h) ... */
+    float    rain_goal, rain_left; /* ... where it's going, for how much longer (game s) */
+    int      weather;      /* WeatherKind: on its own, or set (dev menu) */
+    float    wet;          /* 0..1 everything wet from the rain (dries over hours) */
+    Vec3     eye;          /* where the camera is (set by the game: the rain falls round it) */
+    struct RainDrops *drops;
 } World;
 
 void World_init(World *world);