Nimbin[12]?Games / Entropie / commits / 87a199e

Entropie git · main

Entropie festival game; first steps

sdl c game entropie cmake · first commit 2026-10-10 · last commit 2026-11-02 (15 hours ago) · synced 6 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.48.0: no falling through, landings, the old tree, any tree

Christian Immanuel · 2026-11-02 17:09 · 87a199e5a79e054aa815b1330033874f728d5d6b

- Nobody goes down through a floor or a roof any more (the feet caught at
  a top: back up onto it; never pushed down out of something you stand on),
  and whoever drops into the void anyway is put back where they last stood
- Landings: always into the knees first; moving along and not too high
  (about 3.3 m, the very sporty 4 m) a roll; higher a crash. How hard it
  was decides: a twisted ankle (a few hours of limping) or a broken bone
- The big tree is old and half dead: gaps and dead brown layers in the
  crown, the top snapped off. A storm can bring it down; felled without the
  rope it goes the way it leans
- Any tree can be felled - Kurti's apple trees (300 EUR, Kurti furious) and
  the protected ones (a 1000 EUR fine) cost dearly; the hint warns first
 commit_message.txt               | 29 ++++++++++++-----------------
 entropie/Makefile                |  2 +-
 entropie/README.md               | 10 +++++++---
 entropie/STATE.md                | 40 +++++++++++++++++++++++++++++++++-------
 entropie/commit_message.txt      | 29 ++++++++++++-----------------
 entropie/src/game/game.h         |  1 +
 entropie/src/game/game_actions.c |  5 ++++-
 entropie/src/game/game_bigtree.c | 35 ++++++++++++++++++++++++++++++++++-
 entropie/src/game/game_injury.c  |  3 +++
 entropie/src/game/save.c         |  2 +-
 entropie/src/people/body.c       | 29 +++++++++++++++++++----------
 entropie/src/people/body.h       | 10 ++++++----
 entropie/src/render/shapes.c     | 40 ++++++++++++++++++++++++++++++++++++++--
 entropie/src/render/shapes.h     |  1 +
 entropie/src/world/forestry.c    |  8 ++++++++
 entropie/src/world/injury.c      | 27 +++++++++++++++++++++++++++
 entropie/src/world/injury.h      |  4 ++++
 entropie/src/world/object.c      |  4 ++--
 entropie/src/world/object.h      |  2 ++
 entropie/src/world/physics.c     | 30 ++++++++++++++++++++++++++++++
 entropie/src/world/tools.c       |  4 ++--
 entropie/src/world/world.c       | 33 +++++++++++++++++++++++++--------
 entropie/src/world/world.h       |  2 ++
 23 files changed, 274 insertions(+), 76 deletions(-)

diff --git a/commit_message.txt b/commit_message.txt
index c9bf6fa..df92fd3 100644
--- a/commit_message.txt
+++ b/commit_message.txt
@@ -1,18 +1,13 @@
-entropie 2.47.0: the big old tree, injuries
+entropie 2.48.0: no falling through, landings, the old tree, any tree
 
-- A big old tree between the Obstwiese and the Kartoffelacker leans towards
-  the road and the phone line along it (poles, a tall mast, wires)
-- Enrico from Go-Darmstadt (new, not at the Berlin meetings) fells it: the
-  throw line at its foot, the notch with the chainsaw, the crew on the rope,
-  the back cut
-- You throw the line up over a branch (T, looking up): the higher it
-  catches, the better it pulls; pull it down to try again; take hold of the
-  rope yourself
-- Not enough pull, a low rope, a gust: it comes down off to the side or
-  across the road through the phone line (1500 EUR, Kurti cross)
-- Broken arms and legs: under a falling tree, landing hard from high up.
-  The ambulance comes (lights, two-tone horn), the hospital, back days
-  later with a cast - weaker or slower for two weekends
-- The cast is drawn (white plaster on the forearm or shin)
-- No rain drawn or heard in Berlin
-- Dev menu: the big tree step by step
+- Nobody goes down through a floor or a roof any more (the feet caught at
+  a top: back up onto it; never pushed down out of something you stand on),
+  and whoever drops into the void anyway is put back where they last stood
+- Landings: always into the knees first; moving along and not too high
+  (about 3.3 m, the very sporty 4 m) a roll; higher a crash. How hard it
+  was decides: a twisted ankle (a few hours of limping) or a broken bone
+- The big tree is old and half dead: gaps and dead brown layers in the
+  crown, the top snapped off. A storm can bring it down; felled without the
+  rope it goes the way it leans
+- Any tree can be felled - Kurti's apple trees (300 EUR, Kurti furious) and
+  the protected ones (a 1000 EUR fine) cost dearly; the hint warns first
diff --git a/entropie/Makefile b/entropie/Makefile
index 6c4d1b1..3067d0d 100644
--- a/entropie/Makefile
+++ b/entropie/Makefile
@@ -1,5 +1,5 @@
 APP       := entropie
-VERSION   := 2.47.0
+VERSION   := 2.48.0
 
 PREFIX    ?= /usr
 BINDIR    ?= $(PREFIX)/bin
diff --git a/entropie/README.md b/entropie/README.md
index 297555a..e617417 100644
--- a/entropie/README.md
+++ b/entropie/README.md
@@ -43,8 +43,12 @@ people living there.
 - **The big old tree:** Enrico from Go-Darmstadt fells it with the
   chainsaw. You throw the rope up as high as you can, the others pull it
   away from the phone line. Not enough pull and it comes down on the line
-- **Injuries:** a falling tree or a hard fall can break an arm or a leg.
-  The ambulance comes; they're back days later with a cast, and slower
+- **Injuries:** a falling tree or a hard fall can twist an ankle or break
+  an arm or a leg. Landing, you bounce into the knees and roll if you're
+  moving; too high and something gives. Broken: the ambulance comes,
+  they're back days later with a cast, and slower
+- **Any tree can be felled** - the protected ones and Kurti's apple trees
+  cost dearly
 - **Money and shopping:** a cash box of 1200 EUR. Send Volkram to the
   hardware store for boards, nails, screws, paint and food. The stages need
   that material; the crew fetches it from the store
