Nimbin[12]?Games / Entropie / commits / 306d09e

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 1.81.0: no stop-go walking, no flipping elbows

Christian Immanuel · 2026-10-15 16:40 · 306d09e8185116a33d1f195cf713131d7b0f27b2

- Fix: following someone on a chat break (and walking up to things) went
  walk-stop-walk every frame, the whole body shook; now they slow down
  coming up and stay with them
- Fix: an elbow could flip over when a hand went up past the shoulder
  (scratching the head); it turns round the arm now

Includes 1.80.0 (if not committed separately):
- An old tin by the fire as ashtray: you and the tidy put butts in there
- The Urinella is drawn while it's used; the dress is lifted higher
- After peeing you take up again what you put down
- The others can slip off a wobbly ladder too

Includes 1.79.0 (if not committed separately):
- Helpers (G) follow you up a leaning ladder onto a roof or wall and down
  again; without a ladder they pull themselves up after you (if they reach)
  and jump down when you're down
- Pulling up is a module for anybody now (people/climb.c), not only you

Includes 1.78.0 (if not committed separately):
- Cold nights: away from the fire a jacket goes on (each their own colour)
- Working hard in the midday sun your shirt comes off too
- Women who never tried a Urinella may try one: some like it, some don't
- Dying for a smoke makes restless; a wobbling ladder rocks
- Moving heavy wood: tested with a called helper; the player is asked when
  nobody else is around

Includes 1.77.0 (if not committed separately):
- Ladders: carried nearly upright when out; stood up on their own they
  fall over; leaned, how steady they stand depends on angle, ground, feet
  and what holds them; a wobbly one wobbles, then slips and falls with you
- Somebody holds it (hands on the rails, a foot on the bottom rung):
  helpers (G) when you climb, helpful people when it looks wobbly
- The work place: the tidy clear away what lies where they'd stand; no room
  to swing, they take the piece somewhere with room (carried, or dragged
  with somebody called to help)
- The sporty pull up and climb ladders much quicker

Includes 1.76.0 (if not committed separately):
- Smoking: packs and tobacco in the pockets (used up); smokers get a
  craving and light up in a quiet moment - rolling one first, the lighter
  to it, drags (the tip glows), smoke breathed out, the butt flicked away
- Shared: "Got a light?", bumming a cigarette (or a no); talk to ask for
  one or offer one; K lights yours / puts it out; Tab shows your pockets
- Shadows: soft, smooth edges instead of stair steps

Includes 1.75.0 (if not committed separately):
- Urinellas: one-time use, up to 4 in the pocket; a box by the toilet
  refills it (the others fetch more when they run out, you click it)
- Peeing: what's in the hands is put down first (the others take it up
  again); standing, the fly is open or the skirt or dress lifted
- Lighter: needed to light the fire, runs empty; borrowed when there's
  none, given back (or not), owners ask for it; talk: borrow, give back,
  ask for yours back
- Working hard in the midday sun the shirt comes off (women keep a top)
- Dropping from high up: knees pulled up, legs reach for the ground, deep
  into the knees, a roll or a crash
- Ladders: one-handed climbing (you may slip), a lying ladder is stepped
  over, kicked it hardly moves and your toe hurts
- Dev menu: teleport to the parkour / yard; the player spawns 1 m up

Includes 1.74.0 (if not committed separately):
- Fix: legs showed through a skirt or dress standing normally; the thighs
  under it are cloth now and it's wider over the hips
- Mila's Urinellas come in a pack of 3 (then she squats); now and then a
  mishap (on the shoes); used ones go into the bag, are dropped or flung
  away (drunk) as trash

Includes 1.73.0 (if not committed separately):
- Clothes: trousers, shorts, skirt, dress; squatting to pee trousers come
  down, a skirt or dress is gathered up

Includes 1.72.0 (if not committed separately):
- Fix: walking off the other way from something half stepped over left
  one foot planted behind, sliding along

Includes 1.71.0 (if not committed separately):
- Two-handed fidgets: rubbing the hands, hands behind the head, both
  hands on the hips

Includes 1.70.0 (if not committed separately):
- More one-hand gestures (neck, nose, ear, hair, thumb in the belt, hand
  on the chest, shading the eyes); mood and personal habits pick them

