Nimbin[12]?Games / Entropie / commits / 8b8b058

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.42.0: pavilions by the house, beer table sitting, Kurti shows you all the way

Christian Immanuel · 2026-10-28 00:09 · 8b8b05874defb3c00879c0be496c0a9999cbd145

- Pavilions turned 90 degrees and moved in front of the main building's
  right half, left of its porch
- Beer benches: you step over the bench and sit facing the table (turned
  away from it: back to the table as before); the others always face it
- Benches have evenly spaced places, so a third person always fits
- People no longer slide along the ground while seated when they set off
  (Kurti showing the way, project work, sharing an umbrella)
- Kurti calls everybody to the end of the garages by the barn, points up
  the path and explains the rest; then the marker shows your tent spot
- The chat at the fire waits until everybody has sat down
 commit_message.txt               | 27 +++++-------
 entropie/Makefile                |  2 +-
 entropie/README.md               |  4 +-
 entropie/STATE.md                | 28 ++++++++++---
 entropie/commit_message.txt      | 27 +++++-------
 entropie/src/game/game.h         |  4 +-
 entropie/src/game/game_arrival.c | 90 ++++++++++++++++++++++++++++++----------
 entropie/src/game/game_shop.c    |  2 +
 entropie/src/people/ai.c         |  5 ++-
 entropie/src/people/ai_project.c |  5 +--
 entropie/src/world/manor.c       |  1 +
 entropie/src/world/object.c      | 37 +++++++++++++----
 entropie/src/world/object.h      |  1 +
 entropie/src/world/site.c        |  8 ++--
 entropie/src/world/site.h        |  2 +
 15 files changed, 164 insertions(+), 79 deletions(-)

diff --git a/commit_message.txt b/commit_message.txt
index fbd34bf..0aab3e9 100644
--- a/commit_message.txt
+++ b/commit_message.txt
@@ -1,17 +1,12 @@
-entropie 2.41.0: materials and money, the start shows the way, pavilions, GPU rests
+entropie 2.42.0: pavilions by the house, beer table sitting, Kurti shows you all the way
 
-- Cash box (1200 EUR, saved, on the F2 projects page)
-- Building material: bundles of boards, nails, screws, paint (OBJ_MATERIAL).
-  Stage steps need it lying by them: deck and roof boards plus nails or
-  screws, decoration paint; used up when the step is done
-- Shopping run: ask whoever has the car keys (Volkram), pick the list; he
-  drives off and comes back an hour later, the crew unloads (material to a
-  store in front of the car room, food into the kitchen)
-- Food runs out now (food crate 40 portions, cool box 24 sausages)
-- Crew fetches material from the store to a stage that waits for it
-- Arrival: a golden marker over Kurti, the car, Kurti again; Kurti walks
-  you to your camping spot, then the marker shows your tent spot
-- Yard: the roof on posts replaced by two party pavilions with beer table sets
-- Kartoffelacker moved behind the road to the chicken farm, a strip of wood
-  right of it
-- GPU: vsync plus a 60 fps frame cap (it ran flat out before)
+- Pavilions turned 90 degrees and moved in front of the main building's
+  right half, left of its porch
+- Beer benches: you step over the bench and sit facing the table (turned
+  away from it: back to the table as before); the others always face it
+- Benches have evenly spaced places, so a third person always fits
+- People no longer slide along the ground while seated when they set off
+  (Kurti showing the way, project work, sharing an umbrella)
+- Kurti calls everybody to the end of the garages by the barn, points up
+  the path and explains the rest; then the marker shows your tent spot
+- The chat at the fire waits until everybody has sat down
diff --git a/entropie/Makefile b/entropie/Makefile
index 2b1095f..56b75f6 100644
--- a/entropie/Makefile
+++ b/entropie/Makefile
@@ -1,5 +1,5 @@
 APP       := entropie
-VERSION   := 2.41.0
+VERSION   := 2.42.0
 
 PREFIX    ?= /usr
 BINDIR    ?= $(PREFIX)/bin
diff --git a/entropie/README.md b/entropie/README.md
index 9953134..041205d 100644
--- a/entropie/README.md
+++ b/entropie/README.md
@@ -17,7 +17,7 @@ people living there.
   forest, roads. Painted in the rotoscope style of *Waking Life*
 - **Arriving:** by car with Volkram, Jana and Toni. Say hello to Kurti and
   his sons, unload the car, pitch the tents. A golden marker shows who or
