Nimbin[12]?Games / Entropie / commits / 55ef667

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.62.0: smooth pull-ups (no joint jumps climbing up)

Christian Immanuel · 2026-10-15 02:00 · 55ef667179280a4cb83f1526f41d652338aad341

- The climb ends exactly where you then stand (no hop at the end), each
  foot lands on what's under it, the feet stay put instead of replanting
- Feet come over the edge more slowly; the foot kept back from the face
  lets go gradually; knees ease out of the pull-up pose
- An elbow swinging up over an edge and a hand taking hold blend in
  smoothly (they flipped at half way)
- Audit walk (walks into a wall and climbs it): joint jumps 161 -> 58,
  the worst 22 -> 10 cm

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                |  6 +++---
 entropie/commit_message.txt      | 19 +++++++++++++------
 entropie/src/game/game_actions.c | 34 +++++++++++++++++++++++++++-------
 entropie/src/people/body.h       |  6 ++++--
 entropie/src/people/body_pose.c  | 34 +++++++++++++++++++++++-----------
 6 files changed, 71 insertions(+), 30 deletions(-)

diff --git a/entropie/Makefile b/entropie/Makefile
index 81a5ea7..2c5dc21 100644
--- a/entropie/Makefile
+++ b/entropie/Makefile
@@ -1,5 +1,5 @@
 APP       := entropie
-VERSION   := 1.61.0
+VERSION   := 1.62.0
 
 PREFIX    ?= /usr
 BINDIR    ?= $(PREFIX)/bin
diff --git a/entropie/STATE.md b/entropie/STATE.md
index 0737850..275e788 100644
--- a/entropie/STATE.md
+++ b/entropie/STATE.md
@@ -84,7 +84,7 @@ Talk topics: offer, ask, suggest, where (new woodpile spot), ask for a hand, ask
 - **Bushes**: trying to get over one ends in it, twigs snap
 - **Half way over something**: from when a foot is well into a step over something (lifted 12 cm+, 15 % into the step) until the body is over there (6 cm behind where that foot came down, at most 20 cm past it), you're crossing (`body.c` crossOver): stopping (or slower than 0.3 m/s) the body carries on over it at 0.6 m/s (`physics.c` carryOver); the foot over there stays down, the other comes after. Walking back takes you back. Standing still half over costs energy (2.5 %/s, a leg held up 6 %/s; less sporty, more); worn out, held longer than 1-6 s (fitness, energy) or legs over 0.95 m apart: you're made to go over; still stuck 1.5 s later: you fall (`world.c` halfOver)
 - **Sure-footedness**: one factor (`world.c`, unsure: ~0.2 sporty and careful .. ~1 average .. ~2.7 clumsy and unfit) scales every trip, slip, scrape and run-into, and how bad it gets; clumsy and unfit, you also stumble over your own feet jogging or running