Includes 1.69.0 (if not committed separately):
- Faster with a crowd: 750 people 22 -> 14 ms a frame (kind index, cell
  grid for company, no foot checks far from you)

Includes 1.68.0 (if not committed separately):
- The walking map is updated in place when fixed things change
  (0.3 ms instead of 10 ms)

Includes 1.67.0 (if not committed separately):
- Pour your drink on the fire (C): a small fire goes out, a big one
  hisses, vodka flares up

Includes 1.66.0 (if not committed separately):
- Careful people sort the woodpile by length (front even) and put the
  branches lying about onto a heap at the end of the woodpile

Includes 1.65.0 (if not committed separately):
- Smooth off the ladder (a step back, one foot after the other) and
  taking it down (the hand reaches for the rail at chest height)

Includes 1.64.0 (if not committed separately):
- Smooth ladder and climbs: getting on, over the edge at the top, feet
  from where they are; the easy 1 m climb no longer drops you off again

Includes 1.63.0 (if not committed separately):
- The hose melts in the fire (leaks, burns through); duct tape and the
  shears mend it

Includes 1.62.0 (if not committed separately):
- Smooth pull-ups: no hop at the end, feet and knees ease over the edge,
  elbows and hands taking hold blend in (audit walk: joint jumps 161 -> 58)

Includes 1.61.0 (if not committed separately):
- A step ladder in the tool room: stands on its own, at a wall with its
  back to it; climb onto the wall from its top

Includes 1.60.0 (if not committed separately):
- A folding ladder (two 2.5 m halves on a hinge, 5 m open) beside the
  telescopic one; one click at a wall unfolds it and leans it there

Includes 1.59.0 (if not committed separately):
- Stepping up onto blocks above knee high; set moves and jumps only for
  things standing on the ground in front of you

Includes 1.58.0 (if not committed separately):
- Walking over knee-high things is one smooth set move that always
  finishes; at a run: jump up onto blocks, catch climbable edges
 entropie/Makefile                   |  2 +-
 entropie/STATE.md                   |  4 +++-
 entropie/commit_message.txt         |  9 ++++++++-
 entropie/src/people/ai.h            |  1 +
 entropie/src/people/ai_internal.h   |  5 +++--
 entropie/src/people/ai_work.c       | 11 ++++++++++-
 entropie/src/people/body_internal.h |  3 ++-
 entropie/src/people/body_pose.c     | 35 ++++++++++++++++++++++++++---------
 8 files changed, 54 insertions(+), 16 deletions(-)

diff --git a/entropie/Makefile b/entropie/Makefile
index cfd249d..46189d2 100644
--- a/entropie/Makefile
+++ b/entropie/Makefile
@@ -1,5 +1,5 @@
 APP       := entropie
-VERSION   := 1.80.0
+VERSION   := 1.81.0
 
 PREFIX    ?= /usr
 BINDIR    ?= $(PREFIX)/bin