-  what's next; Kurti walks you to your camping spot
+  what's next; Kurti shows you all the way up to the camping meadow
 - **People:** everyone has their own temperament, build, skills, walk and
   habits. They eat, drink, smoke, pee, sleep, work, chat and say no
 - **The festival:** the crew turns up over the first day. Four floors get
@@ -134,5 +134,5 @@ Dependencies point downward: `main → game → world/people → render → core
 
 ## Version
 
-`make version` prints e.g. `entropie 2.41.0 (build 6232605)`. The build id is
+`make version` prints e.g. `entropie 2.42.0 (build e597e7a)`. The build id is
 an md5 of all sources and assets.
diff --git a/entropie/STATE.md b/entropie/STATE.md
index 0ad72ab..cf3c317 100644
--- a/entropie/STATE.md
+++ b/entropie/STATE.md
@@ -341,11 +341,14 @@ 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)
+- Kurti's chat waits till everybody sits (or stands with the others) at the
+  fire - the last one in isn't left out
 - A golden diamond floats over who or what to go to next: Kurti (say hello,
   the chat), the car ("get your things from the car - then come to me"),
   Kurti again (with something of yours in your hands, or all of it out: he
-  walks you up to the meadow, waiting when you fall behind: "this is a good
-  spot for your tent"), then your tent's spot till it stands
+  calls everybody along to the end of your garages by the barn, waiting
+  when you fall behind; the others gather round; he points up the path and
+  says the rest), then your tent's spot till it stands
 - 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
@@ -426,9 +429,15 @@ Painted in the rotoscope style of *Waking Life* (2001):
   leans over the two of them
 - People get out of it: Kurti and his sons into the house, the others into
   their tent (at night, or when it's nearer), your garages' kitchen or the
-  two party pavilions in the yard (side by side by the fire, a beer table
-  set under each) - the nearest with a seat free. Raining hard, they sit it
+  two party pavilions in the yard (in a row in front of the main building's
+  right half, left of its porch; a beer table set under each) - the nearest
+  with a seat free. Raining hard, they sit it
   out there (else on the floor); out again when it stops
+- A beer bench (C): walking up to it you step over it and sit at the table,
+  facing it; turned away from it, you sit down with your back to the table.
+  The others always sit at the table. Benches have their places evenly
+  along them (three on the fire's benches and the beer benches): the third
+  always fits
 - Out in the rain your clothes get wet (darker; Tab: Wet) and you get cold.
   They dry by a fire quickest, in the day slower, at night slowest. Wet
   through, the others go and sit by a burning fire
@@ -1389,6 +1398,15 @@ Round 2.41 (materials and money; the start shows the way; the pavilions; the GPU
 - The GPU: vsync (adaptive if offered) and a frame cap of FRAME_CAP 60 (`platform.c` Platform_swap: never quicker than 1/60 s a frame) - it ran flat out before.
 - Checked: a run with 4 bundles, nails and screws, paint, food (245 EUR): walked over in 1 s, off, away, back, all 11 things unloaded in 4 min (store 7, kitchen 2 food boxes); Kilian fetched 2 bundles and nails to the Obstwiese and the deck got going; saved and loaded while away (back as it should); the start: marker on Kurti, the car, Kurti, the tent spot - Kurti walked you up there.
 
+Round 2.42 (the pavilions in front of the house; sitting at a beer table; Kurti shows you all the way up):
+- The pavilions: turned 90 degrees (in a row along the front) and moved in front of the main building's right half, left of its porch: court(SHELTER_A 10.2, SHELTER_B 3.8), 1.7 m off its wall.
+- Beer benches (`Object.table`: set in `Shelter_build`): `Object_sit` sits at the table, facing it - the others always, you when not turned away from the bench (`Object_facing` . to the bench > -0.2); from the far side, stepped over (`sit_backing` from the start: straight onto the spot, no turning round in front of it). NPCs walk up to it from behind (`rainRoom`). Its description says so.
+- Bench places (`freeAlong`): a bench's places are evenly along it (n = 2 half / SEAT_SPACE + 1, three on the fire's and the beer benches), the free one nearest where they come to it; sat anywhere, two left no room for a third.
+- Getting up: `ai_errand`, `ai_projectWork` and walking under somebody's umbrella "stood" a seated person without getting them up - they slid along the ground sitting (Kurti, showing the way). Now up first.
+- Kurti shows you all where to camp: with your things he calls everybody along (`guide`: `Arrival.guide_line`, `guide_t`) to `Site_campPath` (court(36, -11): the end of your garages by the barn, the foot of the path up), the three friends round him (`ai.errand`, no waiting); there (GATHER_NEAR 5.5 m, or after GATHER_WAIT 45 s) he faces up the path, points (TALK_POINT) and says three lines (GUIDE_LINE 3.8 s apart); then everybody is back to the day. `ai_errand` no longer drops what's carried (the shopping driver puts it down in `game_shopStart`).
+- The chat with Kurti waits till the three friends sit (or stand with the others) - the slowest (Volkram, the driver) was still walking up when it started and was left standing.
+- Checked: Jana and Volkram stepped over and sat facing the table, one each side; the start: all four at the foot of the path in 25 s, his three lines, then the tent spot marked; at the fire everybody sat before the chat (You, Toni, Kurti / Volkram, Jana, Martin / Jonas); hard rain: Volkram and Jana at the beer tables.
+
 ## Open tasks
 
 Most important first.
@@ -1432,4 +1450,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.41.0 (build 6232605)`
+State of `entropie 2.42.0 (build e597e7a)`
diff --git a/entropie/commit_message.txt b/entropie/commit_message.txt
index fbd34bf..0aab3e9 100644
--- a/entropie/commit_message.txt
+++ b/entropie/commit_message.txt
@@ -1,17 +1,12 @@
-entropie 2.41.0: materials and money, the start shows the way, pavilions, GPU rests
+entropie 2.42.0: pavilions by the house, beer table sitting, Kurti shows you all the way
 