@@ -155,5 +159,5 @@ Dependencies point downward: `main → game → world/people → render → core
 
 ## Version
 
-`make version` prints e.g. `entropie 2.47.0 (build eafe545)`. The build id is
+`make version` prints e.g. `entropie 2.48.0 (build 4f0a82a)`. The build id is
 an md5 of all sources and assets.
diff --git a/entropie/STATE.md b/entropie/STATE.md
index fb52aa1..c92eb86 100644
--- a/entropie/STATE.md
+++ b/entropie/STATE.md
@@ -271,7 +271,7 @@ Ladder kicks (`physics.c` push / noteBump): a lying ladder is stepped over (only
 
 One-handed ladder (`game_actions.c` oneHandSlip): climbing with something up to 5 kg in the other hand. Slip risk a second while moving: 0.16 x (1.3 - fitness) x (0.6 + clumsy) x (1 + 3 drunk) x (1.5 - energy) x (1 - 0.9 practice); `ladder_skill` grows 0.01 a second of it. First slip a wobble (3 s), the next one a fall (on the bottom rung: you just slip off).
 
-Landing from high up (`world.c` landFromHigh, `body.c` Body_land): the fastest fall speed in the air (`air_fall`) at touchdown. In the air, falling over 2 m/s with the ground 0.5 m+ below: feet pulled up (knees to the belly), 0.12-0.28 s before touchdown the legs reach down. Landing: under 5 m/s nothing extra; 5-7 m/s into the knees (`land_squat`); 7-9.5 a roll (chance: sure = fitness, energy, sober, sure-footed) or a crash (Body_fall, -0.25 energy, dirt); over 9.5 always a crash ("my ankle"). The player now spawns 1 m up (was 4: that would be a crash).
+Landing from high up (`world.c` landFromHigh, `body.c` Body_land): the fastest fall speed in the air (`air_fall`) at touchdown. In the air, falling over 2 m/s with the ground 0.5 m+ below: feet pulled up (knees to the belly), 0.12-0.28 s before touchdown the legs reach down. Landing: under 5 m/s nothing extra; over it always into the knees first (`land_squat`); going along (1.2 m/s+) from 6.5 m/s up to 8 (+1 the very sporty) a roll for the sure; up to 8.2 the knees alone (the sure); else a crash (Body_fall, -0.25 energy, dirt). How bad it was: a twisted ankle or a broken bone (see Round 2.48). The player now spawns 1 m up (was 4: that would be a crash).
 
 Ladder: one click at a wall slides it out to fit (top on the edge, 0.9 m over it; up to its 6 m) and leans it; slid out by hand, it keeps that length. Putting it up is a 1.1 s animation (`ladder.c`, putUp): stood upright, slid out, tipped onto the wall; you stand still with both hands on it (`game.c`, puttingUp); it can only be climbed or taken once it leans.
 
@@ -406,14 +406,27 @@ Painted in the rotoscope style of *Waking Life* (2001):
   and through the phone line: the wires torn down, the phone company's bill
   (1500 EUR from the cash box), Kurti cross, everybody a bit down. Enrico
   jumps out of the way to the side it's not turning to
+- It's old and half dead (gaps in the crown, brown dead layers, the top
+  snapped off): it has to come down before it falls by itself. Left
+  standing, a storm (wind over 11 m/s) can bring it down - the way it leans
+- You can fell it yourself with the chainsaw - without the rope it goes the
+  way it leans (the road, the phone line)
 - Lying down it's a big tree to delimb and cut up like any other (big logs)
 - The Herberge hears how it went
 
+### Felling any tree
+
+- You can fell any tree. The ones that aren't to be felled cost: one of
+  Kurti's apple trees 300 EUR from the cash box (and Kurti is furious), a
+  protected tree (the ones round the camping meadow) a fine of 1000 EUR. With
+  an axe or the chainsaw at one, the hint says so first. The others never
+  fell those
+
 ### Injuries
 
 - A falling tree that knocks somebody down breaks an arm or a leg about one
   time in three (the big tree: nearly always). Landing hard from high up
-  (from about 4 m) can break a leg or an arm too
+  twists an ankle or breaks a leg or an arm (see Moving)
 - Hurt, they go down and stay down. The nearest of the crew calls 112; out
   here the ambulance takes about 20 minutes (game time). It comes in along
   the way in by car - white, a red band, blue lights, the two-tone horn -
@@ -836,10 +849,16 @@ and satellite pictures (about 340 x 340 m).
 - Walking at a block a bit higher than that (up to about the hip) with
   room on top: you step up onto it the same way, one leg after the other
 - Dropping from high up: the knees come up to the belly, the legs reach
-  down just before the ground. Over about 1.3 m you go deep into the knees;
-  over about 2.5 m the sporty roll over a shoulder the way they were going,
-  the others crash (energy, dirt). The very sporty (fresh, sober) roll it off
-  from 5 m and more without falling
+  down just before the ground. Landing, you always go into the knees first
+  and bounce it off (over about 1.3 m: deep). Going somewhere (moving along)
+  from about 2.5 m, the sure-footed roll over a shoulder the way they were
+  going - up to about 3.3 m, the very sporty about 4 m. Higher, or standing
+  still: deep in the knees (the sure, up to about 3.4 m) or a crash (energy,
+  dirt). How hard it was decides what gives: a twisted ankle (limping for
+  some hours: slower, hardly a jump) or, worse, a broken leg (a crash: now
+  and then an arm). Roughly: 3 m now and then a twisted ankle; 4 m always a
+  crash, often an ankle; 5 m half the time something broken; 7 m nearly
+  always
 - Everyone moves at their own size: taller people step higher and run a
   little faster; each has a stride of their own (long steps or quick ones)
 - Bottles, cigarettes and the like sit in the fist, the wrist kept straight
@@ -1621,6 +1640,12 @@ Round 2.47 (the big old tree, injuries):
 - Dev menu: "The big tree" (Right: Enrico here and at it, the rope up, the notch done). F2 projects page and the notebook: how far it is.
 - Checked (the harness): Enrico in, the throw line put down, your throw caught at 7.5 m, the notch, three called to the rope and you on it, the back cut, down 1 degree off; from a save before the pulling with a 4.5 m rope and you off the rope: 21 and 75 degrees off, and once (no pullers) right through the phone line (1500 EUR); Enrico hit by it once (a broken leg - the escape now goes away from the turn); the ambulance in and out, Toni at the hospital and back with a cast on the arm; the casts drawn.
 
+Round 2.48 (no falling through, landings, the old tree, any tree):
+- `physics.c`: nobody goes down through a floor or a roof any more - `neverThroughFloor` (the feet went into a fixed thing's top this step: back up onto it) and, for people, a push down out of something whose top is within SINK_MOST 0.45 m above their feet is a step up onto it instead. A safety net in `world.c`: a person below VOID_BELOW (-60 m) is put back where they last stood (`last_ground`, saved). Checked: 480 walks across the main building's roofs (old: 1 through, now 0), 464 walks and 280 sit-down-get-up-walk-offs in the Berlin rooms (none through; I couldn't make the Herberge one happen).
+- Landings (`Body_land(..., forward, ..., &bad)`): into the knees first always; a roll needs going along (ROLL_FORWARD 1.2 m/s), the sure, from LAND_DEEP_ - 0.5 up to LAND_ROLL_MOST 8 m/s (3.3 m) + LAND_SPORT 1 x sporty (4.1 m); the knees alone up to LAND_KNEES 8.2 (the sure, 3.4 m); else a crash. `bad` (0..1, LAND_BREAK 10.5 m/s the worst): `world.c` landFromHigh - hurt that likely; badly enough (bad + some luck over 0.8) a broken leg (a crash: now and then an arm), else a twisted ankle (`Injury_sprain`: SPRAIN_TIME 8 game h, speed x0.6, jump x0.5; `Object.sprain`, saved). Checked, 12 drops each: 2 m nothing; 3 m one sprain; 4 m all crash, 3-6 sprains; 5 m 4-5 broken; 7 m 9-11 broken.
+- The big tree: `TreeSize.old` (variant 2): every seventh layer gone, some dead brown and thinner, the top three missing, a bare snag with splinters, three dead branches. A storm (wind over STORM_WIND 11 m/s) brings it down the way it leans (STORM_FALLS 0.3 a game hour) while nobody's cutting it. Felled by you (no rope): away from you plus BIG_LEAN 1.4 x the lean. However it came down, the lines and the review follow (its angle off from the fall itself).
+- Any tree: `tools.c` lets you (only you) fell kept trees; `forestry.c` counts a fine (`World.tree_fine`: FINE_APPLE 300 for an apple tree, FINE_PROTECTED 1000); `game_bigtree.c` takes it from the cash box and says so (Kurti -0.3 for an apple tree). The felling hint warns first.
+
 ## Open tasks
 
 Most important first.
@@ -1638,6 +1663,7 @@ Most important first.
 - Crowds: lots of "Ouch! My shin" where many people mill about (the shin scrapes)
 - The big tree, still to do: the trunk cut up for the stages' frames (it's huge); the phone line mended (now it stays down); Enrico's geo-dome at the Kartoffelacker
 - Injuries, still to do: heavy things (logs, crates) falling on somebody; a limp with a leg cast; crutches; somebody sitting with the hurt till the ambulance comes
+- Next (from the latest notes): E / Q reach for whatever's in front and hold on to it (staying ready to grab till something's in reach); grabbing people by a body part (shoulder, hip, arm, hand) with what it means between the two (calm someone, show them a place, walk hand in hand, pull closer, push away); relationships (friendship, love - kept off screen); harassment and how the people round notice it, with a group awareness that rises and falls with the mood, kind acts and the place itself; drugs and their effects
 
 **Bugs**
 - Joint jumps left in the audit: jog+stop 171, sprint 297 (worst 26 cm) - running into things: a hurried catch step swings through in a few frames (step 0.07 -> 0.34, the ankle up 20 cm). Capping the swing speed helped sprint (278) but made jumps, drunk walk and the NPCs worse - tried and reverted in 1.83; ladder 45: toes on the sloping roof (a landing step's toes snap onto the slope, 16 cm; blending them in made all walking worse), toes turning onto the rungs getting on from the top (14 cm). Note (2.35): the audit is chaotic - each part starts from where the one before left you, so one small change anywhere reshuffles the later parts (a fall in the drunk walk or not, the ladder test not getting on the ladder); compare parts run on their own (the harness: LADDERONLY / JUMPSONLY / JOGONLY). Tried in 2.35: easing the running swing's ends (sprint worse), a swing not hurrying when stopping (no better on its own)
@@ -1670,4 +1696,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.47.0 (build eafe545)`
+State of `entropie 2.48.0 (build 4f0a82a)`
diff --git a/entropie/commit_message.txt b/entropie/commit_message.txt
index c9bf6fa..df92fd3 100644
--- a/entropie/commit_message.txt
+++ b/entropie/commit_message.txt
@@ -1,18 +1,13 @@
-entropie 2.47.0: the big old tree, injuries
+entropie 2.48.0: no falling through, landings, the old tree, any tree
 