-- **Climbing**: hand on it (up to 1.35 m), hang and pull up (higher, both hands free), jump for the edge; ladder up and down, over the edge at the top. Walking up to something 0.7–2 m high, the hands go onto its edge first; pulling up, the feet push against the face below the edge, then the right knee and foot come over onto it, then the left (scripted in `game_actions.c`, climbFeet); knees bend up. A foot below the edge goes back until its (forward bent) knee is 8 cm clear of the face (`game_actions.c`, kneeClear); onto something low (under 1 m) the body rises quickly first, so the knee comes up above the edge. Walking up to anything higher than 0.5 m, the feet stay 0.35 m back from it (room for the knee; `body.c`, footSpot). Climb test: 0.8-1.5 m, walking or jumping, legs at most 5 cm into it (was 19 cm at 0.8 m). High pull-ups: pulled up till the arms press down (hips near the edge), then the knee comes up and you rise; the hands go on ahead onto the top and stay there, the back bends over the edge as far as it takes to keep them on it (body_pose.c, reach_bend). Jumping: holding Space (or W) grabs an edge the hands reach (about 2.4 m); up to 0.35 m higher, the fingertips touch it ("Just out of reach"); hands go up wide, then onto the edge
+- **Climbing**: hand on it (up to 1.35 m), hang and pull up (higher, both hands free), jump for the edge; ladder up and down, over the edge at the top. Walking up to something 0.7–2 m high, the hands go onto its edge first; pulling up, the feet push against the face below the edge, then the right knee and foot come over onto it, then the left (scripted in `game_actions.c`, climbFeet); knees bend up. A foot below the edge goes back until its (forward bent) knee is 8 cm clear of the face (`game_actions.c`, kneeClear); onto something low (under 1 m) the body rises quickly first, so the knee comes up above the edge. Walking up to anything higher than 0.5 m, the feet stay 0.35 m back from it (room for the knee; `body.c`, footSpot). Climb test: 0.8-1.5 m, walking or jumping, legs at most 5 cm into it (was 19 cm at 0.8 m). High pull-ups: pulled up till the arms press down (hips near the edge), then the knee comes up and you rise; the hands go on ahead onto the top and stay there, the back bends over the edge as far as it takes to keep them on it (body_pose.c, reach_bend). Jumping: holding Space (or W) grabs an edge the hands reach (about 2.4 m); up to 0.35 m higher, the fingertips touch it ("Just out of reach"); hands go up wide, then onto the edge. Smooth pull-ups (audit walk: joint jumps 161 -> 58, the worst 22 -> 10 cm): the climb ends at the highest point under the body's footprint (where the physics stands you: no hop), each foot lands on what's under it, no replanting at the end (the feet are there already; body velocity zeroed: no stride ahead); feet over the edge over longer windows (lead 0.56-0.86, trail 0.74-1.0 of a high climb); kneeClear eases the foot back in 1 cm steps and lets go gradually as the knee comes over the top; the knee pole for pulling up eases in and out (`pull_knees`); an elbow swinging up over an edge blends between the two solves (no flip at half way); a hand taking hold eases out of the arm's "move out" (`arm_hold`)
 - **Drink and the rest**: sips, burps, sways, veers, sick, passes out, sleeps it off; pees (stream lands on the real ground, soaks in or puddles)
 - **Throwing**: underhand lob, overhand when wound up long enough
 - **Carrying one-handed**: hangs at the side by its top (a bag by its neck), wide things with the arm a little out; only long things (ladder, logs, axe) stop you at walls, bags and bottles squeeze by
@@ -135,7 +135,7 @@ A cell grid of the site (2 m cells, `src/world/grid.c`, built once a frame) answ
 Most important first.
 
 **Bugs**
-- Arms brushing past things while walking ease out of them at 2 m/s: a few more small joint jumps in the audit (walk 116 -> 161, the worst 21 cm; walking into a wall and climbing it is part of that run)
+- Joint jumps left in the audit: jog+stop 191, sprint 373 (worst 28 cm), ladder 163 (27 cm) - running into things and the ladder climb; walk is down to 58 (10 cm)
 
 **Body and moving**
 - Obstacle approach, still to do: prefer a step in between on top of something stable (or unstable, if clumsy) over a long stride; NPCs: pull up or jump onto things on their own (the player does when running at them)
@@ -160,4 +160,4 @@ Most important first.
 - The walking map rebuild takes ~9 ms when fixed things change (a felled tree)
 
 ---
-State of `entropie 1.61.0 (build e870d0c)`
+State of `entropie 1.62.0 (build 4e94661)`
diff --git a/entropie/commit_message.txt b/entropie/commit_message.txt
index 5ca9a15..b421ebf 100644
--- a/entropie/commit_message.txt
+++ b/entropie/commit_message.txt
@@ -1,10 +1,17 @@
-entropie 1.61.0: step ladder
+entropie 1.62.0: smooth pull-ups (no joint jumps climbing up)
 