-- Cash box (1200 EUR, saved, on the F2 projects page)
-- Building material: bundles of boards, nails, screws, paint (OBJ_MATERIAL).
-  Stage steps need it lying by them: deck and roof boards plus nails or
-  screws, decoration paint; used up when the step is done
-- Shopping run: ask whoever has the car keys (Volkram), pick the list; he
-  drives off and comes back an hour later, the crew unloads (material to a
-  store in front of the car room, food into the kitchen)
-- Food runs out now (food crate 40 portions, cool box 24 sausages)
-- Crew fetches material from the store to a stage that waits for it
-- Arrival: a golden marker over Kurti, the car, Kurti again; Kurti walks
-  you to your camping spot, then the marker shows your tent spot
-- Yard: the roof on posts replaced by two party pavilions with beer table sets
-- Kartoffelacker moved behind the road to the chicken farm, a strip of wood
-  right of it
-- GPU: vsync plus a 60 fps frame cap (it ran flat out before)
+- Pavilions turned 90 degrees and moved in front of the main building's
+  right half, left of its porch
+- Beer benches: you step over the bench and sit facing the table (turned
+  away from it: back to the table as before); the others always face it
+- Benches have evenly spaced places, so a third person always fits
+- People no longer slide along the ground while seated when they set off
+  (Kurti showing the way, project work, sharing an umbrella)
+- Kurti calls everybody to the end of the garages by the barn, points up
+  the path and explains the rest; then the marker shows your tent spot
+- The chat at the fire waits until everybody has sat down
diff --git a/entropie/src/game/game.h b/entropie/src/game/game.h
index e7d2054..ece0ad4 100644
--- a/entropie/src/game/game.h
+++ b/entropie/src/game/game.h
@@ -31,7 +31,9 @@ typedef struct {
     float    chat_wait;             /* ... since the hello */
     int      welcome;               /* Kurti and his sons welcoming you at the fire: lines said so far ... */
     float    welcome_t;             /* ... until the next */
-    int      guide;                 /* Kurti showing you where to camp: 0 not yet, 1 on the way, 2 shown */
+    int      guide;                 /* Kurti showing you all where to camp: 0 not yet, 1 on the way and telling, 2 shown ... */
+    int      guide_line;            /* ... his lines said so far ... */
+    float    guide_t;               /* ... since the last (or waiting for the others) */
 } Arrival;
 
 /* A shopping run (game_shop.c): over to the car, out along the way in, away, back, parked, the boot open. */
