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

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.11.0: taking tents down

Christian Immanuel · 2026-10-20 15:19 · ad5ea18a015a0534d197ccf88b39574f558522ff

- Hold left click right at a standing tent: after a moment it comes down
  (pegs out, cover off, poles down, folded into its bag)
- Sleeping bags and mats that were inside come out at the door
- The owner and people nearby help; a tent packed on purpose stays packed
- New Gear_create (car.c) for camping things
 commit_message.txt               | 12 ++---
 entropie/Makefile                |  2 +-
 entropie/README.md               |  6 ++-
 entropie/STATE.md                |  8 +++-
 entropie/src/game/game.h         |  1 +
 entropie/src/game/game_actions.c | 51 +++++++++++++++------
 entropie/src/people/ai.c         | 10 ++--
 entropie/src/world/car.c         | 16 ++++---
 entropie/src/world/car.h         |  3 ++
 entropie/src/world/object.h      |  3 +-
 entropie/src/world/tent.c        | 99 ++++++++++++++++++++++++++++++++++++----
 entropie/src/world/tent.h        |  6 +++
 12 files changed, 174 insertions(+), 43 deletions(-)

diff --git a/commit_message.txt b/commit_message.txt
index 327e736..e487023 100644
--- a/commit_message.txt
+++ b/commit_message.txt
@@ -1,7 +1,7 @@
-entropie 2.10.0: skirt when sitting, rain in tents
+entropie 2.11.0: taking tents down
 
-- A skirt or dress lies over the lap when sitting (knees and shins show),
-  instead of a ball round the thighs
-- Rain: the friends shelter in their own tent at night or when it's nearer
-  than the garage kitchen; they doze and come out when it stops
-- Kurti no longer wanders with the chainsaw (checked in the NPC audit)
+- Hold left click right at a standing tent: after a moment it comes down
+  (pegs out, cover off, poles down, folded into its bag)
+- Sleeping bags and mats that were inside come out at the door
+- The owner and people nearby help; a tent packed on purpose stays packed
+- New Gear_create (car.c) for camping things
diff --git a/entropie/Makefile b/entropie/Makefile
index c5c4d9a..4185ade 100644
--- a/entropie/Makefile
+++ b/entropie/Makefile
@@ -1,5 +1,5 @@
 APP       := entropie
-VERSION   := 2.10.0
+VERSION   := 2.11.0
 
 PREFIX    ?= /usr
 BINDIR    ?= $(PREFIX)/bin
diff --git a/entropie/README.md b/entropie/README.md
index 6849ced..9764bd5 100644
--- a/entropie/README.md
+++ b/entropie/README.md
@@ -67,6 +67,10 @@ Painted in the rotoscope style of *Waking Life* (2001):
 - Z at your tent's door: crawl in and sleep (better with your sleeping bag
   in it: warm, and you're rested sooner). Z again: you crawl out
 - Sleepy, the others go to bed in their tents
+- Taking one down: hold left click right at a standing tent. After a moment
+  the pegs come out, then the cover, the poles, and it's folded into its bag.
+  Sleeping bags and mats come out at the door; the owner helps
+- A tent packed on purpose stays packed (nobody puts it up again on their own)
 
 ## The site
 
@@ -683,7 +687,7 @@ say no more often and don't come to you for a bit.
 | P             | Pee (next to the toilet: use it); Shift: further, mouse: aim |
 | K             | Light a cigarette / put it out |
 | Left click at the car's boot | Take out the next thing |