-- A big old tree between the Obstwiese and the Kartoffelacker leans towards
-  the road and the phone line along it (poles, a tall mast, wires)
-- Enrico from Go-Darmstadt (new, not at the Berlin meetings) fells it: the
-  throw line at its foot, the notch with the chainsaw, the crew on the rope,
-  the back cut
-- You throw the line up over a branch (T, looking up): the higher it
-  catches, the better it pulls; pull it down to try again; take hold of the
-  rope yourself
-- Not enough pull, a low rope, a gust: it comes down off to the side or
-  across the road through the phone line (1500 EUR, Kurti cross)
-- Broken arms and legs: under a falling tree, landing hard from high up.
-  The ambulance comes (lights, two-tone horn), the hospital, back days
-  later with a cast - weaker or slower for two weekends
-- The cast is drawn (white plaster on the forearm or shin)
-- No rain drawn or heard in Berlin
-- Dev menu: the big tree step by step
+- Nobody goes down through a floor or a roof any more (the feet caught at
+  a top: back up onto it; never pushed down out of something you stand on),
+  and whoever drops into the void anyway is put back where they last stood
+- Landings: always into the knees first; moving along and not too high
+  (about 3.3 m, the very sporty 4 m) a roll; higher a crash. How hard it
+  was decides: a twisted ankle (a few hours of limping) or a broken bone
+- The big tree is old and half dead: gaps and dead brown layers in the
+  crown, the top snapped off. A storm can bring it down; felled without the
+  rope it goes the way it leans
+- Any tree can be felled - Kurti's apple trees (300 EUR, Kurti furious) and
+  the protected ones (a 1000 EUR fine) cost dearly; the hint warns first
diff --git a/entropie/src/game/game.h b/entropie/src/game/game.h
index 72b1cc9..4ba49a2 100644
--- a/entropie/src/game/game.h
+++ b/entropie/src/game/game.h
@@ -162,6 +162,7 @@ typedef struct {
     Festival festival;      /* the festival weekend (game_festival.c) */
     Ambulance ambulance;    /* somebody hurt: the ambulance (game_injury.c) ... */
     float    you_hurt_t;    /* ... you hurt: lying there this long */
+    bool     sprain_told;   /* ... your twisted ankle said */
     bool     on_rope;       /* you on the big tree's rope (game_bigtree.c) ... */
     struct Object *rope_look; /* ... the rope as it's seen (made again when it changes) ... */
     int      rope_shown;
diff --git a/entropie/src/game/game_actions.c b/entropie/src/game/game_actions.c
index 1cef801..9fede07 100644
--- a/entropie/src/game/game_actions.c
+++ b/entropie/src/game/game_actions.c
@@ -1136,7 +1136,10 @@ static bool fellingHint(Game *game, char *out, size_t size) {
     if (vec3_length(dir) < 1e-3f) return false;
     const Object *onto = Ai_fallsOnto(tree, dir);
     bool someone = Tools_fellDone(tree) > 0.6f && Ai_warnFellingRoom(p, game->world.objects, game->world.count, tree, dir);
-    if (someone) snprintf(out, size, "Timber! Somebody's where it'll come down - wait till they're out of the way");
+    if (tree->variant == 1) snprintf(out, size, "One of Kurti's apple trees - felling it costs 300 EUR, and Kurti won't forgive it");
+    else if (tree->variant == 2) snprintf(out, size, "No rope on it: it'll go the way it leans - across the road, onto the phone line");
+    else if (tree->keep) snprintf(out, size, "A protected tree - felling it costs a fine of 1000 EUR");
+    else if (someone) snprintf(out, size, "Timber! Somebody's where it'll come down - wait till they're out of the way");
     else if (onto) snprintf(out, size, "Careful: it would come down on the %s - fell it from another side", thingName(onto));
     else snprintf(out, size, "It falls away from you - clear there");
     return true;
diff --git a/entropie/src/game/game_bigtree.c b/entropie/src/game/game_bigtree.c
index c28d558..daf4346 100644
--- a/entropie/src/game/game_bigtree.c
+++ b/entropie/src/game/game_bigtree.c
@@ -22,6 +22,7 @@
 #include "world/cast.h"
 #include "world/forestry.h"
 #include "world/injury.h"
+#include "world/weather.h"
 
 #define LINE_BILL     1500.0f /* EUR: the phone company puts the line back up */
 #define PULL_EACH     0.35f   /* each on the rope (x how strong: fitness) ... */
@@ -32,6 +33,8 @@
 #define CALL_NEAR     160.0f  /* m: the crew this near get called to the rope */
 #define ON_ROPE       3.0f    /* m: you near the rope's end: on it (left click) */
 #define PLACE_NEAR    1.2f    /* m: a puller at their place counts */
+#define STORM_WIND    11.0f   /* m/s: wind like this can bring the old tree down ... */
+#define STORM_FALLS   0.3f    /* ... this likely a game hour */
 
 static Object *named(const Game *game, const char *name) {
     for (int i = 0; i < game->world.count; i++) {
@@ -217,7 +220,8 @@ static bool crosses(Vec3 a, Vec3 b, Vec3 c, Vec3 d) { /* (two stretches on the g
 static void down(Game *game, Object *tree) {
     World *w = &game->world;
     BigTree *bt = &w->bigtree;
-    Vec3 t = Site_bigTree(), tip = vec3_add(t, vec3_scale(tree->fall_dir, 21.0f));
+    Vec3 t = Site_bigTree(), tip = vec3_add(t, vec3_scale(tree->fall_dir, 21.0f)), safe = Site_bigTreeSafe();
+    bt->off = acosf(clampf(vec3_dot(tree->fall_dir, safe), -1.0f, 1.0f)) * 180.0f / PI_F; /* (however it was felled) */
     Vec3 pole[4];
     int n = Site_phoneLine(pole, 4);
     for (int k = 0; k + 1 < n; k++) if (crosses(t, tip, pole[k], pole[k + 1])) bt->lines_down = true;
@@ -257,16 +261,45 @@ static void down(Game *game, Object *tree) {
     game->message_time = 8.0f;
 }
 
+/* A tree felled that wasn't to be: the fine from the cash box (forestry.c counted it). */
+static void treeFine(Game *game) {
+    World *w = &game->world;
+    if (w->tree_fine <= 0) return;
+    w->cash -= (float)w->tree_fine;
+    Object *kurti = named(game, "Kurti");
+    static char line[160];
+    if (w->tree_fine_kind == 1) {
+        if (kurti) { kurti->commitment = fmaxf(0.0f, kurti->commitment - 0.3f); Ai_say(kurti, "My apple tree! My father planted that one. Unbelievable."); }
+        snprintf(line, sizeof line, "One of Kurti's apple trees - %d EUR from the cash box for a new one, and Kurti's furious", w->tree_fine);
+    } else snprintf(line, sizeof line, "That tree was protected - the town's fine: %d EUR from the cash box", w->tree_fine);
+    say(game, line);
+    game->message_time = 6.0f;
+    w->tree_fine = 0;
+}
+
 void game_bigTree(Game *game, float dt) {
     World *w = &game->world;
     BigTree *bt = &w->bigtree;
     (void)dt;
+    treeFine(game);
     if (bt->lines_down != game->wires_shown) { Site_phoneWiresDown(w, bt->lines_down); game->wires_shown = bt->lines_down; }
     Object *tree = BigTree_tree(w);
     if (!tree) return;
     Object *enrico = named(game, "Enrico");
     throwLine(game);
     pullers(game, enrico);
+    if (tree->toppled && bt->step < BT_FALLING) { /* (felled some other way: by you without the rope, or the storm) */
+        bt->step = BT_FALLING;
+        for (int i = 0; i < w->count; i++) w->objects[i]->puller = 0.0f;
+    }
+    if (!tree->toppled && bt->step < BT_CUT && Weather_windSpeed(w) > STORM_WIND) { /* old and half dead: a storm can bring it down */
+        w->rng = w->rng * 1664525u + 1013904223u;
+        if ((float)(w->rng >> 8) / 16777216.0f < STORM_FALLS * w->game_dt / 3600.0f) {
+            Forestry_fell(w, tree, Site_bigTreeLean(), 0.3f);
+            say(game, "CRACK! The big old tree gives way in the storm - it's coming down!");
+            game->message_time = 6.0f;
+        }
+    }
     if (bt->step == BT_FALLING && !tree->toppled) fell(game, tree, enrico);
     if (bt->step == BT_FALLING && tree->toppled && tree->topple >= 1.0f) down(game, tree);
     showRope(game);
diff --git a/entropie/src/game/game_injury.c b/entropie/src/game/game_injury.c
index e4018e0..1d0aef2 100644
--- a/entropie/src/game/game_injury.c
+++ b/entropie/src/game/game_injury.c
@@ -185,6 +185,9 @@ static void ambulance(Game *game, float dt) {
 void game_injury(Game *game, float dt) {
     World *w = &game->world;
     if (game->meeting.kind) return;
+    Object *you = game->player;
+    if (you->sprain > 0.0f && !game->sprain_told) { game->sprain_told = true; say(game, "Ow - you twisted your ankle. A few hours of limping"); }
+    else if (you->sprain <= 0.0f && game->sprain_told) { game->sprain_told = false; say(game, "Your ankle's fine again"); }
     youHurt(game, dt);
     ambulance(game, dt);
     for (int i = 0; i < w->count; i++) {
diff --git a/entropie/src/game/save.c b/entropie/src/game/save.c
index 2846a41..a9d1cf0 100644
--- a/entropie/src/game/save.c
+++ b/entropie/src/game/save.c
@@ -79,7 +79,7 @@ static const Field PERSON[] = {
     RE("ai_woodpile", ai.woodpile), BY("ai_banned", ai.banned), BY("ai_defies", ai.defies), BO("ai_unload", ai.unload),
     BO("ai_greet", ai.greet), FL("willing", ai.character.willing), IN("snails", snails),
     IN("injured", injured), IN("injury_side", injury_side), FL("hospital_until", hospital_until), IN("cast_until", cast_until),
-    IN("cast_limb", look.cast), IN("guest", guest), FL("fun", fun), IN("camp_spot", camp_spot), BO("gone_home", gone_home), BO("storm_told", storm_told),
+    IN("cast_limb", look.cast), FL("sprain", sprain), VE("last_ground", last_ground), IN("guest", guest), FL("fun", fun), IN("camp_spot", camp_spot), BO("gone_home", gone_home), BO("storm_told", storm_told),
     VE("inside_door", inside_door),
 };
 
diff --git a/entropie/src/people/body.c b/entropie/src/people/body.c
index 0a78517..a45b431 100644
--- a/entropie/src/people/body.c
+++ b/entropie/src/people/body.c
@@ -276,25 +276,34 @@ void Body_rollOnto(Body *b, Vec3 along, float side) {
     b->roll_over  = side >= 0.0f ? 1.0f : -1.0f;
 }
 
-#define LAND_EASY  5.0f  /* m/s down (about 1.3 m): the legs take it as they are */
-#define LAND_DEEP_ 7.0f  /* (2.5 m) deep into the knees; faster: roll or crash */
-#define LAND_BREAK 9.5f  /* (4.6 m) nobody lands that on their feet ... */
-#define LAND_SPORT 1.3f  /* ... but the very sporty roll it off up to this much faster (5.8 m) */
-
-int Body_land(Body *b, Vec3 dir, float speed, float sure, float sporty) {
+#define LAND_EASY      5.0f  /* m/s down (about 1.3 m): the legs take it as they are */
+#define LAND_DEEP_     7.0f  /* (2.5 m) deep into the knees, bounced off ... */
+#define LAND_KNEES     8.2f  /* (3.4 m) ... the knees alone take it up to here (the sure), harder: a roll or a crash */
+#define LAND_ROLL_MOST 8.0f  /* (3.3 m) a roll takes it up to this ... */
+#define LAND_SPORT     1.0f  /* ... the very sporty this much more (4.1 m) */
+#define LAND_BREAK     10.5f /* (5.6 m) landing this hard, something gives (the worst) */
+#define ROLL_FORWARD   1.2f  /* m/s along: a roll needs going somewhere */
+
+int Body_land(Body *b, Vec3 dir, float speed, float forward, float sure, float sporty, float *bad) {
+    *bad = 0.0f;
     if (b->pose != POSE_UP || speed < LAND_EASY) return LAND_SOFT;
+    /* first always into the knees: bounce it off */
     float hard = (speed - LAND_EASY) / (LAND_DEEP_ - LAND_EASY); /* 0 .. 1 at a deep landing */
     b->land_squat = clampf(0.35f + 0.5f * hard, 0.0f, 1.1f);
     b->crouch_v += 3.0f + 2.0f * hard; /* (down at once, not eased) */
-    if (speed < LAND_DEEP_) return LAND_DEEP;
-    float limit = LAND_BREAK + LAND_SPORT * sporty;
-    float risk = (speed - LAND_DEEP_) / (limit - LAND_DEEP_) * (1.0f - 0.8f * sporty); /* 0 .. 1 (the very sporty: hardly any) */
-    if (speed < limit && random01(b) < sure * (1.0f - 0.7f * risk)) { /* over the shoulder, and up again */
+    /* going somewhere, not too high, and knowing how: over the shoulder, and up again */
+    float limit = LAND_ROLL_MOST + LAND_SPORT * sporty;
+    if (speed >= LAND_DEEP_ - 0.5f && forward >= ROLL_FORWARD && speed < limit && random01(b) < sure) {
         float lateral = vec3_dot(flat(dir), rightOf(b->last_facing)); /* (the shoulder on the side the way goes, else their own) */
         float side = fabsf(lateral) > 0.2f ? (lateral > 0.0f ? 1.0f : -1.0f) : (b->quirk[0] > 0.0f ? 1.0f : -1.0f);
         Body_shoulderRoll(b, dir, side);
+        *bad = 0.05f * clampf((speed - LAND_DEEP_) / (limit - LAND_DEEP_), 0.0f, 1.0f);
         return LAND_ROLL;
     }
+    if (speed < LAND_DEEP_) { *bad = 0.12f * hard * (1.0f - sure); return LAND_DEEP; } /* (the knees take it - an unsure one may twist an ankle) */
+    float over = clampf((speed - LAND_DEEP_) / (LAND_BREAK - LAND_DEEP_), 0.0f, 1.0f);
+    *bad = clampf(0.2f + 0.8f * over - 0.25f * sure, 0.05f, 1.0f);
+    if (speed < LAND_KNEES && random01(b) < 0.4f + 0.5f * sure) { *bad *= 0.6f; return LAND_DEEP; } /* (deep, and still on the feet) */
     Body_fall(b, dir);
     return LAND_CRASH;
 }
diff --git a/entropie/src/people/body.h b/entropie/src/people/body.h
index 91740f6..9225b0e 100644
--- a/entropie/src/people/body.h
+++ b/entropie/src/people/body.h
@@ -300,11 +300,13 @@ Vec3  Body_takeShift(Body *body);
 void  Body_stagger(Body *body, Vec3 push);
 /* Falls over towards dir (world), lies a moment and gets up again. */
 void  Body_fall(Body *body, Vec3 dir);
-/* Landed from high up at this speed (m/s down); sure 0..1 (fit, sober, fresh, sure-footed),
-   sporty 0..1 (the very sporty: they roll off drops of 5 m and more).
-   Soft: into the knees; harder: deep, a roll (the sure) or a crash. Returns LAND_*. */
+/* Landed from high up at this speed (m/s down), going this fast along (m/s); sure 0..1 (fit, sober,
+   fresh, sure-footed), sporty 0..1. Always into the knees first (bounced off); going along and not
+   too high (about 3.3 m, the very sporty 4 m): a roll over the shoulder (the sure); higher, or nowhere
+   to roll to: deep in the knees, or a crash. Returns LAND_*; bad: how hard it was on the body (0..1:
+   a twisted ankle, a broken bone - world.c decides). */
 enum { LAND_SOFT, LAND_DEEP, LAND_ROLL, LAND_CRASH };
-int   Body_land(Body *body, Vec3 dir, float speed, float sure, float sporty);
+int   Body_land(Body *body, Vec3 dir, float speed, float forward, float sure, float sporty, float *bad);
 /* Over one shoulder (side -1 left, 1 right) along dir (world) and up on the feet again: the sporty landing from high up. */
 void  Body_shoulderRoll(Body *body, Vec3 dir, float side);
 bool  Body_isRolling(const Body *body);
diff --git a/entropie/src/render/shapes.c b/entropie/src/render/shapes.c
index 17bea06..265adb3 100644
--- a/entropie/src/render/shapes.c
+++ b/entropie/src/render/shapes.c
@@ -359,11 +359,22 @@ Mesh Shapes_tree(TreeSize size) {
     const Vec3 leaves = size.fruit ? COLOR_FRUIT_LEAVES : COLOR_LEAVES;
     const int full = size.fruit ? TREE_FRUIT_LAYERS : size.leaf_layers;
     unsigned seed = 7u + (unsigned)(size.trunk_height * 100.0f);
+    const Vec3 dead = { 0.48f, 0.38f, 0.24f };
     for (int l = 0; l < size.leaf_layers; l++) {
         float y = size.trunk_height + (float)l * layerStep(size) - (size.fruit ? 0.3f : 0.0f);
         float r = size.fruit ? size.leaf_radius * (0.45f + 0.55f * sinf(PI_F * ((float)l + 0.7f) / ((float)full + 0.4f)))
                              : size.leaf_radius * (1.0f - (float)l / (float)size.leaf_layers);
         Vec3 tip = vec3(0.0f, y + 0.8f, 0.0f);
+        Vec3 leaves_l = leaves;
+        if (size.old) { /* (old: some layers gone, some dead and thin, the top broken off) */
+            unsigned h = (unsigned)l * 2654435761u ^ 0x9e3779b9u;
+            h ^= h >> 13;
+            int k = (int)(h % 7u);
+            if (k == 0 || l >= size.leaf_layers - 3) continue;
+            if (k <= 2) { leaves_l = dead; r *= 0.75f; }
+            else if (k == 3) leaves_l = vec3_lerp(leaves, dead, 0.5f);
+            if (l >= size.leaf_layers - 6) r *= 0.7f;
+        }
 
         for (int i = 0; i < segments; i++) {
             float a0 = 2.0f * PI_F * (float)i / (float)segments;
@@ -372,10 +383,10 @@ Mesh Shapes_tree(TreeSize size) {
             Vec3 p1 = vec3(cosf(a1) * r, y, sinf(a1) * r);
             Vec3 n  = vec3_normalize(vec3_cross(vec3_sub(p0, tip), vec3_sub(p1, tip)));
             if (n.y < 0.0f) n = vec3_scale(n, -1.0f); /* cone surface faces up/out */
-            MeshBuilder_gridTriangle(&b, tip, p0, p1, n, leaves, CELL_SIZE);
+            MeshBuilder_gridTriangle(&b, tip, p0, p1, n, leaves_l, CELL_SIZE);
             /* Underside closes the cone so it casts a shadow */
             MeshBuilder_gridTriangle(&b, vec3(0.0f, y, 0.0f), p0, p1, vec3(0, -1, 0),
-                                     leaves, CELL_SIZE);
+                                     leaves_l, CELL_SIZE);
         }
         /* A few apples hanging at the rim */
         for (int k = 0; size.fruit && k < 3; k++) {
@@ -383,6 +394,31 @@ Mesh Shapes_tree(TreeSize size) {
             addBlock(&b, vec3(cosf(a) * r * 0.92f, y + 0.02f, sinf(a) * r * 0.92f), vec3(0.09f, 0.09f, 0.09f), COLOR_APPLE_RED);
         }
     }
+    if (size.old) { /* the trunk on up through the crown, bare, snapped off up top: a jagged end */
+        float top = size.trunk_height + (float)(size.leaf_layers - 2) * layerStep(size);
+        shapes_addTube(&b, size.trunk_height, size.trunk_radius * 0.8f, size.trunk_radius * 0.3f, top - size.trunk_height, segments, COLOR_BARK, false);
+        for (int k = 0; k < 4; k++) {
+            float a = 1.7f * (float)k;
+            addBlock(&b, vec3(cosf(a) * size.trunk_radius * 0.18f, top + 0.15f + 0.12f * (float)(k % 2), sinf(a) * size.trunk_radius * 0.18f),
+                     vec3(size.trunk_radius * 0.18f, 0.35f + 0.15f * (float)k, size.trunk_radius * 0.18f), (Vec3){ 0.62f, 0.56f, 0.44f }); /* (splinters) */
+        }
+        for (int k = 0; k < 3; k++) { /* dead branches sticking out, bare */
+            float a = 2.3f * (float)k + 0.4f, y = size.trunk_height + 1.0f + 2.6f * (float)k;
+            Vec3 from = vec3(cosf(a) * size.trunk_radius * 0.6f, y, sinf(a) * size.trunk_radius * 0.6f);
+            Vec3 to = vec3(cosf(a) * (size.leaf_radius * 0.9f), y + 0.6f, sinf(a) * (size.leaf_radius * 0.9f));
+            size_t f2 = b.vertex_count;
+            float len = vec3_length(vec3_sub(to, from));
+            shapes_addTube(&b, 0.0f, 0.07f, 0.03f, len, 6, COLOR_BARK, false);
+            Vec3 ay = vec3_normalize(vec3_sub(to, from)); /* (the tube's +Y along the branch) */
+            Vec3 x = vec3_normalize(vec3_cross(ay, vec3(0.0f, 1.0f, 0.0f))), z = vec3_cross(x, ay);
+            Mat4 m = mat4_identity();
+            m.m[0] = x.x; m.m[1] = x.y; m.m[2] = x.z;
+            m.m[4] = ay.x; m.m[5] = ay.y; m.m[6] = ay.z;
+            m.m[8] = z.x; m.m[9] = z.y; m.m[10] = z.z;
+            m.m[12] = from.x; m.m[13] = from.y; m.m[14] = from.z;
+            transformSince(&b, f2, m);
+        }
+    }
     return Mesh_fromBuilder(&b);
 }
 
diff --git a/entropie/src/render/shapes.h b/entropie/src/render/shapes.h
index 09be0b4..823a9f6 100644
--- a/entropie/src/render/shapes.h
+++ b/entropie/src/render/shapes.h
@@ -36,6 +36,7 @@ typedef struct {
     float leaf_radius;   /* radius of the lowest cone (fruit trees: the widest) */
     bool  fruit;         /* a round crown with apples instead of a pine */
     bool  cut;           /* felled: no foot, the cut face at its end (else the foot flares into roots) */
+    bool  old;           /* old and half dead: gaps in the crown, brown dead layers, the top snapped off (a bare snag) */
     unsigned seed;       /* the foot's roots (Shapes_stump makes the same ones from the same seed) */
 } TreeSize;
 
diff --git a/entropie/src/world/forestry.c b/entropie/src/world/forestry.c
index 374cf3f..7b04c48 100644
--- a/entropie/src/world/forestry.c
+++ b/entropie/src/world/forestry.c
@@ -1,6 +1,7 @@
 #include "world/forestry.h"
 #include "world/physics.h"
 #include "world/injury.h"
+#include "world/site.h"
 
 #define STUMP_MIN        0.3f    /* stumps are at least this wide */
 #define ROUND_PIECE      0.6f    /* a chopped-out stump gives a chunk of wood this long at most */
@@ -28,11 +29,18 @@ static void releaseAll(World *world, Object *o) {
 
 static void fellTowards(World *world, Object *tree, Vec3 away, float cut_height);
 
+#define FINE_APPLE     300  /* EUR: one of Kurti's apple trees felled (and Kurti cross) */
+#define FINE_PROTECTED 1000 /* EUR: a tree that stays (the town's tree protection) */
+#define BIG_LEAN       1.4f /* the big old tree felled without a rope: it goes the way it leans */
+
 static void fell(World *world, Object *tree, const Object *by, float cut_height) {
     if (tree->type != OBJ_TREE || tree->toppled) return;
     Vec3 away = vec3_sub(tree->position, by->position);
     away.y = 0.0f;
     if (vec3_length(away) < 1e-3f) away = Object_facing(by);
+    if (tree->variant == 2) away = vec3_add(vec3_normalize(away), vec3_scale(Site_bigTreeLean(), BIG_LEAN)); /* (no rope on it) */
+    else if (tree->variant == 1) { world->tree_fine += FINE_APPLE; world->tree_fine_kind = 1; }
+    else if (tree->keep) { world->tree_fine += FINE_PROTECTED; world->tree_fine_kind = 2; }
     fellTowards(world, tree, away, cut_height);
 }
 
diff --git a/entropie/src/world/injury.c b/entropie/src/world/injury.c
index 3516264..82e9a62 100644
--- a/entropie/src/world/injury.c
+++ b/entropie/src/world/injury.c
@@ -7,6 +7,9 @@
 #define ARM_TEMPO    0.75f /* ... and of their tempo at work */
 #define LEG_SPEED    0.42f /* a cast on the leg: this much of their speed ... */
 #define LEG_JUMP     0.35f /* ... and of their jump */
+#define SPRAIN_TIME  (8.0f * 3600.0f) /* game s limping on a twisted ankle ... */
+#define SPRAIN_SPEED 0.6f  /* ... this much of their speed ... */
+#define SPRAIN_JUMP  0.5f  /* ... and jump */
 
 static float dice(World *world) {
     world->rng = world->rng * 1664525u + 1013904223u;
@@ -50,3 +53,27 @@ void Injury_setCast(Object *who, int kind, int side, bool on) {
     }
     who->look.cast = on ? limb : 0;
 }
+
+static void sprainOn(Object *who, bool on) {
+    float k = on ? 1.0f : -1.0f;
+    who->top_speed *= powf(SPRAIN_SPEED, k);
+    who->jump_speed *= powf(SPRAIN_JUMP, k);
+    who->pace *= powf(SPRAIN_SPEED + 0.15f, k);
+}
+
+bool Injury_sprain(World *world, Object *who) {
+    (void)world;
+    if (!who || !who->is_character || who->type == OBJ_DOG || who->sprain > 0.0f || who->injured) return false;
+    who->sprain = SPRAIN_TIME;
+    sprainOn(who, true);
+    if (who->type != OBJ_PLAYER) Ai_say(who, "Ow! My ankle - twisted it. That'll be a few hours of limping.");
+    return true;
+}
+
+void Injury_update(Object *who, float game_dt) {
+    if (who->sprain <= 0.0f) return;
+    if ((who->sprain -= game_dt) > 0.0f) return;
+    who->sprain = 0.0f;
+    sprainOn(who, false);
+    if (who->type != OBJ_PLAYER) Ai_say(who, "Ankle's fine again.");
+}
diff --git a/entropie/src/world/injury.h b/entropie/src/world/injury.h
index e35902f..190a9cd 100644
--- a/entropie/src/world/injury.h
+++ b/entropie/src/world/injury.h
@@ -16,6 +16,10 @@ bool Injury_break(World *world, Object *who, int kind);
 int  Injury_roll(World *world, float chance);
 /* The cast on (what it does to them) or off again. */
 void Injury_setCast(Object *who, int kind, int side, bool on);
+/* A twisted ankle: limping for some hours (slower, hardly a jump), then fine. False: already. */
+bool Injury_sprain(World *world, Object *who);
+/* Every frame: the sprain wearing off. */
+void Injury_update(Object *who, float game_dt);
 /* What they have on: INJ_* of the cast (from look.cast). */
 int  Injury_castKind(const Object *who);
 
diff --git a/entropie/src/world/object.c b/entropie/src/world/object.c
index 61072e7..f37f5bc 100644
--- a/entropie/src/world/object.c
+++ b/entropie/src/world/object.c
@@ -57,7 +57,7 @@ static void setupBuilding(Object *obj, BuildingSize b) {
 static TreeSize treeSize(const Object *obj) {
     return (TreeSize){ .trunk_height = obj->size.y, .trunk_radius = obj->variant == 2 ? BIG_TRUNK : obj->variant ? TRUNK_RADIUS * 0.8f : TRUNK_RADIUS,
                        .leaf_layers = obj->branches > 0 ? obj->branches : 1, .leaf_radius = obj->size.x * 0.5f,
-                       .fruit = obj->variant == 1, .cut = obj->trunk_cut,
+                       .fruit = obj->variant == 1, .cut = obj->trunk_cut, .old = obj->variant == 2,
                        .seed = Shapes_treeSeed(obj->position.x, obj->position.z) };
 }
 
@@ -1657,7 +1657,7 @@ void Object_describe(const Object *obj, char *out, size_t size) {
     case OBJ_BRANCH: snprintf(out, size, "Branch"); break;
     case OBJ_WOODPILE: snprintf(out, size, "Woodpile - %d pieces of firewood", obj->stored); break;
     case OBJ_TREE:
-        if (obj->variant == 2 && !obj->toppled) snprintf(out, size, "The big old tree - it leans towards the phone line. Enrico's job (Go-Darmstadt)");
+        if (obj->variant == 2 && !obj->toppled) snprintf(out, size, "The big old tree - half dead, top broken off, leaning to the road. It must come down before it falls: Enrico's job");
         else if (obj->variant == 2) snprintf(out, size, "The big old tree, down - %d branch%s left", obj->branches, obj->branches == 1 ? "" : "es");
         else if (!obj->toppled && obj->variant) snprintf(out, size, "Apple tree - pick an apple");
         else if (!obj->toppled && obj->keep) snprintf(out, size, "Tree - this one stays (Kurti's rule)");
diff --git a/entropie/src/world/object.h b/entropie/src/world/object.h
index 07a851d..d86ed5b 100644
--- a/entropie/src/world/object.h
+++ b/entropie/src/world/object.h
@@ -394,6 +394,8 @@ typedef struct Object {
     bool       drop_tip;      /* bottles: dropped (not set down): it falls over when it lands, mostly */  /* characters: sitting, the chair shuffled in to the table */
     bool       leaning;       /* ladders: leaning against something (stands still) */
     float      air_fall;      /* characters: in the air, falling this fast at most (m/s; for the landing) */
+    Vec3       last_ground;   /* characters: where they last stood on something (fell through anyway: back there) */
+    float      sprain;        /* characters: game s left limping on a twisted ankle (world/injury.c) */
     float      ladder_skill;  /* characters: practice climbing ladders one-handed (0..1) ... */
     float      ladder_wobble; /* ... nearly slipped: the next slip is a fall (seconds this counts) */
     Vec3       lean_normal;   /* ladders: away from what it leans on */
diff --git a/entropie/src/world/physics.c b/entropie/src/world/physics.c
index ff75df4..7097fd4 100644
--- a/entropie/src/world/physics.c
+++ b/entropie/src/world/physics.c
@@ -6,6 +6,7 @@
 #define GRAVITY             9.81f
 #define WALKABLE_SLOPE      0.7f   /* cos of steepest walkable angle (~45 deg) */
 #define CEILING_SLOPE      -0.7f
+#define SINK_MOST           0.45f  /* m: a person's feet this far into something solid's top: up onto it (not pushed down) */
 #define MAX_PUSH_CORRECTION 0.5f   /* max push-out per contact between dynamic objects */
 #define PUSH_STRENGTH       2.0f
 #define PUSH_RECOIL         0.2f
@@ -468,6 +469,11 @@ static void resolveContact(Object *obj, int io, Object *other, int ic, Vec3 norm
     if (normal.y <= CEILING_SLOPE) {
         /* Dynamic objects below are resolved when they rest on us. */
         if (!other->is_static) return;
+        if (obj->is_character) { /* (the feet at its top - a roof met at a valley, a floor's edge: up onto it, never pushed down through) */
+            float t = rayDownInto(vec3(obj->position.x, obj->position.y + 2.0f, obj->position.z), other, ic);
+            float rise = t >= 0.0f ? 2.0f - t : -1.0f;
+            if (rise >= -0.05f && rise < SINK_MOST) { land(obj, fmaxf(rise, 0.0f)); obj->standing_on = other; return; }
+        }
         if (obj->on_terrain) { /* lying on the ground under it (a shelf): squeezed out sideways, never into the ground */
             Vec3 away = vec3_sub(Object_colliderCenter(obj, io), Object_colliderCenter(other, ic));
             away.y = 0.0f;
@@ -562,6 +568,29 @@ static void neverThrough(Object *obj, Vec3 start, Object *const *objects, int co
     }
 }
 
+/* Nobody goes down through a floor or a roof: the feet went in at its top this step - back up onto it. */
+static void neverThroughFloor(Object *obj, Vec3 start, Object *const *objects, int count) {
+    if (obj->position.y >= start.y) return;
+    Vec3 a = vec3_add(start, vec3(0.0f, 0.05f, 0.0f)), b = vec3_add(obj->position, vec3(0.0f, 0.05f, 0.0f));
+    float best = -1e9f;
+    for (int i = 0; i < count; i++) {
+        const Object *o = objects[i];
+        if (!o->is_static || o == obj || o->collider_count == 0 || o->type == OBJ_DECAL) continue;
+        for (int c = 0; c < o->collider_count; c++) {
+            if (!segmentThrough(a, b, o, c)) continue;
+            float t = rayDownInto(vec3(b.x, a.y, b.z), o, c);
+            if (t < 0.0f) continue;
+            float top = a.y - t;
+            if (top > b.y && top > best) best = top;
+        }
+    }
+    if (best < -1e8f) return;
+    obj->position.y = best - 0.05f;
+    if (obj->velocity.y < 0.0f) obj->velocity.y = 0.0f;
+    obj->is_grounded = true;
+    Object_syncColliders(obj);
+}
+
 /* The eight corners of a collider. */
 static void corners(const Object *obj, int i, Vec3 out[8]) {
     const Collider *c = &obj->colliders[i];
@@ -1152,6 +1181,7 @@ void Physics_step(Object *obj, Object *const *objects, int count, const Terrain
     resolveTerrain(obj, terrain);
     resolveObjects(obj, objects, count);
     if (obj->is_character) neverThrough(obj, start, objects, count);
+    if (obj->is_character) neverThroughFloor(obj, start, objects, count);
     obj->impact = vec3_length(vec3_sub(before, obj->velocity));
     if (obj->drop_tip && obj->is_grounded) { /* a bottle dropped: it hardly ever lands standing */
         obj->drop_tip = false;
diff --git a/entropie/src/world/tools.c b/entropie/src/world/tools.c
index 67f5f1c..d3c302e 100644
--- a/entropie/src/world/tools.c
+++ b/entropie/src/world/tools.c
@@ -212,7 +212,7 @@ static Plan planFor(const Object *who, ToolKind tool, Object *const *objects, in
         Object *o = objects[i];
         if (o == who || o->is_held || o->collider_count == 0) continue;
 
-        if (o->type == OBJ_TREE && !o->toppled && !o->keep && workFor(tool, JOB_FELL) > 0.0f) {
+        if (o->type == OBJ_TREE && !o->toppled && (!o->keep || who->type == OBJ_PLAYER) && workFor(tool, JOB_FELL) > 0.0f) { /* (you may fell any tree - some cost dear: forestry.c) */
             /* The cut goes in where the worker looks, within the tool's range */
             Vec3 c = Object_colliderCenter(o, 0);
             float distance = vec3_length(flat(vec3_sub(c, who->position)));
@@ -316,7 +316,7 @@ static bool stillThere(const Work *w) {
     const Object *o = w->target;
     if (!o) return false;
     switch (w->job) {
-    case JOB_FELL:   return o->type == OBJ_TREE && !o->toppled && !o->keep;
+    case JOB_FELL:   return o->type == OBJ_TREE && !o->toppled;
     case JOB_DELIMB: return o->type == OBJ_TREE && o->toppled && o->topple >= 1.0f;
     case JOB_CUT:    return o->type == OBJ_LOG && !o->is_held;
     case JOB_SPLIT:  return (o->type == OBJ_LOG || (o->type == OBJ_FIREWOOD && o->size.z < 4.0f)) && !o->is_held;
diff --git a/entropie/src/world/world.c b/entropie/src/world/world.c
index 22ab6b1..1bfdc9e 100644
--- a/entropie/src/world/world.c
+++ b/entropie/src/world/world.c
@@ -922,6 +922,7 @@ static void showProps(World *world) {
     }
 }
 
+#define VOID_BELOW -60.0f /* m: below everything there is (off the map the ground is far down) */
 #define ROLL_ON 3.0f /* m/s on through a shoulder roll (scaled) */
 
 /* Down from high up: into the knees, deeper, a roll or a crash (see Body_land). */
@@ -934,17 +935,22 @@ static void landFromHigh(World *world, Object *o) {
                * (1.0f - 0.5f * Body_clumsiness(&o->body) * (1.0f - sporty)) + 0.15f * sporty; /* (the very sporty: next to sure) */
     Vec3 dir = vec3(o->velocity.x, 0.0f, o->velocity.z);
     if (vec3_length(dir) < 0.3f) dir = Object_facing(o);
-    int how = Body_land(&o->body, vec3_normalize(dir), speed, sure, sporty);
+    float forward = hypotf(o->body.velocity.x, o->body.velocity.z), bad = 0.0f;
+    int how = Body_land(&o->body, vec3_normalize(dir), speed, forward, sure, sporty, &bad);
     float jar = smoothstepf(5.0f, 10.0f, speed);
     o->needs.stamina = fmaxf(0.0f, o->needs.stamina - (how == LAND_CRASH ? 0.25f : 0.08f) * jar);
-    if (how == LAND_CRASH) {
-        o->dirt = fminf(1.0f, o->dirt + 0.1f);
-        int broke = Injury_roll(world, smoothstepf(9.0f, 13.0f, speed) * 0.8f); /* (from high enough: a broken leg, or an arm put out to catch it) */
-        if (broke && Injury_break(world, o, broke)) return;
-        Ai_say(o, speed > 9.5f + 1.3f * sporty ? "Aaargh... my ankle!" : "Ouch!");
-    } else if (how == LAND_ROLL) {
-        o->dirt = fminf(1.0f, o->dirt + 0.05f);
+    if (how == LAND_CRASH) o->dirt = fminf(1.0f, o->dirt + 0.1f);
+    else if (how == LAND_ROLL) o->dirt = fminf(1.0f, o->dirt + 0.05f);
+    /* how it went for the body: the worse, the likelier something gives - a twisted ankle, or (bad) a broken bone */
+    world->rng = world->rng * 1664525u + 1013904223u;
+    float d1 = (float)(world->rng >> 8) / 16777216.0f;
+    world->rng = world->rng * 1664525u + 1013904223u;
+    float d2 = (float)(world->rng >> 8) / 16777216.0f;
+    if (bad > 0.0f && d1 < bad) {
+        if (bad + 0.35f * d2 > 0.8f && Injury_break(world, o, how == LAND_CRASH && d2 < 0.3f ? INJ_ARM : INJ_LEG)) return;
+        if (Injury_sprain(world, o)) return;
     }
+    if (how == LAND_CRASH) Ai_say(o, "Ouch!");
 }
 
 /* Up a steep bank (walking straight up it): bent forward, a free hand put down
@@ -1546,6 +1552,7 @@ void World_update(World *world, float dt, float game_dt) {
         }
         Condition_useGrid(&world->grid);
         Condition_update(o, world->objects, world->count, dt, game_dt);
+        Injury_update(o, 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;
@@ -1584,6 +1591,16 @@ void World_update(World *world, float dt, float game_dt) {
         hatBackOn(o);
         Physics_step(o, world->objects, world->count, &world->terrain, dt);
         landThrown(world, o);
+        if (o->is_character) { /* (a safety net: whoever got through the floor somehow is back where they last stood) */
+            if (o->is_grounded && !o->riding && o->indoors <= 0.0f) o->last_ground = o->position;
+            else if (o->position.y < VOID_BELOW && vec3_length(o->last_ground) > 0.1f) {
+                o->position = vec3_add(o->last_ground, vec3(0.0f, 0.05f, 0.0f));
+                o->velocity = o->movement = vec3_zero();
+                o->air_fall = 0.0f;
+                o->body.replant = true;
+                Object_syncColliders(o);
+            }
+        }
     }
     Grid_build(&world->grid, world->objects, world->count); /* (where things are now: for the questions below) */
     Forestry_update(world, dt);
diff --git a/entropie/src/world/world.h b/entropie/src/world/world.h
index 63d6ba0..bea5b2c 100644
--- a/entropie/src/world/world.h
+++ b/entropie/src/world/world.h
@@ -125,6 +125,8 @@ typedef struct World {
     int      why_heard;    /* ... bit k: came up at the Herberge (not seen on the ground) */
     Fest     fest;         /* the festival weekend (set by the game every frame) */
     BigTree  bigtree;      /* the big old tree's felling */
+    int      tree_fine;    /* EUR just fined for a tree that wasn't to be felled (the game says so, then 0) ... */
+    int      tree_fine_kind; /* ... 1 one of Kurti's apple trees, 2 a protected one */
 } World;
 
 /* A sound made here (dropped when the list is full: nobody's listening). */