-- A step ladder lies in the tool room, in front of the other two
-- A click opens it and stands it in front of you; with a wall just ahead
-  it stands with its back to it, and from its top you climb onto the wall
-- Climb it like the others (feet on the treads, hands on the top);
-  E / Q folds it up again
+- The climb ends exactly where you then stand (no hop at the end), each
+  foot lands on what's under it, the feet stay put instead of replanting
+- Feet come over the edge more slowly; the foot kept back from the face
+  lets go gradually; knees ease out of the pull-up pose
+- An elbow swinging up over an edge and a hand taking hold blend in
+  smoothly (they flipped at half way)
+- Audit walk (walks into a wall and climbs it): joint jumps 161 -> 58,
+  the worst 22 -> 10 cm
+
+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
diff --git a/entropie/src/game/game_actions.c b/entropie/src/game/game_actions.c
index 96d75a6..22c398e 100644
--- a/entropie/src/game/game_actions.c
+++ b/entropie/src/game/game_actions.c
@@ -86,6 +86,17 @@ static bool startClimb(Game *game, float top, Vec3 probe) {
     Vec3 to = vec3(probe.x + f.x * 0.25f * s, top, probe.z + f.z * 0.25f * s);
     to.y = World_groundHeight(&game->world, to.x, to.z, top + 0.6f * s); /* (a sloping roof goes on up) */
     if (to.y < top - 0.3f || to.y > top + 0.4f * s) to.y = top;
+    { /* (where the body will stand up there: the highest point under its footprint - no hop once up) */
+        const Collider *pc = &p->colliders[0];
+        float under = to.y;
+        for (int cx = -1; cx <= 1; cx += 2)
+            for (int cz = -1; cz <= 1; cz += 2) {
+                Vec3 q = vec3_add(to, vec3_add(vec3_scale(pc->axes[0], (float)cx * pc->half_extents.x), vec3_scale(pc->axes[2], (float)cz * pc->half_extents.z)));
+                float g = World_groundHeight(&game->world, q.x, q.z, to.y + 0.4f * s);
+                if (g > under && g < to.y + 0.3f * s) under = g;
+            }
+        to.y = under;
+    }
     game->climb_from = p->position;
     game->climb_to = to;
 
@@ -127,16 +138,19 @@ static bool startClimb(Game *game, float top, Vec3 probe) {
    until the knee is clear of it. */
 static float kneeClear(float along, float y, float root_along, float root_y, float face, float top, float s) {
     const float leg = KNEE_LEG * s, hip_a = root_along + 0.04f * s, hip_y = root_y + KNEE_HIP * s;
-    for (int k = 0; k < 14; k++, along -= 0.03f * s) {
-        float dx = along - hip_a, dy = y - hip_y, d = sqrtf(dx * dx + dy * dy);
+    float pushed = along, knee_y = top;
+    for (int k = 0; k < 42; k++, pushed -= 0.01f * s) {
+        float dx = pushed - hip_a, dy = y - hip_y, d = sqrtf(dx * dx + dy * dy);
         if (d < 1e-3f) continue;
         float h = d < 2.0f * leg ? sqrtf(leg * leg - 0.25f * d * d) : 0.0f;
         float px = dy / d, py = -dx / d; /* (perpendicular, the forward one) */
         if (px < 0.0f) { px = -px; py = -py; }
         float ka = hip_a + 0.5f * dx + px * h, ky = hip_y + 0.5f * dy + py * h;
-        if (ka < face - KNEE_GAP * s || ky > top + 0.04f * s) break;
+        if (k == 0) knee_y = ky;
+        if (ka < face - KNEE_GAP * s) break;
     }
-    return along;
+    /* (the knee up over the top already: no need to keep the foot back - eased off, no jump) */
+    return lerpf(pushed, along, smoothstepf(top - 0.08f * s, top + 0.06f * s, knee_y));
 }
 
 static void climbFeet(Game *game, Object *p, float t, bool high, float s) {
@@ -150,7 +164,7 @@ static void climbFeet(Game *game, Object *p, float t, bool high, float s) {
     for (int i = 0; i < 2; i++) {
         float sign = i == 0 ? -1.0f : 1.0f;
         bool lead = i == 1; /* (the right knee comes up first) */
-        float u = high ? (lead ? smoothstepf(0.7f, 0.86f, t) : smoothstepf(0.84f, 0.98f, t)) /* (once the hips are up near the edge) */
+        float u = high ? (lead ? smoothstepf(0.56f, 0.86f, t) : smoothstepf(0.74f, 1.0f, t)) /* (once the hips are up near the edge) */
                        : (lead ? smoothstepf(0.12f, 0.4f, t) : smoothstepf(0.5f, 0.85f, t));
         /* below: under you, but in front of the face, no higher than just under the edge */
         float along_below = fminf(root_along - 0.06f * s, face - (!high && top - ground < 1.0f * s && !lead ? LOW_BACK : 0.25f) * s); /* (the knees bend forward: room for them; low, the push-off foot further back) */
@@ -158,8 +172,13 @@ static void climbFeet(Game *game, Object *p, float t, bool high, float s) {
         /* on top: a little over the edge, or under you once you're up there */
         float along_top = fmaxf(face + 0.2f * s, root_along);
         float y_top = top + 0.08f * s;
+        { /* (on what's right under that foot up there - the top isn't always level) */
+            Vec3 q = vec3_add(a, vec3_add(vec3_scale(f, along_top), vec3_scale(side, sign * 0.11f * s)));
+            float g = World_groundHeight(&game->world, q.x, q.z, top + 0.3f * s);
+            if (g > top - 0.25f * s && g < top + 0.05f * s) y_top = g + 0.08f * s;
+        }
         /* up first (clear of the edge), then over */
-        float up = smoothstepf(0.0f, 0.55f, u), across = smoothstepf(0.45f, 1.0f, u);
+        float up = smoothstepf(0.0f, 0.6f, u), across = smoothstepf(0.35f, 1.0f, u);
         if (top - ground < 1.0f * s) { /* low: up and over in one swing (the knee comes over the edge) */
             up = smoothstepf(0.0f, 0.3f, u);
             across = smoothstepf(0.25f, 0.85f, u);
@@ -254,7 +273,8 @@ void game_climb(Game *game, const Input *in, float dt) {
         if (t >= 1.0f) {
             p->climbing = false;
             p->is_grounded = true;
-            p->body.replant = true; /* up there: the feet stand on it (not left down below, through it) */
+            /* (the feet are on it already - climbFeet - and stay put: no replanting, no jump) */
+            p->body.velocity = vec3_zero(); /* (standing there, not a stride ahead) */
         }
         return;
     }
diff --git a/entropie/src/people/body.h b/entropie/src/people/body.h
index 18dff96..83bf9f3 100644
--- a/entropie/src/people/body.h
+++ b/entropie/src/people/body.h
@@ -131,9 +131,10 @@ typedef struct {
     PushOutQuery push_out;   /* ... and solid things (arms keep out of them) */
     const void  *clutter_ctx;
     float sulk;              /* 0..1: annoyed (shows in the face; set by the world) */
-    float lifted[JOINT_COUNT];
+    float lifted[JOINT_COUNT]; /* down or sitting: how far each joint is lifted onto what it lies on (see Body_solve) */
     Vec3  arm_out[2];        /* standing: how far each arm is moved out of what it would be in (figure space, eases) ... */
-    float elbow_up[2];       /* ... and how far its elbow swings back over an edge its hand holds (0..1, eases) */        /* down or sitting: how far each joint is lifted onto what it lies on (see Body_solve) */
+    float elbow_up[2];       /* ... how far its elbow swings back over an edge its hand holds (0..1, eases) ... */
+    float arm_hold[2];       /* ... and whether its hand holds on there (0..1, eases: the hand isn't moved out with the arm) */
     Vec3  at;                /* where the body stands (last frame, world) ... */
     float at_scale;          /* ... and its size (height / reference height) */
     bool  limb_pole_ok[4];
@@ -164,6 +165,7 @@ typedef struct {
     bool  asleep;
     bool  replant;           /* plant the feet again next update */
     bool  pulling_up;        /* climbing up over an edge (on_ladder too): the legs still keep clear of it */
+    float pull_knees;        /* ... its knees up and out, eased (0..1) */
     float reach_bend, reach_bend_v; /* ... and the back bends over as far as it takes to keep the hands on it */
     float fitness;           /* how sporty (0..1): runs smoother, steps over things surer */
     float bob, bob_v;        /* running: the body's rise and fall with each stride (sprung, -1..1) */
diff --git a/entropie/src/people/body_pose.c b/entropie/src/people/body_pose.c
index b776a88..43611d8 100644
--- a/entropie/src/people/body_pose.c
+++ b/entropie/src/people/body_pose.c
@@ -518,6 +518,8 @@ static void solveStanding(Body *b, Vec3 position, Vec3 facing, float s, float dt
                                                      vec3_scale(out->head_forward, 0.015f)));
 
     /* ----- Legs: IK from the hips to the planted / stepping feet ----- */
+    /* (pulling up: knees up and out - eased in and out, so they don't swing over when it ends) */
+    b->pull_knees += clampf((b->pulling_up ? 1.0f : 0.0f) - b->pull_knees, -3.0f * dt, 3.0f * dt);
     for (int side = 0; side < 2; side++) {
         float sign = side == 0 ? -1.0f : 1.0f;
         int hip = side == 0 ? JOINT_HIP_L : JOINT_HIP_R;
@@ -542,7 +544,8 @@ static void solveStanding(Body *b, Vec3 position, Vec3 facing, float s, float dt
         Vec3 fd = vec3(tip.x - target.x, 0.0f, tip.z - target.z);
         fd = vec3_length(fd) > 0.02f ? vec3_normalize(fd) : vec3(0.0f, 0.0f, 1.0f);
         Vec3 pole = vec3_add(fd, vec3(sign * (0.06f - 0.3f * shy), 0.0f, 0.15f));
-        if (b->pulling_up) { pole.y += 0.8f; pole.x += sign * 0.35f; } /* (getting up onto something: the knee comes up, not into it - and not into the other) */
+        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]);
         j[hip + 3] = vec3_add(j[hip + 2], vec3_sub(tip, target)); /* foot keeps its shape if the leg can't reach */
     }
@@ -1282,7 +1285,7 @@ static void keepBodyOut(Body *b, Vec3 position, Vec3 facing, float s, float dt,
 
 static void keepArmsOut(Body *b, Vec3 position, Vec3 facing, float s, float dt, Skeleton *out) {
     if (b->pose != POSE_UP || !b->push_out) {
-        for (int k = 0; k < 2; k++) { b->arm_out[k] = vec3_zero(); b->elbow_up[k] = 0.0f; }
+        for (int k = 0; k < 2; k++) { b->arm_out[k] = vec3_zero(); b->elbow_up[k] = b->arm_hold[k] = 0.0f; }
         return;
     }
     Vec3 *j = out->joints;
@@ -1304,6 +1307,7 @@ static void keepArmsOut(Body *b, Vec3 position, Vec3 facing, float s, float dt,
         }
         /* holding: the elbow swings back, up and out (the forearm over the edge); else the arm moves out */
         float up_goal = holding && most > 0.0f ? 1.0f : 0.0f;
+        b->arm_hold[side] += clampf((holding ? 1.0f : 0.0f) - b->arm_hold[side], -6.0f * dt, 6.0f * dt);
         b->elbow_up[side] += clampf(up_goal - b->elbow_up[side], -4.0f * dt, 4.0f * dt);
         Vec3 goal = vec3_zero();
         if (!holding && most > 0.0f) {
@@ -1314,19 +1318,27 @@ static void keepArmsOut(Body *b, Vec3 position, Vec3 facing, float s, float dt,
         float dl = vec3_length(d);
         b->arm_out[side] = dl > step ? vec3_add(b->arm_out[side], vec3_scale(d, step / dl)) : goal;
         if (vec3_length(b->arm_out[side]) < 1e-4f && b->elbow_up[side] < 1e-3f) continue;
-        if (!holding) for (int k = 1; k <= 3; k++) j[sh + k] = vec3_add(j[sh + k], b->arm_out[side]);
-        else j[sh + 1] = vec3_add(j[sh + 1], b->arm_out[side]); /* (just taken hold: the hand stays, the elbow eases back in - no jump) */
+        /* (taking hold: the hand goes to where it holds, the elbow eases back in - both eased, no jump) */
+        Vec3 hand_out = vec3_scale(b->arm_out[side], 1.0f - b->arm_hold[side]);
+        j[sh + 1] = vec3_add(j[sh + 1], b->arm_out[side]);
+        j[sh + 2] = vec3_add(j[sh + 2], hand_out);
+        j[sh + 3] = vec3_add(j[sh + 3], hand_out);
         float l1 = UPPER_ARM * s, l2 = FOREARM * s;
         Vec3 tail = vec3_sub(j[sh + 3], j[sh + 2]);
         Vec3 pole = vec3_lerp(vec3_normalize(vec3_sub(j[sh + 1], j[sh])), vec3_normalize(vec3(sign * 0.45f, 0.6f, -0.65f)), b->elbow_up[side]);
-        Vec3 end;
-        if (b->elbow_up[side] > 0.5f) { /* (elbow up and back: its own way, folded or not) */
-            Vec3 mid;
-            twoBone(j[sh], j[sh + 2], l1, l2, pole, &mid, &end);
+        /* 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 < 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);
+            Vec3 u = vec3_sub(mid, j[sh]);
+            if (vec3_length(u) > 1e-4f) mid = vec3_add(j[sh], vec3_scale(u, l1 / vec3_length(u))); /* (bones keep their length) */
+            Vec3 v = vec3_sub(e, mid);
+            if (vec3_length(v) > 1e-4f) e = vec3_add(mid, vec3_scale(v, l2 / vec3_length(v)));
             j[sh + 1] = mid;
-            j[sh + 2] = end;
-        } else {
-            end = refitLimb(j, sh, l1, l2, pole);
+            j[sh + 2] = end = e;
         }
         float tl = vec3_length(tail);
         if (tl > 1e-4f) j[sh + 3] = vec3_add(end, vec3_scale(tail, HAND * s / tl));