diff --git a/entropie/src/game/game_arrival.c b/entropie/src/game/game_arrival.c
index d3541b8..241f52e 100644
--- a/entropie/src/game/game_arrival.c
+++ b/entropie/src/game/game_arrival.c
@@ -159,8 +159,10 @@ static void welcome(Game *game, float dt) {
 
 /* ---------- Kurti shows you where to camp ---------- */
 
-#define GUIDE_NEAR  4.0f /* m: by Kurti with your things, he takes you there */
-#define GUIDE_THERE 6.0f /* m: you by him (he's there): shown */
+#define GUIDE_NEAR  4.0f  /* m: by Kurti with your things, he calls everybody over */
+#define GATHER_NEAR 5.5f  /* m from where he stands: there */
+#define GATHER_WAIT 45.0f /* s he waits there for the others (then he starts anyway) */
+#define GUIDE_LINE  3.8f  /* s between his lines */
 #define MARK_ABOVE  0.75f /* m: the marker over a head */
 
 static bool carrying(const Object *p) {
@@ -175,34 +177,74 @@ static bool yourThingsOut(const Game *game) {
     return true;
 }
 
-static void guide(Game *game) {
+static void sendTo(Object *o, Vec3 to, Object *with) {
+    if (Ai_isHelping(o)) Ai_stopHelping(o);
+    o->ai.mode = AI_IDLE;
+    o->ai.errand = true;
+    o->ai.errand_there = false;
+    o->ai.errand_to = to;
+    o->ai.errand_with = with;
+}
+
+static float flatGap(const Object *a, Vec3 b) { return hypotf(a->position.x - b.x, a->position.z - b.z); }
+
+/* Everybody to the foot of the path up behind your garages: Kurti shows the way up, says the rest. */
+static void guide(Game *game, float dt) {
+    static const char *const SHOW[] = {
+        "All here? Up this path - the meadow behind the garages is yours for the tents.",
+        "Pitch them where you like up there. The trees and bushes stay, mind.",
+        "Toilet's in the house, water in the kitchen. Make yourselves at home!",
+    };
+    const int lines = (int)(sizeof SHOW / sizeof SHOW[0]);
     Arrival *a = &game->arrival;
     if (a->task != TASK_UNLOAD || a->guide >= 2) return;
     Object *kurti = person(game, "Kurti"), *p = game->player;
     if (!kurti) { a->guide = 2; return; }
-    Vec3 spot = Site_campSpot(0);
+    Vec3 foot = Site_campPath(), up = vec3_scale(Car_campDoor(), -1.0f), side = vec3(-up.z, 0.0f, up.x);
     if (a->guide == 0) {
         if (!carrying(p) && !yourThingsOut(game)) return;
-        if (hypotf(kurti->position.x - p->position.x, kurti->position.z - p->position.z) > GUIDE_NEAR || p->riding) return;
-        Ai_say(kurti, "Got your things? Come with me - I'll show you where you can camp.");
+        if (flatGap(kurti, p->position) > GUIDE_NEAR || p->riding) return;
+        Ai_say(kurti, "Got your things? Everybody, come along - I'll show you where you camp!");
         a->guide = 1;
-        say(game, "Follow Kurti - he shows you where you can camp");
+        a->guide_line = 0;
+        a->guide_t = 0.0f;
+        say(game, "Follow Kurti - he shows you all where you can camp");
+    }
+    /* (off they go - again after loading): Kurti to the foot of the path, the others round him */
+    if (!kurti->ai.errand) sendTo(kurti, foot, p);
+    for (int k = 0; k < 3; k++) {
+        Object *o = person(game, FRIENDS[k]);
+        if (!o || o->ai.errand || o->riding) continue;
+        sendTo(o, vec3_add(foot, vec3_add(vec3_scale(up, -2.2f), vec3_scale(side, 1.4f * (float)(k - 1)))), NULL);
     }
-    if (!kurti->ai.errand) { /* (off he goes - again after loading) */
-        if (Ai_isHelping(kurti)) Ai_stopHelping(kurti);
-        kurti->ai.mode = AI_IDLE;
-        kurti->ai.errand = true;
-        kurti->ai.errand_there = false;
-        kurti->ai.errand_to = vec3_add(spot, vec3_scale(Car_campDoor(), CAMP_BEDS_OUT + 1.5f)); /* (in front of where the tent's door will be) */
-        kurti->ai.errand_with = p;
+    if (!kurti->ai.errand_there) return;
+    kurti->ai.errand_with = NULL; /* (there: no more looking back for you - up the path he looks, while he talks) */
+    float yaw = atan2f(up.z, up.x) + PI_F / 2.0f, turn = remainderf(yaw - kurti->rotation.y, 2.0f * PI_F);
+    kurti->rotation.y += clampf(turn, -3.0f * dt, 3.0f * dt);
+    bool all = flatGap(p, foot) < GATHER_NEAR;
+    for (int k = 0; k < 3 && all; k++) {
+        Object *o = person(game, FRIENDS[k]);
+        if (o && !Body_isDown(&o->body) && flatGap(o, foot) > GATHER_NEAR) all = false;
     }
-    if (kurti->ai.errand_there && hypotf(kurti->position.x - p->position.x, kurti->position.z - p->position.z) < GUIDE_THERE) {
-        Ai_say(kurti, "Here - this is a good spot for your tent. The others go up next to you.");
-        kurti->ai.errand = false;
-        kurti->ai.errand_with = NULL;
-        a->guide = 2;
-        say(game, "Your tent goes here - put it down and hold left click on it to put it up");
+    a->guide_t += dt;
+    if (a->guide_line == 0 && !all && a->guide_t < GATHER_WAIT) return; /* (waiting for the others) */
+    if (a->guide_line > 0 && a->guide_t < GUIDE_LINE) return;
+    if (a->guide_line < lines) {
+        Ai_say(kurti, SHOW[a->guide_line]);
+        if (a->guide_line == 0) Body_talkGesture(&kurti->body, TALK_POINT);
+        a->guide_line++;
+        a->guide_t = 0.0f;
+        return;
     }
+    /* said: on with the day */
+    kurti->ai.errand = false;
+    kurti->ai.errand_with = NULL;
+    for (int k = 0; k < 3; k++) {
+        Object *o = person(game, FRIENDS[k]);
+        if (o) o->ai.errand = false;
+    }
+    a->guide = 2;
+    say(game, "Up the path behind the garages: put your tent up on the meadow (hold left click on it)");
 }
 
 /* Your tent: standing, or where it's to go up. */
@@ -250,7 +292,7 @@ void game_arrival(Game *game, float dt) {
     Arrival *a = &game->arrival;
     Object *car = a->car;
     hostsStay(game);
-    guide(game);
+    guide(game, dt);
     welcome(game, dt);
     chat(game, dt);
     settled(game);
@@ -333,6 +375,10 @@ static void chat(Game *game, float dt) {
     a->chat_wait += dt;
     if (a->chat == 0) {
         bool sat = p->sitting && kurti && hypotf(kurti->position.x - p->position.x, kurti->position.z - p->position.z) < 5.0f;
+        for (int k = 0; k < 3 && sat; k++) { /* (and the others: sat down too, or standing with them - the last one isn't left out) */
+            const Object *o = person(game, FRIENDS[k]);
+            if (o && !o->sitting && !o->ai.greet_stand) sat = false;
+        }
         if (!sat && a->chat_wait < CHAT_WAIT) return;
         a->welcome_t = 0.0f;
     }
@@ -364,7 +410,7 @@ const char *game_task(Game *game) {
     case TASK_CHAT:   return "To do: sit down with them (C) and have a chat";
     case TASK_UNLOAD:
         if (a->guide == 0) return "To do: get your things from the car (left click at the boot), then over to Kurti - he shows you where to camp";
-        if (a->guide == 1) return "To do: follow Kurti - he shows you where you can camp";
+        if (a->guide == 1) return "To do: follow Kurti to the end of the garages - he shows you all where to camp";
         return "To do: put your tent up (hold left click on it), help the others empty the car";
     default:          return NULL;
     }
diff --git a/entropie/src/game/game_shop.c b/entropie/src/game/game_shop.c
index d739627..12a813e 100644
--- a/entropie/src/game/game_shop.c
+++ b/entropie/src/game/game_shop.c
@@ -49,7 +49,9 @@ void game_shopStart(Game *game, Object *driver, const int list[SHOP_ITEMS]) {
     e->stage = ERRAND_TO_CAR;
     if (Ai_isHelping(driver)) Ai_stopHelping(driver);
     driver->ai.mode = AI_IDLE;
+    for (int h = 0; h < 2; h++) if (driver->hands.item[h] && driver->hands.state[h] == HAND_HOLDING) Hands_drop(driver, h); /* (hands free to drive) */
     driver->ai.errand = true;
+    driver->ai.errand_with = NULL;
     driver->ai.errand_there = false;
     driver->ai.errand_to = Car_door(car, 0);
     game->world.shopping = true;
diff --git a/entropie/src/people/ai.c b/entropie/src/people/ai.c
index e712660..43569b0 100644
--- a/entropie/src/people/ai.c
+++ b/entropie/src/people/ai.c
@@ -2117,7 +2117,7 @@ static bool walkUnder(Object *self, Object *const *objects, int count, float dt)
     }
     h->umbrella_guest = self;
     h->umbrella_guest_t = 0.5f;
-    if (self->sitting) stand(self);
+    if (self->sitting) self->sitting = false; /* (up first) */
     Vec3 facing = Object_facing(h), right = vec3_normalize(vec3_cross(facing, UP));
     float side = h->hands.item[1] == u ? 1.0f : -1.0f; /* (the umbrella between them) */
     Vec3 spot = vec3_add(h->position, vec3_add(vec3_scale(right, side * SHARE_GAP * h->look.height / 1.75f), vec3_scale(facing, -0.05f)));
@@ -2187,7 +2187,8 @@ static bool rainRoom(Object *self, Object *const *objects, int count, float dt)
         Vec3 at = seat->position; /* (a chair: up to it; a bench: in front of it, where along it we are - not round its end, through a post) */
         float near = 0.9f;
         if (seat->variant == FURN_BENCH) {
-            Vec3 axis = seat->colliders[0].axes[0], front = vec3_sub(room, seat->position);
+            Vec3 axis = seat->colliders[0].axes[0], front = seat->table ? vec3_sub(seat->position, seat->table->position) /* (a beer bench: from behind it, over it) */
+                                                                         : vec3_sub(room, seat->position);
             float half = fmaxf(0.0f, 0.5f * seat->size.x - 0.4f), along = clampf(vec3_dot(vec3_sub(self->position, seat->position), axis), -half, half);
             front = flat(vec3_sub(front, vec3_scale(axis, vec3_dot(front, axis))));
             front = vec3_length(front) > 1e-3f ? vec3_normalize(front) : vec3(0.0f, 0.0f, 1.0f);
diff --git a/entropie/src/people/ai_project.c b/entropie/src/people/ai_project.c
index 1066a56..a9987da 100644
--- a/entropie/src/people/ai_project.c
+++ b/entropie/src/people/ai_project.c
@@ -192,9 +192,8 @@ static bool fetchMaterial(Object *self, Object *stage, Object *const *objects, i
 bool ai_errand(Object *self, Object *const *objects, int count, float dt) {
     Ai *ai = &self->ai;
     if (!ai->errand) return false;
-    if (self->sitting) stand(self);
+    if (self->sitting) self->sitting = false; /* (up first) */
     self->indoors = 0.0f;
-    for (int h = 0; h < 2; h++) if (self->hands.item[h] && self->hands.state[h] == HAND_HOLDING && !ai->errand_with) Hands_drop(self, h);
     const Object *w = ai->errand_with;
     if (w && !ai->errand_there && flatDistance(w->position, self->position) > GUIDE_WAIT) { /* (they're coming? waiting, looking back) */
         stand(self);
@@ -248,7 +247,7 @@ bool ai_projectWork(Object *self, Object *const *objects, int count, float dt) {
         }
     }
     if ((ai->shift += gdt) > SHIFT) return stop(self, lead ? "Break! Back in a bit." : NULL);
-    if (self->sitting) stand(self);
+    if (self->sitting) self->sitting = false; /* (up first) */
     if (fetchMaterial(self, stage, objects, count, dt)) return true;
     freeHands(self, NULL);
     float side = ai->side != 0.0f ? ai->side * 0.8f : (lead ? 0.0f : 0.6f);
diff --git a/entropie/src/world/manor.c b/entropie/src/world/manor.c
index d3c89b4..078ad1e 100644
--- a/entropie/src/world/manor.c
+++ b/entropie/src/world/manor.c
@@ -513,6 +513,7 @@ bool Shelter_build(World *world, Vec3 origin, float yaw) {
         for (int s = -1; s <= 1; s += 2) {
             Object *bench = Object_createFurniture(FURN_BENCH, toWorld(p, vec3(x, 0.0f, (float)s * 0.62f)), vec3(2.2f, SEAT_HEIGHT, 0.25f), yaw);
             if (!bench || !World_add(world, bench)) return false;
+            bench->table = table;
         }
     }
     return p->ok;
diff --git a/entropie/src/world/object.c b/entropie/src/world/object.c
index 989339e..cfc16c5 100644
--- a/entropie/src/world/object.c
+++ b/entropie/src/world/object.c
@@ -832,17 +832,29 @@ void Object_seePeople(Object *const *objects, int count) { g_people = objects; g
 
 /* Where along a bench or log (from its middle, axis along it) somebody can
    sit nearest to `want`, clear of everybody already on it; NAN: full. */
+static bool clearAt(const Object *who, const Object *seat, Vec3 c, Vec3 axis, float cand) {
+    for (int i = 0; i < g_people_n; i++) {
+        const Object *o = g_people[i];
+        if (o == who || !o->is_character || o->seat != seat || !o->sitting) continue;
+        if (fabsf(vec3_dot(vec3_sub(o->sit_at, c), axis) - cand) < SEAT_SPACE - 0.06f) return false; /* (a little give: moved up exactly that far) */
+    }
+    return true;
+}
+
 static float freeAlong(const Object *who, const Object *seat, Vec3 c, Vec3 axis, float want, float half) {
+    if (seat->type == OBJ_FURNITURE) { /* a bench: its places, evenly along it (sat anywhere, a third didn't fit) - the free one nearest */
+        int n = (int)(2.0f * half / SEAT_SPACE) + 1;
+        float gap = n > 1 ? 2.0f * half / (float)(n - 1) : 0.0f, best = NAN;
+        for (int k = 0; k < n; k++) {
+            float cand = n > 1 ? -half + gap * (float)k : 0.0f;
+            if (clearAt(who, seat, c, axis, cand) && (isnan(best) || fabsf(cand - want) < fabsf(best - want))) best = cand;
+        }
+        if (!isnan(best)) return best;
+    }
     for (int k = 0; k <= 2 * (int)(half / 0.1f) + 2; k++) {
         float cand = want + 0.1f * (float)((k + 1) / 2) * (k % 2 ? 1.0f : -1.0f);
         if (cand < -half || cand > half) continue;
-        bool clear = true;
-        for (int i = 0; i < g_people_n && clear; i++) {
-            const Object *o = g_people[i];
-            if (o == who || !o->is_character || o->seat != seat || !o->sitting) continue;
-            clear = fabsf(vec3_dot(vec3_sub(o->sit_at, c), axis) - cand) >= SEAT_SPACE - 0.06f; /* (a little give: moved up exactly that far) */
-        }
-        if (clear) return cand;
+        if (clearAt(who, seat, c, axis, cand)) return cand;
     }
     return NAN;
 }
@@ -993,6 +1005,14 @@ bool Object_sit(Object *who, Object *seat, Vec3 toward) {
         return true;
     }
     if (seat->variant == FURN_CHAIR && seatTaken(who, seat)) return noRoom(who);
+    bool over = false; /* a beer bench: at its table, stepped over it (the others always; you walking up to it, not turned away) */
+    if (seat->variant == FURN_BENCH && seat->table) {
+        Vec3 to_seat = vec3(seat->position.x - who->position.x, 0.0f, seat->position.z - who->position.z);
+        Vec3 to_table = vec3(seat->table->position.x - who->position.x, 0.0f, seat->table->position.z - who->position.z);
+        bool beyond = vec3_length(to_table) > vec3_length(to_seat); /* (we're on the far side of the bench from the table) */
+        bool facing = vec3_length(to_seat) < 1e-3f || vec3_dot(Object_facing(who), vec3_normalize(to_seat)) > -0.2f;
+        if (who->type != OBJ_PLAYER || facing) { toward = seat->table->position; over = beyond; }
+    }
     Vec3 n = seat->colliders[0].axes[2]; /* across the seat */
     Vec3 to = vec3_sub(toward, seat->position);
     if (seat->variant == FURN_CHAIR || vec3_dot(n, to) < 0.0f) n = seat->variant == FURN_CHAIR ? n : vec3_scale(n, -1.0f);
@@ -1020,7 +1040,7 @@ bool Object_sit(Object *who, Object *seat, Vec3 toward) {
     who->sit_yaw = atan2f(n.z, n.x) + PI_F / 2.0f; /* facing n */
     who->sit_dir = n;
     who->sit_moving = true;
-    who->sit_backing = false;
+    who->sit_backing = over; /* (over the bench: straight to the spot, no turning round in front of it) */
     who->velocity = vec3_zero();
     who->sit_style = SIT_SEAT;
     who->body.seat_height = SEAT_HEIGHT;
@@ -1481,6 +1501,7 @@ void Object_describe(const Object *obj, char *out, size_t size) {
         };
         if (obj->variant == FURN_ASHTRAY) { snprintf(out, size, "Ashtray (an old tin) - %d butts in it", obj->stored); break; }
         if (obj->variant == FURN_SMOKES) { snprintf(out, size, "Cigarette machine - click: a pack of %d", SMOKES_PACKS); break; }
+        if (obj->variant == FURN_BENCH && obj->table) { snprintf(out, size, "Beer bench - C: over it and sit at the table (turned away: your back to it)"); break; }
         snprintf(out, size, "%s", obj->variant >= 0 && obj->variant < FURN_COUNT && WHAT[obj->variant] ? WHAT[obj->variant] : "Furniture");
         break;
     }
diff --git a/entropie/src/world/object.h b/entropie/src/world/object.h
index b2c777c..759eac6 100644
--- a/entropie/src/world/object.h
+++ b/entropie/src/world/object.h
@@ -313,6 +313,7 @@ typedef struct Object {
     int        build_stage;   /* a stage (OBJ_STAGE): how far it's built (BuildStage) ... */
     float      build_work;    /* ... person-hours of work into the next bit */
     int        stock;         /* a bought food box (OBJ_SUPPLY): portions left (-1: empty); 0: never runs out (the scene's) */
+    struct Object *table;     /* a bench of a beer table set: its table (sat at facing it, stepped over) */
     float      commitment;    /* people: 0..1 how much they're into their part of the festival */
     int        leads, helps;  /* people: the Project they lead, one they help with (world/cast.h; PROJECT_NONE 0) */
     struct {                  /* dogs */
diff --git a/entropie/src/world/site.c b/entropie/src/world/site.c
index b07e61a..9d30cf0 100644
--- a/entropie/src/world/site.c
+++ b/entropie/src/world/site.c
@@ -315,8 +315,8 @@ static const CourtDoorBuilding COURT[] = {
 #define YARD_B   12.0f
 #define TAP_A     3.0f   /* the tap: on the right end of the main building */
 #define TAP_B    17.25f
-#define SHELTER_A 24.5f  /* the pavilions: two side by side, beer tables under them, by the fire towards the barn */
-#define SHELTER_B  3.5f
+#define SHELTER_A 10.2f  /* the pavilions: two side by side, beer tables under them, in front of the main building's */
+#define SHELTER_B  3.8f  /* right half, left of its porch (in a row along the front) */
 #define FIRE_A   16.0f   /* the fireplace: near the main building and your garages */
 #define FIRE_B   -4.0f
 #define BENCH_RADIUS 2.8f /* log benches round it */
@@ -1111,7 +1111,7 @@ bool Site_build(World *world) {
     if (!placePieces(world, WALLED, COUNT(WALLED), WALLED_CENTER, walled, wyaw, zsign) ||
         !placePieces(world, FRAME, COUNT(FRAME), FRAME_CENTER, court(FRAME_A, FRAME_B), across, 1.0f) ||
         !Shell_build(world, placed(SHELL_AT, FRAME_CENTER, court(FRAME_A, FRAME_B), across, 1.0f), across) ||
-        !Shelter_build(world, court(SHELTER_A, SHELTER_B), yaw_along(mapDir(COURT_ANGLE))) ||
+        !Shelter_build(world, court(SHELTER_A, SHELTER_B), yaw_along(mapDir(COURT_ANGLE - 90.0f))) ||
         !furnishGarages(world, walled, wyaw, zsign) || !hangGates(world, walled, wyaw, zsign))
         return false;
     if (!buildGarages(world) || !plantOrchard(world) || !plantMeadows(world) || !furnishYard(world)) return false;
@@ -1282,6 +1282,8 @@ static const struct { Project project; float a, b, front; } FLOORS[] = {
     { PROJECT_HIDDEN,        -20.0f,  -2.0f,  1.0f }, /* behind the main building, facing its back */
 };
 
+Vec3 Site_campPath(void) { return court(36.0f, -11.0f); } /* (the foot of the path up behind your garages: their end by the barn) */
+
 Vec3 Site_floorSpot(int project) {
     for (int k = 0; k < COUNT(FLOORS); k++)
         if ((int)FLOORS[k].project == project) return court(FLOORS[k].a, FLOORS[k].b);
diff --git a/entropie/src/world/site.h b/entropie/src/world/site.h
index ac9b991..1c799f5 100644
--- a/entropie/src/world/site.h
+++ b/entropie/src/world/site.h
@@ -33,6 +33,8 @@ Vec3  Site_materialSpot(void);
 Vec3 Site_rainRoom(void);
 /* Where a floor's stage stands (the floors: world/stage.h). */
 Vec3 Site_floorSpot(int project);
+/* The foot of the path up to the tents' meadow (the end of your garages by the barn). */
+Vec3 Site_campPath(void);
 /* Every place like that (the kitchen, the shelter in the yard): their middles, how many. */
 int  Site_rainPlaces(Vec3 *out, int max);
 Vec3  Site_campSpot(int k);