diff --git a/entropie/STATE.md b/entropie/STATE.md
index 2e54e50..1bc1746 100644
--- a/entropie/STATE.md
+++ b/entropie/STATE.md
@@ -70,6 +70,8 @@ Climbing for anybody (`people/climb.c`, moved out of the game): `Climb_ledgeAhea
 
 Ashtray (FURN_ASHTRAY, an old tin by the fire, `stored` = butts): a finished cigarette within 4 m of it goes in if it's yours or the smoker is tidy (likes trash over 0.2 or pride over 0.4, under 0.6 drunk). The Urinella is drawn while it's used (skeleton `uri`, a funnel from the body to the spout; the stream starts at its end); standing with one, a skirt or dress is lifted higher in front. You take up again what you put down to pee (`ai.set_aside`, within 1.8 m). The others slip off wobbly ladders like you (`ai_climbing`: wobble, then the ladder falls sideways).
 
+Smoother: walking up to something (`goTo`) slows down as they get there (no full speed then stop, then on again); on a chat break they stay "close" until 1.6 x the chatting distance (was: one frame walking, one standing, after somebody walking slowly - the whole body shook). Arms: when the elbow's pole lies nearly along the arm (a hand going up past the shoulder) the bend leans on "out to the side" or "down and back", blended (`twoBone`, arms only), and an elbow moves at most 5.5 m/s (`JOINT_TOP_ELBOW`): no elbow flipping over (worst arm jump in the audit 58 -> 40 cm).
+
 Night clothes (`world.c` dressForNight): `chill` 0..1 grows 1/60 s at night (21:00-7:00) away from a burning fire (drunk: slower), falls by a fire (1/60 s) or in the day (2/60 s); over 0.6 the jacket (`look.top` TOP_JACKET, colour per person: `Figure_wardrobe`) goes on, under 0.15 off. Drawn a bit bulkier, long sleeves, a zip and a darker collar. The sun logic only takes off / puts on the shirt, never the jacket. You too (`game.c` sunShirt: working = a stroke in the last 3 s).
 
 Urinella, trying: a woman who never had one (`urinella_tried` 0), bladder 0.35-0.65, chance 0.1 + 0.2 energy per think: fetches some from the box (30 m). The first one used decides: like = 0.6 - 0.3 shyness - 0.2 (pride over 0.5) + 0.2 energy; not liked: back to squatting ("not for me"). Craving: dying for a smoke (0.8-1) adds up to 0.5 x smoker to the nerves shown. A wobble rocks the ladder (`shake`, 1.2 s). A heavy piece to drag and nobody to call: the player is asked ("grab it with F").
@@ -201,4 +203,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 1.80.0 (build ef616ba)`
+State of `entropie 1.81.0 (build 255293d)`
diff --git a/entropie/commit_message.txt b/entropie/commit_message.txt
index efae13e..b13057c 100644
--- a/entropie/commit_message.txt
+++ b/entropie/commit_message.txt
@@ -1,5 +1,12 @@
-entropie 1.80.0: ashtray, the Urinella shown, things back in the hand
+entropie 1.81.0: no stop-go walking, no flipping elbows
 
+- Fix: following someone on a chat break (and walking up to things) went
+  walk-stop-walk every frame, the whole body shook; now they slow down
+  coming up and stay with them
+- Fix: an elbow could flip over when a hand went up past the shoulder
+  (scratching the head); it turns round the arm now
+
+Includes 1.80.0 (if not committed separately):
 - An old tin by the fire as ashtray: you and the tidy put butts in there
 - The Urinella is drawn while it's used; the dress is lifted higher
 - After peeing you take up again what you put down