-| Left click on a tent | Spread it out; hold: put it up |
+| Left click on a tent | Spread it out; hold: put it up (a standing one: take it down) |
 | Z             | Sleep / get up (at your tent's door: in it) |
 | C             | Sit down / get up; on fire: drop and roll; at a fire with a drink: pour it on |
 | Space (asleep, hold) | Sleep faster |
diff --git a/entropie/STATE.md b/entropie/STATE.md
index b10b338..4007350 100644
--- a/entropie/STATE.md
+++ b/entropie/STATE.md
@@ -220,6 +220,10 @@ Round 2.10:
 - Rain, into the tent (`ai.c` shelter): the friends go into their own tent (up) at night (21-7) or when it's nearer than the kitchen; in, they doze (`rain_nap`: not woken by being rested, `condition.c`); `tent.c` Tent_update gets them up once the rain is under RAIN_NAP_END 0.15 (sleepy, over 0.6: they sleep on).
 - Kurti and the chainsaw: gone with "no helping" (2.8) - NPC audit: Kurti 33 joint pops (was 300-900), no tools in hand in 10 min.
 
+Round 2.11:
+- Taking a tent down (`tent.c` Tent_takeDown, `tent_down`): anybody in it comes out (woken); then Tent_work runs the stages backwards (workDown: each DOWN_QUICKER 0.6 of the time up, the pegs out one by one from the last, the cover off, the poles down, FOLD_TIME 7 s folding) and returns TENT_DOWN + the stage (TENT_DOWN_PACKED folded). Tent_update packs it (`pack`): its bag (`Gear_create`, from car.c) where it lay, `keep` (its owner doesn't put it up again on their own: `ai.c` pitchTents), the sleeping bags and mats that were in it at the door; the tent used up (no owner: Tent_of skips it). Drawn the other way round (`shownWork`).
+- You: hold left click right at a standing tent (0.6 m, so a click near it still takes things): TAKE_DOWN_HOLD 1 s, then it comes down while you hold (`game_actions.c` workTent). The owner and anybody near help (helpers drop a packed job). Hints: "hold left click: take it down".
+
 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).
@@ -366,7 +370,7 @@ Most important first.
 - Smoking, still to do: paying for a pack (no money in the game yet)
 - "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
+- Tents, still to do: wind blowing an unpegged one about; people busy (work) when sleepy go to bed late; NPCs taking theirs down on their own (leaving)
 - Rain, still to do: thunder; wet clothes
 - Main building: "the left building's roof peak is too far left" - to be clarified
 
@@ -377,4 +381,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.10.0 (build 4d58e30)`
+State of `entropie 2.11.0 (build 7d62268)`
diff --git a/entropie/src/game/game.h b/entropie/src/game/game.h
index 0c133f6..ce199bb 100644
--- a/entropie/src/game/game.h
+++ b/entropie/src/game/game.h
@@ -74,6 +74,7 @@ typedef struct {
     Object  *putting_up;    /* the ladder you are putting up (hands on it, standing still) */
     Arrival  arrival;       /* the start: in by car */
     struct Object *tent;    /* the tent you're working on (left button held) */
+    float          tent_hold; /* ... held this long at a standing one (a moment: you start taking it down) */
     float    dt;            /* this frame's step */
     bool     rain_told;     /* told it's raining (this shower) */
 } Game;
diff --git a/entropie/src/game/game_actions.c b/entropie/src/game/game_actions.c
index 97a15c9..0a95bf0 100644
--- a/entropie/src/game/game_actions.c
+++ b/entropie/src/game/game_actions.c
@@ -703,16 +703,16 @@ static bool takePullover(Game *game) {
 
 #define TENT_REACH 1.2f /* m: a tent this near to work on it */
 
-/* A packed tent (its bag lying) or a tent not up yet, near and in front. */
-static Object *tentNear(const Game *game, bool packed) {
+/* A packed tent (its bag lying) or a tent not up yet (or a standing one), near and in front. */
+static Object *tentFind(const Game *game, bool packed, bool standing) {
     const Object *p = game->player;
     Vec3 facing = Object_facing(p);
     Object *best = NULL;
-    float best_gap = packed ? 0.9f : TENT_REACH;
+    float best_gap = packed ? 0.9f : standing ? 0.6f : TENT_REACH; /* (a standing one: right at it, so a click near it still takes things) */
     for (int i = 0; i < game->world.count; i++) {
         Object *o = game->world.objects[i];
         bool fits = packed ? o->type == OBJ_GEAR && o->variant >= GEAR_TENT_SMALL && !o->is_held && !o->broken
-                           : o->type == OBJ_TENT && o->tent_stage < TENT_UP;
+                           : o->type == OBJ_TENT && !o->broken && (standing ? o->tent_stage >= TENT_UP && !o->tent_down : o->tent_stage < TENT_UP || o->tent_down);
         if (!fits) continue;
         Vec3 to = vec3_sub(o->position, p->position);
         to.y = 0.0f;
@@ -722,6 +722,8 @@ static Object *tentNear(const Game *game, bool packed) {
     return best;
 }
 
+static Object *tentNear(const Game *game, bool packed) { return tentFind(game, packed, false); }
+
 /* Left click: a packed tent in front spread out (the door towards you), or a tent going up: work on it (held). */
 static int tentClick(Game *game) {
     Object *p = game->player, *bag = tentNear(game, true);
@@ -736,22 +738,36 @@ static int tentClick(Game *game) {
         return CLICK_NONE;
     }
     Object *tent = tentNear(game, false);
+    if (!tent) tent = tentFind(game, false, true); /* (standing: held a moment, it comes down) */
     if (!tent) return -1;
+    game->tent_hold = 0.0f;
     if (p->hands.state[HAND_LEFT] != HAND_FREE && p->hands.state[HAND_RIGHT] != HAND_FREE) { say(game, "A hand free for that"); return CLICK_NONE; }
     game->tent = tent;
     return CLICK_TENT;
 }
 
 /* Left button held at a tent: the next bit of it. */
+#define TAKE_DOWN_HOLD 1.0f /* s: held this long at a standing tent, you start taking it down */
+
 static bool workTent(Game *game) {
     Object *p = game->player, *tent = game->tent;
-    if (!tent || tent->tent_stage >= TENT_UP || Hands_gapFrom(p->position, p->look.height, tent) > TENT_REACH + 0.6f) return false;
+    if (!tent || tent->broken || Hands_gapFrom(p->position, p->look.height, tent) > TENT_REACH + 0.6f) return false;
+    if (tent->tent_stage >= TENT_UP && !tent->tent_down) { /* standing: a moment first (no accidents) */
+        if (game->tent_hold == 0.0f) say(game, "Keep holding to take the tent down");
+        if ((game->tent_hold += game->dt) < TAKE_DOWN_HOLD) return true;
+        Tent_takeDown(game->world.objects, game->world.count, tent);
+        say(game, Tent_isTheirs(tent, p) || !tent->owner ? "You start taking the tent down - pegs out first"
+                                                          : "You start taking their tent down - pegs out first");
+    }
     Hands_touch(p, Tent_handsAt(tent, p, tent->tent_work * 18.0f));
     int got = Tent_work(tent, p, game->dt);
     static const char *const SAID[] = { "", "Spread out - now the poles in and up", "The poles are in - now pull the cover over it",
                                         "The cover's on - now peg it down (a stone helps)", "All pegged - the tent stands" };
-    if (got >= 0) say(game, SAID[got % 5]);
-    return tent->tent_stage < TENT_UP;
+    static const char *const DOWN[] = { "Poles down - now fold it up", "The cover's off - now the poles down", "The pegs are out - now pull the cover off" };
+    if (got == TENT_DOWN_PACKED) { say(game, "Folded up and in its bag - E / Q: carry it"); return false; }
+    if (got >= TENT_DOWN) say(game, DOWN[(got - TENT_DOWN) % 3]);
+    else if (got >= 0) say(game, SAID[got % 5]);
+    return tent->tent_stage < TENT_UP || tent->tent_down;
 }
 
 /* At the cigarette machine: a pack. */
@@ -879,14 +895,19 @@ static bool game_bootHint(Game *game, char *out, size_t size) {
 static bool tentHint(Game *game, char *out, size_t size) {
     Object *p = game->player, *bag = tentNear(game, true), *tent = tentNear(game, false);
     if (bag) { snprintf(out, size, "A tent, packed  -  left click: spread it out here (the door towards you)  -  E / Q: carry it"); return true; }
-    if (tent) { snprintf(out, size, "The tent: hold left click - %s%s", Tent_job(tent), tent->tent_stage == TENT_PEGS ? " (a stone in the other hand: quicker)" : ""); return true; }
-    Object *mine = Tent_of(game->world.objects, game->world.count, p);
-    if (!mine || mine->tent_stage < TENT_COVER) return false;
-    Vec3 door = Tent_door(mine);
-    if (hypotf(door.x - p->position.x, door.z - p->position.z) > 1.6f) return false;
-    if (Hands_carries(p, OBJ_GEAR)) { snprintf(out, size, "Your tent's door: right click - put it down here, it goes in"); return true; }
-    if (mine->tent_stage >= TENT_UP) {
-        snprintf(out, size, "Your tent%s  -  Z: crawl in and sleep", Tent_hasBed(mine, p) ? "" : " (your sleeping bag isn't in it: put it down at the door)");
+    if (tent) { snprintf(out, size, "The tent: hold left click - %s%s", Tent_job(tent), tent->tent_stage == TENT_PEGS && !tent->tent_down ? " (a stone in the other hand: quicker)" : ""); return true; }
+    Object *mine = Tent_of(game->world.objects, game->world.count, p), *standing = tentFind(game, false, true);
+    Vec3 door = mine ? Tent_door(mine) : p->position;
+    bool at_door = mine && mine->tent_stage >= TENT_COVER && hypotf(door.x - p->position.x, door.z - p->position.z) <= 1.6f;
+    if (at_door && Hands_carries(p, OBJ_GEAR)) { snprintf(out, size, "Your tent's door: right click - put it down here, it goes in"); return true; }
+    if (at_door && mine->tent_stage >= TENT_UP && !mine->tent_down) {
+        snprintf(out, size, "Your tent%s  -  Z: crawl in and sleep%s", Tent_hasBed(mine, p) ? "" : " (your sleeping bag isn't in it: put it down at the door)",
+                 standing == mine ? "  -  hold left click: take it down" : "");
+        return true;
+    }
+    if (standing) {
+        snprintf(out, size, "%s%s tent  -  hold left click: take it down", standing->owner == p ? "Your" : standing->owner ? standing->owner->name : "A",
+                 standing->owner && standing->owner != p ? "'s" : "");
         return true;
     }
     return false;
diff --git a/entropie/src/people/ai.c b/entropie/src/people/ai.c
index 1753c8d..4ca5f8a 100644
--- a/entropie/src/people/ai.c
+++ b/entropie/src/people/ai.c
@@ -1822,7 +1822,11 @@ static void workOnTent(Object *self, Object *tent, Object *const *objects, int c
     turnTowards(self, vec3_sub(hands, self->position), dt);
     Hands_touch(self, hands);
     int got = Tent_work(tent, self, dt);
-    if (got >= 0 && random01(ai) < 0.6f) {
+    if (got >= TENT_DOWN) {
+        static const char *const DOWN[] = { "Folding it up now.", "Poles down.", "Cover's off.", "", "" };
+        if (got < TENT_DOWN_PACKED && random01(ai) < 0.5f) Ai_say(self, DOWN[got - TENT_DOWN]);
+        else if (got == TENT_DOWN_PACKED && random01(ai) < 0.7f) Ai_say(self, "Packed. That one's done.");
+    } else if (got >= 0 && random01(ai) < 0.6f) {
         static const char *const SAY[] = { "", "Poles in - up it goes.", "Now the cover over it.", "Pegs next - who's got a stone?", "There - it stands!" };
         Ai_say(self, SAY[got % 5]);
     }
@@ -1839,7 +1843,7 @@ static bool pitchTents(Object *self, Object *const *objects, int count, float dt
     }
     if (!tent) { /* still in its bag: spread it out where it lies */
         Object *bag = ownGear(self, objects, count, true);
-        if (bag && !bag->is_held && flatDistance(bag->position, self->position) < TENT_WALK) {
+        if (bag && !bag->is_held && !bag->keep && flatDistance(bag->position, self->position) < TENT_WALK) {
             if (self->sitting) self->sitting = false;
             if (!goToPoint(self, bag->position, 0.6f, objects, count, dt)) return true;
             freeHands(self, NULL);
@@ -1850,7 +1854,7 @@ static bool pitchTents(Object *self, Object *const *objects, int count, float dt
     }
     /* somebody pitching one near: a hand */
     Object *job = ai->tent_job;
-    if (job && (job->tent_stage >= TENT_UP || (ai->tent_time += dt) > HELP_LONG || job->tent_idle > 20.0f)) job = ai->tent_job = NULL;
+    if (job && (job->broken || (job->tent_stage >= TENT_UP && !job->tent_down) || (ai->tent_time += dt) > HELP_LONG || job->tent_idle > 20.0f)) job = ai->tent_job = NULL;
     if (!job && (ai->mode == AI_IDLE || ai->mode == AI_WANDER)) {
         for (int i = 0; i < count && !job; i++) {
             Object *t = objects[i];
diff --git a/entropie/src/world/car.c b/entropie/src/world/car.c
index 4082104..0e5c72e 100644
--- a/entropie/src/world/car.c
+++ b/entropie/src/world/car.c
@@ -88,6 +88,15 @@ static Vec3 gearColor(GearKind kind, const Object *owner) {
     return vec3(0.5f, 0.5f, 0.5f);
 }
 
+Object *Gear_create(GearKind kind, Object *owner, Vec3 at) {
+    Object *item = Object_create(OBJ_GEAR, PALETTE_STANDARD, at, vec3_zero(), (float)kind);
+    if (!item) return NULL;
+    item->owner = owner;
+    Mesh_destroy(&item->mesh);
+    item->mesh = Shapes_gear((int)kind, gearColor(kind, owner));
+    return item;
+}
+
 Object *Car_takeOut(World *world, Object *car) {
     CarLoad *l = car->load;
     if (!l || l->n == 0 || !car->boot_open) return NULL;
@@ -101,12 +110,7 @@ Object *Car_takeOut(World *world, Object *car) {
     } 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;
-        item = Object_create(OBJ_GEAR, PALETTE_STANDARD, at, vec3_zero(), (float)g);
-        if (item) {
-            item->owner = owner;
-            Mesh_destroy(&item->mesh);
-            item->mesh = Shapes_gear((int)g, gearColor(g, owner));
-        }
+        item = Gear_create(g, owner, at);
     }
     if (!item) return NULL;
     item->rotation.y = car->rotation.y + PI_F * 0.5f; /* (lying across the back of it) */
diff --git a/entropie/src/world/car.h b/entropie/src/world/car.h
index c106c79..1e14e70 100644
--- a/entropie/src/world/car.h
+++ b/entropie/src/world/car.h
@@ -60,4 +60,7 @@ Object     *Crate_takeBottle(World *world, Object *crate);
    the boot (ai.take_from) gets it lying there for them (ai.target). */
 void        Car_serve(World *world);
 
+/* Somebody's camping thing (OBJ_GEAR) lying at `at` (not added to the world yet), or NULL. */
+Object *Gear_create(GearKind kind, Object *owner, Vec3 at);
+
 #endif /* WORLD_CAR_H */
diff --git a/entropie/src/world/object.h b/entropie/src/world/object.h
index 7491639..144e876 100644
--- a/entropie/src/world/object.h
+++ b/entropie/src/world/object.h
@@ -161,7 +161,7 @@ typedef struct Object {
     struct CarLoad *load;     /* cars: what's in the boot (malloc'd, freed with the car) */
     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) */
+    bool       keep;          /* trees: may not be cut down (round the meadow behind your garages); a tent bag: packed on purpose (its owner leaves it packed) */
     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 */
@@ -172,6 +172,7 @@ typedef struct Object {
     float      tent_work;     /* ... how far the stage is (0..1) ... */
     float      tent_idle;     /* ... seconds since anybody worked on it */
     int        tent_beds;     /* ... sleeping bags (bit 0 owner, 2 owner2) and mats (1, 3) put in */
+    bool       tent_down;     /* ... being taken down: the stages backwards, then packed into its bag */
     struct Object *in_tent;   /* characters: sleeping in this tent (no collisions with it) */
     bool          rain_nap;  /* in their tent out of the rain, dozing: not woken by being rested, up when it stops */
     struct Object *riding;    /* characters: sitting in this car (hidden, no physics, no AI: see car.c) ... */
diff --git a/entropie/src/world/tent.c b/entropie/src/world/tent.c
index ae10995..513d025 100644
--- a/entropie/src/world/tent.c
+++ b/entropie/src/world/tent.c
@@ -14,6 +14,13 @@
 #define PEG_TIME    2.5f   /* ... each peg (with a stone to hit it: twice as quick) */
 #define DOOR_OUT    0.45f  /* in front of the door: this far out from the cover */
 #define BED_REACH   0.8f   /* a sleeping bag or mat put down this near the door goes in */
+#define DOWN_QUICKER 0.6f  /* taking it down: each bit this much of the time putting it up took ... */
+#define FOLD_TIME   7.0f   /* ... and folding it up into its bag at the end */
+
+/* How far the stage shows: up, its work; coming down, the other way round. */
+static float shownWork(const Object *tent) {
+    return tent->tent_down ? 1.0f - tent->tent_work : tent->tent_work;
+}
 
 static Vec3 local(const Object *tent, Vec3 p) {
     return vec3_add(tent->position, mat4_mul_dir(mat4_rotate_y(tent->rotation.y), p));
@@ -34,13 +41,13 @@ static Vec3 flyColor(const Object *tent) {
 
 /* Its look and its box for how far it's up (built again only when that shows). */
 static void reshape(Object *tent) {
-    int big = tent->variant, steps = tent->tent_stage == TENT_POLES || tent->tent_stage == TENT_COVER ? (int)(tent->tent_work * 12.0f) : 0;
+    int big = tent->variant, steps = tent->tent_stage == TENT_POLES || tent->tent_stage == TENT_COVER ? (int)(shownWork(tent) * 12.0f) : 0;
     int shown = tent->tent_stage * 1000 + steps * 10 + tent->stored;
     if (shown == tent->shown) return;
     tent->shown = shown;
     Mesh_destroy(&tent->mesh);
     tent->mesh = Shapes_tent(big, tent->tent_stage, (float)steps / 12.0f, tent->stored, vec3(0.90f, 0.86f, 0.62f), flyColor(tent));
-    float h = tent->tent_stage == TENT_LAID ? 0.04f : tent->tent_stage == TENT_POLES ? fmaxf(0.04f, TENT_H(big) * tent->tent_work) : TENT_H(big);
+    float h = tent->tent_stage == TENT_LAID ? 0.04f : tent->tent_stage == TENT_POLES ? fmaxf(0.04f, TENT_H(big) * shownWork(tent)) : TENT_H(big);
     tent->colliders[0].center = vec3(0.0f, h * 0.5f, 0.0f);
     tent->colliders[0].half_extents = vec3(TENT_W(big) * 0.5f, h * 0.5f, TENT_L(big) * 0.5f);
     Object_syncColliders(tent);
@@ -72,10 +79,50 @@ static bool hasStone(const Object *who) {
     return false;
 }
 
+void Tent_takeDown(Object *const *objects, int count, Object *tent) {
+    if (tent->type != OBJ_TENT || tent->tent_down || tent->broken) return;
+    for (int i = 0; i < count; i++) { /* (anybody in it: out) */
+        Object *o = objects[i];
+        if (o->in_tent != tent) continue;
+        o->rain_nap = false;
+        if (o->body.asleep) Body_getUp(&o->body);
+        Tent_crawlOut(o);
+    }
+    tent->tent_down = true;
+    tent->tent_work = 0.0f;
+    if (tent->tent_stage >= TENT_UP) { tent->tent_stage = TENT_PEGS; tent->stored = TENT_PEGS(tent->variant); }
+    else if (tent->tent_stage == TENT_PEGS && tent->stored == 0) tent->tent_stage = TENT_COVER;
+    reshape(tent);
+}
+
+/* Coming down: the pegs out, the cover off, the poles down, folded up (packed in Tent_update). */
+static int workDown(Object *tent, float rate, float dt) {
+    int big = tent->variant;
+    float time = tent->tent_stage == TENT_LAID ? FOLD_TIME
+               : DOWN_QUICKER * (tent->tent_stage == TENT_POLES ? (big ? POLES_BIG : POLES_SMALL)
+                               : tent->tent_stage == TENT_COVER ? (big ? COVER_BIG : COVER_SMALL) : PEG_TIME);
+    tent->tent_work += dt * rate / time;
+    tent->tent_idle = 0.0f;
+    int done = -1;
+    if (tent->tent_work >= 1.0f) {
+        if (tent->tent_stage == TENT_LAID) { tent->tent_work = 1.0f; return TENT_DOWN_PACKED; } /* (Tent_update packs it) */
+        tent->tent_work = 0.0f;
+        if (tent->tent_stage == TENT_PEGS) {
+            if (--tent->stored <= 0) { tent->stored = 0; tent->tent_stage = TENT_COVER; done = TENT_DOWN + TENT_COVER; }
+        } else {
+            tent->tent_stage--;
+            done = TENT_DOWN + tent->tent_stage;
+        }
+    }
+    reshape(tent);
+    return done;
+}
+
 int Tent_work(Object *tent, Object *who, float dt) {
-    if (tent->type != OBJ_TENT || tent->tent_stage >= TENT_UP) return -1;
+    if (tent->type != OBJ_TENT || tent->broken || (tent->tent_stage >= TENT_UP && !tent->tent_down)) return -1;
     int big = tent->variant;
     float rate = (0.75f + 0.5f * who->wits) * (1.0f - 0.5f * fminf(who->drunk, 1.0f));
+    if (tent->tent_down) return workDown(tent, rate, dt);
     float time = tent->tent_stage == TENT_LAID ? LAY_TIME
                : tent->tent_stage == TENT_POLES ? (big ? POLES_BIG : POLES_SMALL)
                : tent->tent_stage == TENT_COVER ? (big ? COVER_BIG : COVER_SMALL) : PEG_TIME / (hasStone(who) ? 2.0f : 1.0f);
@@ -96,6 +143,12 @@ int Tent_work(Object *tent, Object *who, float dt) {
 }
 
 const char *Tent_job(const Object *tent) {
+    if (tent->tent_down) switch (tent->tent_stage) {
+    case TENT_LAID:  return "folding it up into its bag";
+    case TENT_POLES: return "taking the poles down";
+    case TENT_COVER: return "pulling the cover off";
+    default:         return "pulling the pegs out";
+    }
     switch (tent->tent_stage) {
     case TENT_LAID:  return "spreading it out";
     case TENT_POLES: return "putting the poles in";
@@ -107,7 +160,7 @@ const char *Tent_job(const Object *tent) {
 
 static Vec3 nextPeg(const Object *tent) {
     float x, z;
-    Shapes_tentPeg(tent->variant, tent->stored, &x, &z);
+    Shapes_tentPeg(tent->variant, tent->tent_down ? (tent->stored > 0 ? tent->stored - 1 : 0) : tent->stored, &x, &z);
     return vec3(x, 0.0f, z);
 }
 
@@ -131,8 +184,8 @@ Vec3 Tent_handsAt(const Object *tent, const Object *who, float clock) {
     float s = me.x < 0.0f ? -1.0f : 1.0f;
     switch (tent->tent_stage) {
     case TENT_LAID:  return local(tent, vec3(s * hw, 0.08f, clampf(me.z, -hl, hl)));
-    case TENT_POLES: return local(tent, vec3(s * hw * 0.7f, fmaxf(0.1f, 0.55f * h * tent->tent_work), clampf(me.z, -hl * 0.5f, hl * 0.5f)));
-    case TENT_COVER: return local(tent, vec3(s * 0.3f, h * 0.8f, -hl + 2.0f * hl * tent->tent_work));
+    case TENT_POLES: return local(tent, vec3(s * hw * 0.7f, fmaxf(0.1f, 0.55f * h * shownWork(tent)), clampf(me.z, -hl * 0.5f, hl * 0.5f)));
+    case TENT_COVER: return local(tent, vec3(s * 0.3f, h * 0.8f, -hl + 2.0f * hl * shownWork(tent)));
     case TENT_PEGS: {
         Vec3 p = nextPeg(tent);
         p.y = 0.06f + 0.14f * fabsf(sinf(clock * 7.0f)); /* (hitting it in) */
@@ -152,7 +205,7 @@ bool Tent_isTheirs(const Object *tent, const Object *who) {
 
 Object *Tent_of(Object *const *objects, int count, const Object *who) {
     for (int i = 0; i < count; i++)
-        if (objects[i]->type == OBJ_TENT && Tent_isTheirs(objects[i], who)) return objects[i];
+        if (objects[i]->type == OBJ_TENT && !objects[i]->broken && Tent_isTheirs(objects[i], who)) return objects[i];
     return NULL;
 }
 
@@ -188,12 +241,42 @@ void Tent_crawlOut(Object *who) {
     Object_syncColliders(who);
 }
 
+/* Folded up: its bag where it lay (left packed), the sleeping bags and mats that were in it at the door. */
+static void pack(World *world, Object *tent) {
+    const int big = tent->variant;
+    Object *bag = Gear_create(big ? GEAR_TENT_BIG : GEAR_TENT_SMALL, tent->owner, vec3_add(tent->position, vec3(0.0f, 0.02f, 0.0f)));
+    if (bag) {
+        bag->keep = true;
+        bag->rotation.y = tent->rotation.y;
+        Object_syncColliders(bag);
+        if (!World_add(world, bag)) Object_destroy(bag);
+    }
+    Object *sleeper[2] = { tent->owner, tent->owner2 };
+    for (int k = 0; k < 4; k++) {
+        if (!(tent->tent_beds & (1 << k)) || !sleeper[k / 2]) continue;
+        Vec3 at = local(tent, vec3((big ? (k / 2 ? 0.45f : -0.45f) : 0.0f) + (k % 2 ? 0.3f : -0.3f), 0.0f, TENT_L(big) * 0.5f * 1.14f + DOOR_OUT + 0.3f));
+        at.y = World_groundHeight(world, at.x, at.z, at.y + 0.5f) + 0.02f;
+        Object *thing = Gear_create(k % 2 ? GEAR_MAT : GEAR_SLEEPING_BAG, sleeper[k / 2], at);
+        if (!thing) continue;
+        thing->rotation.y = tent->rotation.y;
+        Object_syncColliders(thing);
+        if (!World_add(world, thing)) Object_destroy(thing);
+    }
+    tent->tent_beds = 0;
+    tent->owner = tent->owner2 = NULL;
+    Object_useUp(tent);
+}
+
 #define RAIN_NAP_END 0.15f /* rain: under this, out of the tent again */
 
 void Tent_update(World *world, float dt) {
     for (int i = 0; i < world->count; i++) {
         Object *o = world->objects[i];
-        if (o->type == OBJ_TENT) { o->tent_idle += dt; continue; }
+        if (o->type == OBJ_TENT) {
+            o->tent_idle += dt;
+            if (o->tent_down && !o->broken && o->tent_stage == TENT_LAID && o->tent_work >= 1.0f) pack(world, o);
+            continue;
+        }
         if (o->rain_nap && (!o->body.asleep || world->rain < RAIN_NAP_END)) { /* in out of the rain: up when it stops */
             o->rain_nap = false;
             if (o->body.asleep && o->needs.sleepiness < 0.6f) Body_getUp(&o->body);
diff --git a/entropie/src/world/tent.h b/entropie/src/world/tent.h
index 34cf5b9..493253b 100644
--- a/entropie/src/world/tent.h
+++ b/entropie/src/world/tent.h
@@ -18,6 +18,12 @@ Object     *Tent_unpack(World *world, Object *bag, Vec3 door_dir);
 /* Somebody working on it this frame (both hands free, at it): the next bit
    done. Returns the stage it got to when one was finished just now, else -1. */
 int         Tent_work(Object *tent, Object *who, float dt);
+/* Taking a tent down (anybody in it comes out): Tent_work then goes the other
+   way, returning TENT_DOWN + the stage it's down to (or TENT_DOWN_PACKED: folded
+   up; Tent_update packs it into its bag, left packed, its beds at the door). */
+#define TENT_DOWN        10
+#define TENT_DOWN_PACKED 20
+void        Tent_takeDown(Object *const *objects, int count, Object *tent);
 /* What working on it means now ("putting the poles in") and where the hands go. */
 const char *Tent_job(const Object *tent);
 Vec3        Tent_workSpot(const Object *tent, float side);  /* where to stand (side -1 / 0 / +1) */