diff --git a/entropie/src/people/ai.h b/entropie/src/people/ai.h
index 12910cb..f3148fb 100644
--- a/entropie/src/people/ai.h
+++ b/entropie/src/people/ai.h
@@ -175,6 +175,7 @@ typedef struct {
     struct Object *prep_item;   /* ... what's being cleared away */
     struct Object *prep_helper; /* ... who was called over to help move it */
     float          prep_time;
+    bool           chat_near;   /* on a chat break: close enough (left again only well beyond) */
     float          hold_idle;
     float          ladder_dir;  /* on a ladder: +1 going up, -1 down */
     float          ladder_seek; /* until they look for a way up / down again */
diff --git a/entropie/src/people/ai_internal.h b/entropie/src/people/ai_internal.h
index b3b1f5c..6e98ce9 100644
--- a/entropie/src/people/ai_internal.h
+++ b/entropie/src/people/ai_internal.h
@@ -270,14 +270,15 @@ static inline void approach(Object *self, Topic topic) {
 
 /* Walks towards something; true once within reach of it. */
 static inline bool goTo(Object *self, const Object *target, Object *const *objects, int count, float reach, float dt) {
-    if (Hands_gapFrom(self->position, self->look.height, target) < reach) {
+    float gap = Hands_gapFrom(self->position, self->look.height, target);
+    if (gap < reach) {
         stand(self);
         turnTowards(self, vec3_sub(target->position, self->position), dt);
         return true;
     }
     Vec3 dir = ai_steer(self, target, objects, count, target->position);
     if (vec3_length(dir) < 0.5f) stand(self);
-    else ai_walk(self, dir, WALK_SPEED * 0.8f, dt);
+    else ai_walk(self, dir, fminf(WALK_SPEED * 0.8f, (gap - reach) * 2.5f + 0.25f), dt); /* (slowing down coming up to it: no stop-go) */
     return false;
 }
 
diff --git a/entropie/src/people/ai_work.c b/entropie/src/people/ai_work.c
index 5450d67..eb3fc64 100644
--- a/entropie/src/people/ai_work.c
+++ b/entropie/src/people/ai_work.c
@@ -885,7 +885,16 @@ static bool takeBreak(Object *self, Object *const *objects, int count, float dt)
     Object *other = ai->chat_with;
     if (other && (Body_isDown(&other->body) || flatDistance(other->position, self->position) > CHAT_RANGE * 1.5f)) other = ai->chat_with = NULL;
     if (other) { /* a chat: over to them, then a word now and then (they answer) */
-        if (!goTo(self, other, objects, count, CHAT_CLOSE, dt)) return true;
+        /* (over to them; walking along with them while they move - no stop-go at the edge of chatting distance) */
+        float gap = Hands_gapFrom(self->position, self->look.height, other);
+        ai->chat_near = gap < (ai->chat_near ? CHAT_CLOSE * 1.6f : CHAT_CLOSE);
+        if (!ai->chat_near) {
+            Vec3 dir = ai_steer(self, other, objects, count, other->position);
+            if (vec3_length(dir) < 0.5f) stand(self);
+            else ai_walk(self, dir, fminf(WALK_SPEED * 0.8f, (gap - CHAT_CLOSE) * 2.0f + 0.3f), dt);
+            return true;
+        }
+        stand(self);
         turnTowards(self, vec3_sub(other->position, self->position), dt);
         if ((ai->chat_line -= dt) > 0.0f) return true;
         ai->chat_line = CHAT_PAUSE * (0.8f + 0.4f * random01(ai));
diff --git a/entropie/src/people/body_internal.h b/entropie/src/people/body_internal.h
index 9a10701..5700efe 100644
--- a/entropie/src/people/body_internal.h
+++ b/entropie/src/people/body_internal.h
@@ -165,7 +165,8 @@ enum {
 #define BOB_NOD           0.12f  /* radians the unsporty nod forward with it */
 #define JOINT_TOP_HAND    14.0f  /* m/s (scaled, relative to the body) a hand can move at most (a throw) ... */
 #define JOINT_TOP_FOOT    12.0f  /* ... a foot (sprinting) ... */
-#define JOINT_TOP_MIDDLE  9.0f   /* ... a knee or elbow ... */
+#define JOINT_TOP_MIDDLE  9.0f   /* ... a knee ... */
+#define JOINT_TOP_ELBOW   5.5f   /* ... an elbow (it swings round the arm, never flips over) ... */
 #define JOINT_TOP_TRUNK   5.0f   /* ... hips, chest, head, shoulders */
 #define KNEE_GAP          0.13f  /* knees never come closer than this */
 
diff --git a/entropie/src/people/body_pose.c b/entropie/src/people/body_pose.c
index 5f397f8..cc688b2 100644
--- a/entropie/src/people/body_pose.c
+++ b/entropie/src/people/body_pose.c
@@ -18,7 +18,7 @@ static Vec3 nearestOnSegment(Vec3 a, Vec3 b, Vec3 p);
 #define FOLDED 0.3f /* a limb shorter than this much of its reach is folded right up ... */
 #define FOLD_BAND 0.12f /* ... and this much more: on its way there (blended) */
 
-static void twoBone(Vec3 root, Vec3 target, float l1, float l2, Vec3 pole, Vec3 *mid, Vec3 *end) {
+static void twoBone(Vec3 root, Vec3 target, float l1, float l2, Vec3 pole, Vec3 *mid, Vec3 *end, bool arm) {
     Vec3 to = vec3_sub(target, root);
     /* Soft near full stretch: the limb straightens smoothly instead of
        snapping straight (and back) when the target is just out of reach */
@@ -31,6 +31,22 @@ static void twoBone(Vec3 root, Vec3 target, float l1, float l2, Vec3 pole, Vec3
     float along = (l1 * l1 + dist * dist - l2 * l2) / (2.0f * dist);
     float side  = sqrtf(fmaxf(l1 * l1 - along * along, 0.0f));
     Vec3 bend = vec3_sub(pole, vec3_scale(dir, vec3_dot(pole, dir)));
+    /* The pole nearly along the limb (a hand right above the shoulder, going round behind the
+       head): its sideways part is tiny and swings wildly - lean on "out to the side" instead */
+    float clear = vec3_length(bend) / fmaxf(vec3_length(pole), 1e-5f);
+    if (arm && clear < 0.35f) {
+        /* (out to the side, or down and back - whichever is further off the limb, blended) */
+        Vec3 outward = vec3(root.x >= 0.0f ? 1.0f : -1.0f, -0.2f, -0.3f), downback = vec3(0.0f, -1.0f, -0.5f);
+        Vec3 a1 = vec3_sub(outward, vec3_scale(dir, vec3_dot(outward, dir)));
+        Vec3 a2 = vec3_sub(downback, vec3_scale(dir, vec3_dot(downback, dir)));
+        float w1 = vec3_dot(a1, a1), w2 = vec3_dot(a2, a2);
+        Vec3 alt = vec3_add(vec3_scale(a1, w1 * w1), vec3_scale(a2, w2 * w2));
+        if (vec3_length(alt) > 1e-4f) {
+            float keep = smoothstepf(0.05f, 0.35f, clear);
+            bend = vec3_add(vec3_scale(vec3_length(bend) > 1e-5f ? vec3_normalize(bend) : alt, keep),
+                            vec3_scale(vec3_normalize(alt), 1.0f - keep));
+        }
+    }
     bend = vec3_length(bend) > 1e-5f ? vec3_normalize(bend) : vec3(0, 0, 1);
 
     *mid = vec3_add(root, vec3_add(vec3_scale(dir, along), vec3_scale(bend, side)));
@@ -57,7 +73,7 @@ static Vec3 refitLimb(Vec3 *j, int r, float l1, float l2, Vec3 pole) {
     Vec3 kept_end = keepLengths(j, r, l1, l2), kept_mid = j[r + 1];
     if (w <= 0.0f) return kept_end;
     Vec3 mid, end;
-    twoBone(root, end_was, l1, l2, pole, &mid, &end);
+    twoBone(root, end_was, l1, l2, pole, &mid, &end, false);
     if (w >= 1.0f) {
         j[r + 1] = mid;
         j[r + 2] = end;
@@ -74,7 +90,7 @@ static Vec3 refitLimb(Vec3 *j, int r, float l1, float l2, Vec3 pole) {
 static void reachArm(Vec3 *j, int shoulder, Vec3 palm, Vec3 pole) {
     Vec3 reach = vec3_normalize(vec3_sub(palm, j[shoulder]));
     Vec3 wrist_goal = vec3_sub(palm, vec3_scale(reach, HAND));
-    twoBone(j[shoulder], wrist_goal, UPPER_ARM, FOREARM, pole, &j[shoulder + 1], &j[shoulder + 2]);
+    twoBone(j[shoulder], wrist_goal, UPPER_ARM, FOREARM, pole, &j[shoulder + 1], &j[shoulder + 2], true);
     j[shoulder + 3] = vec3_add(j[shoulder + 2], vec3_scale(vec3_normalize(vec3_sub(palm, j[shoulder + 2])), HAND));
 }
 
@@ -619,7 +635,7 @@ static void solveStanding(Body *b, Vec3 position, Vec3 facing, float s, float dt
         Vec3 pole = vec3_add(fd, vec3(sign * (0.06f - 0.3f * shy), 0.0f, 0.15f));
         pole.y += 0.8f * b->pull_knees;
         pole.x += sign * 0.35f * b->pull_knees; /* (getting up onto something: the knee comes up, not into it - and not into the other) */
-        twoBone(j[hip], target, THIGH, SHIN, pole, &j[hip + 1], &j[hip + 2]);
+        twoBone(j[hip], target, THIGH, SHIN, pole, &j[hip + 1], &j[hip + 2], false);
         j[hip + 3] = vec3_add(j[hip + 2], vec3_sub(tip, target)); /* foot keeps its shape if the leg can't reach */
     }
     /* Knees don't pass through each other (shy knees in, crossing steps) */
@@ -1134,7 +1150,7 @@ static void reLimb(Vec3 *j, int root, float l1, float l2, float l3, Vec3 fallbac
         *last_ok = true;
     }
     Vec3 m2, e2;
-    twoBone(r, e, l1, l2, pole, &m2, &e2);
+    twoBone(r, e, l1, l2, pole, &m2, &e2, false);
     j[root + 1] = m2;
     j[root + 2] = e2;
     float tl = vec3_length(tail);
@@ -1252,7 +1268,7 @@ static void clearLegs(Body *b, Vec3 position, Vec3 facing, float s, float dt, Sk
             for (int k = 1; k <= 9 && 0.1f * (float)k * s <= most_up; k++) {
                 float up = 0.1f * (float)k * s;
                 Vec3 a2 = vec3_add(ankle, vec3(0.0f, up, 0.0f)), k2, e2;
-                twoBone(hip, a2, THIGH * s, SHIN * s, vec3_sub(kn, hip), &k2, &e2);
+                twoBone(hip, a2, THIGH * s, SHIN * s, vec3_sub(kn, hip), &k2, &e2, false);
                 float d = legDepth(b, hip, k2, e2, s);
                 const float sole = e2.y - ANKLE_Y * s;
                 float q = b->clutter(b->clutter_ctx, e2.x, e2.z, 0.06f * s, sole - 0.05f); /* (and the sole over it) */
@@ -1267,7 +1283,7 @@ static void clearLegs(Body *b, Vec3 position, Vec3 facing, float s, float dt, Sk
         *raised = fminf(*raised, fmaxf(hip.y - LEG_RAISE_BELOW_HIP * s - ankle.y, 0.0f));
         if (*raised > 1e-3f) {
             Vec3 a2 = vec3_add(ankle, vec3(0.0f, *raised, 0.0f)), k2, e2;
-            twoBone(hip, a2, THIGH * s, SHIN * s, vec3_sub(kn, hip), &k2, &e2);
+            twoBone(hip, a2, THIGH * s, SHIN * s, vec3_sub(kn, hip), &k2, &e2, false);
             Vec3 toe = Figure_toWorld(position, facing, out->joints[hip_j + 3]);
             out->joints[hip_j + 1] = Figure_toLocal(position, facing, k2);
             out->joints[hip_j + 2] = Figure_toLocal(position, facing, e2);
@@ -1284,7 +1300,8 @@ static float topSpeedOf(int j) {
     switch (j) {
     case JOINT_HAND_L: case JOINT_HAND_R: case JOINT_WRIST_L: case JOINT_WRIST_R: return JOINT_TOP_HAND;
     case JOINT_ANKLE_L: case JOINT_ANKLE_R: case JOINT_TOE_L: case JOINT_TOE_R:    return JOINT_TOP_FOOT;
-    case JOINT_KNEE_L: case JOINT_KNEE_R: case JOINT_ELBOW_L: case JOINT_ELBOW_R:  return JOINT_TOP_MIDDLE;
+    case JOINT_KNEE_L: case JOINT_KNEE_R:                                            return JOINT_TOP_MIDDLE;
+    case JOINT_ELBOW_L: case JOINT_ELBOW_R:                                          return JOINT_TOP_ELBOW;
     default:                                                                         return JOINT_TOP_TRUNK;
     }
 }
@@ -1472,7 +1489,7 @@ static void keepArmsOut(Body *b, Vec3 position, Vec3 facing, float s, float dt,
         /* elbow up and back: its own way, folded or not - blended in from the usual fit (no flip half way) */
         const float own = smoothstepf(0.3f, 0.7f, b->elbow_up[side]);
         Vec3 own_mid = j[sh + 1], own_end = j[sh + 2], end = j[sh + 2];
-        if (own > 0.0f) twoBone(j[sh], j[sh + 2], l1, l2, pole, &own_mid, &own_end);
+        if (own > 0.0f) twoBone(j[sh], j[sh + 2], l1, l2, pole, &own_mid, &own_end, true);
         if (own < 1.0f) end = refitLimb(j, sh, l1, l2, pole);
         if (own > 0.0f) {
             Vec3 mid = vec3_lerp(j[sh + 1], own_mid, own), e = vec3_lerp(end, own_end, own);