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

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.60.0: folding ladder

Christian Immanuel · 2026-10-15 01:12 · 8f8aead874bde5dbca4275667cd696df52688047

- A folding ladder (two 2.5 m halves on a hinge, 5 m open) lies beside
  the telescopic one in the tool room
- One click at a wall unfolds it in the put-up animation and leans it
  there; elsewhere a click unfolds or folds it

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/README.md                 |  3 +++
 entropie/STATE.md                  |  8 ++++---
 entropie/commit_message.txt        | 15 ++++++++-----
 entropie/src/game/game_actions.c   |  9 +++++---
 entropie/src/render/shapes.h       |  5 +++++
 entropie/src/render/shapes_props.c | 46 ++++++++++++++++++++++++++++++++++++++
 entropie/src/world/ladder.c        | 20 ++++++++++-------
 entropie/src/world/ladder.h        |  2 ++
 entropie/src/world/object.c        | 11 ++++++---
 entropie/src/world/site.c          | 13 ++++++-----
 11 files changed, 105 insertions(+), 29 deletions(-)

diff --git a/entropie/Makefile b/entropie/Makefile
index 371d4a5..2ed03c0 100644
--- a/entropie/Makefile
+++ b/entropie/Makefile
@@ -1,5 +1,5 @@
 APP       := entropie
-VERSION   := 1.59.0
+VERSION   := 1.60.0
 
 PREFIX    ?= /usr
 BINDIR    ?= $(PREFIX)/bin
diff --git a/entropie/README.md b/entropie/README.md
index a606264..23241d4 100644
--- a/entropie/README.md
+++ b/entropie/README.md
@@ -317,6 +317,9 @@ A hand that can't reach its grip lets go and takes hold again.
 
 - A telescopic ladder (6 m out, 1.5 m together) lies in the tool room of
   your garages, on the floor along the end wall
+- Beside it lies a folding ladder (two 2.5 m halves on a hinge, 5 m open).
+  A click at a wall unfolds it and leans it there; elsewhere a click unfolds
+  or folds it
 - Ladder in hand: click at a wall and it slides out just as far as it takes
   (about 0.9 m over the edge) and leans there in one go: you stand it up,
   slide it out and tip it onto the wall (about a second); slid out by hand
diff --git a/entropie/STATE.md b/entropie/STATE.md
index 2ecdcf4..8945bf8 100644
--- a/entropie/STATE.md
+++ b/entropie/STATE.md
@@ -93,6 +93,8 @@ Talk topics: offer, ask, suggest, where (new woodpile spot), ask for a hand, ask
 
 Ladder: one click at a wall slides it out to fit (top on the edge, 0.9 m over it; up to its 6 m) and leans it; slid out by hand, it keeps that length. Putting it up is a 1.1 s animation (`ladder.c`, putUp): stood upright, slid out, tipped onto the wall; you stand still with both hands on it (`game.c`, puttingUp); it can only be climbed or taken once it leans.
 
+Folding ladder (`variant` LADDER_FOLDING, 9.8 kg so it carries one-handed): two 2.5 m sections on a hinge (`shapes_props.c`, Shapes_foldLadder: the upper section swings round the hinge by `extend`). It is either folded (2.5 m) or open (5 m, 18 rungs); leaning at a wall it always opens fully, the putUp animation swings the upper half up. `ladder.c` lengthFor / rung / rungCount pick per type.
+
 Raised ladder: slid out (extend goal > 0.5) and in hand, it's held 0.45 m up from its foot, the hand in front, the other hand higher up the rail, tipped up 60 deg in front (43-83 deg with the view pitch, eased 1.6 rad/s; `ladder.c` Ladder_placeHeld). A click leans it where you look (view ray, ladder length + 1.5 m; else a ray along the ladder, length + 0.8 m; a top face: the side towards you under that edge). Aimed within 0.45 m of the top it rests on the edge; lower, against the face at that height, flatter (no flatter than 55 deg: "aim at least X m up"); its foot at most 1.8 m from you. Looking down (view y < -0.6) a click slides it together; nothing aimed at: says so.
 
 Carrying long things (ladder, logs: 0.62 m and longer): they stop at people and the dog like at walls (unless you carry it together), push them out of the way (up to 25 cm a frame), knock light loose things aside; NPCs treat a long thing someone carries as in the way and walk round it (`world.c` pushOffCarried, `ai.c` ai_blockedBy).
@@ -107,7 +109,7 @@ Piling styles: how neatly someone stacks (`ai_stackCare`: firewood skill and lik
 
 Woodpile: firewood is stacked into the lowest free place (gaps left by pieces taken off are filled first); it counts what really lies on it; a piece that fell off is loose again (gets restacked).
 
-Bottles (beer, vodka, water), apples, bread, sausages (grill: done, dark, burnt), crates and boxes, logs, branches, firewood, stumps, woodpiles, fireplace, trash and garbage bags, the waste container, tables and garage furniture, stones, garden hose (a rope: falls, lies, drapes round things), watering can, telescopic ladder, axe, shears, chainsaw, trees (fruit trees too), bushes, cars, buildings.
+Bottles (beer, vodka, water), apples, bread, sausages (grill: done, dark, burnt), crates and boxes, logs, branches, firewood, stumps, woodpiles, fireplace, trash and garbage bags, the waste container, tables and garage furniture, stones, garden hose (a rope: falls, lies, drapes round things), watering can, telescopic and folding ladder, axe, shears, chainsaw, trees (fruit trees too), bushes, cars, buildings.
 
 ## World
 
@@ -146,7 +148,7 @@ Most important first.
 - Clothing (trousers, skirt, dress, fly) – needed for the above
 
 **Things**
-- More ladder types: a folding ladder (unfolds when put up) and a step ladder
+- More ladder types: a step ladder (stands on its own)
 - Hose near fire: melts, may burn; duct tape for small damage; burned through: shorter, or cut the bad part (knife, scissors) and join the ends; put the fire out with your drink (C)
 
 **Tools for us**
@@ -157,4 +159,4 @@ Most important first.
 - The walking map rebuild takes ~9 ms when fixed things change (a felled tree)
 
 ---
-State of `entropie 1.59.0 (build 1eab5b0)`
+State of `entropie 1.60.0 (build 6e3e5e9)`
diff --git a/entropie/commit_message.txt b/entropie/commit_message.txt
index 5bf0914..bdbb1ab 100644
--- a/entropie/commit_message.txt
+++ b/entropie/commit_message.txt
@@ -1,10 +1,13 @@
-entropie 1.59.0: stepping up onto blocks; set moves only for things on the ground
+entropie 1.60.0: folding ladder
 
-- Walking at a block above knee high up to about hip high with room on
-  top: you step up onto it, one leg after the other (no more knees
-  through its face)
-- Set moves and jumps only for things standing on the ground in front of
-  you (something lying up on a roof made people step over thin air)
+- A folding ladder (two 2.5 m halves on a hinge, 5 m open) lies beside
+  the telescopic one in the tool room
+- One click at a wall unfolds it in the put-up animation and leans it
+  there; elsewhere a click unfolds or folds it
+
+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
diff --git a/entropie/src/game/game_actions.c b/entropie/src/game/game_actions.c
index d29585d..e650ffa 100644
--- a/entropie/src/game/game_actions.c
+++ b/entropie/src/game/game_actions.c
@@ -357,15 +357,18 @@ static bool useLadder(Game *game) {
         int lean = Ladder_lean(&game->world, p, ladder, was_in, &why);
         if (was_in && lean != LEAN_DONE) { Ladder_setExtend(ladder, extend_was); ladder->extend_goal = 0.0f; }
         if (lean == LEAN_DONE) game->putting_up = ladder;
+        const bool fold = ladder->variant == LADDER_FOLDING;
         if (lean == LEAN_DONE)
-            say(game, ladder->lean_top > 0.0f ? (was_in ? "You slide the ladder out and lean it there - click it to climb"
+            say(game, ladder->lean_top > 0.0f ? (was_in ? (fold ? "You unfold the ladder and lean it there - click it to climb"
+                                                               : "You slide the ladder out and lean it there - click it to climb")
                                                         : "The ladder leans there - click it to climb")
                                               : "It leans there, but doesn't reach the top");
         else if (lean == LEAN_NO) say(game, why);
         else {
             Ladder_slide(ladder);
-            say(game, ladder->extend_goal > 0.5f ? "You slide the ladder out - click at a wall to lean it there"
-                                                 : "You slide the ladder together");
+            if (fold) say(game, ladder->extend_goal > 0.5f ? "You unfold the ladder - click where it should lean" : "You fold the ladder up");
+            else say(game, ladder->extend_goal > 0.5f ? "You slide the ladder out - click at a wall to lean it there"
+                                                      : "You slide the ladder together");
         }
         return true;
     }
diff --git a/entropie/src/render/shapes.h b/entropie/src/render/shapes.h
index 4cf1566..d086276 100644
--- a/entropie/src/render/shapes.h
+++ b/entropie/src/render/shapes.h
@@ -136,6 +136,11 @@ float Shapes_ladderLength(float extend);
 /* Height of rung k (0 = the lowest) along the ladder. */
 float Shapes_ladderRung(float extend, int k);
 Mesh  Shapes_ladder(float extend);
+/* The folding ladder (two hinged sections): unfold 0 = folded .. 1 = straight. */
+#define FOLD_RUNGS 9 /* per section */
+float Shapes_foldLadderLength(float unfold);
+float Shapes_foldLadderRung(int k);
+Mesh  Shapes_foldLadder(float unfold);
 
 /* A round bush, base at y = 0. */
 Mesh  Shapes_bush(float diameter, float height, unsigned seed);
diff --git a/entropie/src/render/shapes_props.c b/entropie/src/render/shapes_props.c
index 9844511..68edd06 100644
--- a/entropie/src/render/shapes_props.c
+++ b/entropie/src/render/shapes_props.c
@@ -872,6 +872,52 @@ float Shapes_ladderLength(float extend) {
     return Shapes_ladderRung(extend, LADDER_RUNGS - 1) + LADDER_CAP;
 }
 
+/* The folding ladder: two sections on a hinge. Folded, the upper one lies
+   along the lower (inside its rails, a little in front); unfolding, it
+   swings up and over to stand straight on top of it. */
+#define FOLD_PITCH   0.28f
+#define FOLD_SECTION (LADDER_FOOT + FOLD_PITCH * (float)(FOLD_RUNGS - 1) + 0.14f)
+#define FOLD_INSET   0.045f
+#define FOLD_FRONT   0.05f
+
+static float foldAngle(float unfold) { return PI_F * (1.0f - clampf(unfold, 0.0f, 1.0f)); }
+
+float Shapes_foldLadderLength(float unfold) {
+    return fmaxf(FOLD_SECTION, FOLD_SECTION * (1.0f + cosf(foldAngle(unfold))));
+}
+
+float Shapes_foldLadderRung(int k) {
+    return k < FOLD_RUNGS ? LADDER_FOOT + FOLD_PITCH * (float)k
+                          : FOLD_SECTION + LADDER_FOOT + FOLD_PITCH * (float)(k - FOLD_RUNGS);
+}
+
+/* One section along +Y from 0, rails half apart. */
+static void foldSection(MeshBuilder *b, float half, bool lower) {
+    const Vec3 alu = { 0.80f, 0.70f, 0.42f }, joint = { 0.22f, 0.20f, 0.18f }; /* (yellowish: tell it from the other one) */
+    for (int s = -1; s <= 1; s += 2) {
+        float x = (float)s * half;
+        addBlock(b, vec3(x, FOLD_SECTION * 0.5f, 0.0f), vec3(0.04f, FOLD_SECTION, 0.06f), alu);
+        if (lower) addBlock(b, vec3(x, 0.02f, 0.0f), vec3(0.06f, 0.04f, 0.07f), joint); /* rubber feet */
+        else addBlock(b, vec3(x, FOLD_SECTION - 0.015f, 0.0f), vec3(0.05f, 0.03f, 0.06f), joint); /* end caps */
+    }
+    for (int k = 0; k < FOLD_RUNGS; k++)
+        addBlock(b, vec3(0.0f, LADDER_FOOT + FOLD_PITCH * (float)k, 0.0f), vec3(2.0f * half, 0.03f, 0.045f), alu);
+}
+
+Mesh Shapes_foldLadder(float unfold) {
+    const Vec3 joint = { 0.22f, 0.20f, 0.18f };
+    const float half = LADDER_WIDTH * 0.5f;
+    MeshBuilder b;
+    MeshBuilder_init(&b);
+    foldSection(&b, half, true);
+    for (int s = -1; s <= 1; s += 2) /* the hinges */
+        addBlock(&b, vec3((float)s * (half - FOLD_INSET * 0.5f), FOLD_SECTION, FOLD_FRONT * 0.5f), vec3(0.07f, 0.08f, 0.1f), joint);
+    size_t first = b.vertex_count;
+    foldSection(&b, half - FOLD_INSET, false);
+    transformSince(&b, first, mat4_mul(mat4_translate(vec3(0.0f, FOLD_SECTION, FOLD_FRONT)), mat4_rotate_x(foldAngle(unfold))));
+    return Mesh_fromBuilder(&b);
+}
+
 Mesh Shapes_ladder(float extend) {
     const Vec3 alu = { 0.76f, 0.78f, 0.80f }, joint = { 0.20f, 0.20f, 0.22f };
     const float half = LADDER_WIDTH * 0.5f, top = Shapes_ladderLength(extend);
diff --git a/entropie/src/world/ladder.c b/entropie/src/world/ladder.c
index 55e1a85..68148c6 100644
--- a/entropie/src/world/ladder.c
+++ b/entropie/src/world/ladder.c
@@ -32,13 +32,16 @@ static const Vec3 UP = { 0.0f, 1.0f, 0.0f };
 static Vec3 flatv(Vec3 v) { return vec3(v.x, 0.0f, v.z); }
 static float flatDist(Vec3 a, Vec3 b) { return vec3_length(flatv(vec3_sub(a, b))); }
 
-static float lengthOf(const Object *l) { return Shapes_ladderLength(l->extend); }
+/* Two kinds: telescopic (variant 0: slides out) and folding (variant 1: two sections on a hinge, unfolds). */
+static bool folding(const Object *l) { return l->variant == LADDER_FOLDING; }
+static float lengthFor(const Object *l, float extend) { return folding(l) ? Shapes_foldLadderLength(extend) : Shapes_ladderLength(extend); }
+static float lengthOf(const Object *l) { return lengthFor(l, l->extend); }
 static Vec3 upAlong(const Object *l) { return l->colliders[0].axes[1]; }
 static Vec3 pointAt(const Object *l, float along) { return vec3_add(l->position, vec3_scale(upAlong(l), along)); }
 
 /* New mesh and collider for the current length. */
 static void reshape(Object *l) {
-    Mesh mesh = Shapes_ladder(l->extend);
+    Mesh mesh = folding(l) ? Shapes_foldLadder(l->extend) : Shapes_ladder(l->extend);
     if (mesh.vao) {
         Mesh_destroy(&l->mesh);
         l->mesh = mesh;
@@ -242,9 +245,9 @@ int Ladder_lean(World *world, Object *who, Object *l, bool fit, const char **why
             float mid = 0.5f * (lo + hi);
             if (Shapes_ladderLength(mid) < want) lo = mid; else hi = mid;
         }
-        extend_to = hi;
+        extend_to = folding(l) ? 1.0f : hi; /* (folding: all the way, it has no in between) */
     }
-    const float len = Shapes_ladderLength(extend_to);
+    const float len = lengthFor(l, extend_to);
     bool reaches = h <= len * sinf(angle) - 0.15f;
     /* aimed lower down the face (not at its top): against the face there, as high as it reaches */
     float against = len * sinf(angle) - 0.1f;
@@ -411,13 +414,14 @@ int Ladder_climb(Object *who, float input, float dt) {
 }
 
 /* Rung k's height along the ladder. */
-static float rung(const Object *l, int k) { return Shapes_ladderRung(l->extend, k); }
+static float rung(const Object *l, int k) { return folding(l) ? Shapes_foldLadderRung(k) : Shapes_ladderRung(l->extend, k); }
+static int rungCount(const Object *l) { return folding(l) ? (l->extend > 0.99f ? 2 * FOLD_RUNGS : FOLD_RUNGS) : LADDER_RUNGS; }
 
 /* The rung at or just above `along`. */
 static int rungAbove(const Object *l, float along) {
-    for (int k = 0; k < LADDER_RUNGS; k++)
+    for (int k = 0; k < rungCount(l); k++)
         if (rung(l, k) >= along) return k;
-    return LADDER_RUNGS - 1;
+    return rungCount(l) - 1;
 }
 
 /* A hand or foot going from rung to rung: it speeds up and slows down, and
@@ -473,7 +477,7 @@ void Ladder_poseFeet(Object *who, float dt) {
     for (int i = 0; i < 2; i++) {
         /* one foot a rung higher than the other, swapping as you climb */
         int kf = k + ((k + i) % 2 == 0 ? 0 : 1);
-        if (kf >= LADDER_RUNGS) kf = LADDER_RUNGS - 1;
+        if (kf >= rungCount(l)) kf = rungCount(l) - 1;
         float side = i == 0 ? 1.0f : -1.0f;
         Foot *foot = &who->body.feet[i];
         Vec3 goal = vec3_add(pointAt(l, rung(l, kf)), vec3_add(vec3_scale(across, side * 0.11f),
diff --git a/entropie/src/world/ladder.h b/entropie/src/world/ladder.h
index b4957c2..3670244 100644
--- a/entropie/src/world/ladder.h
+++ b/entropie/src/world/ladder.h
@@ -21,6 +21,8 @@ void        Ladder_setExtend(Object *ladder, float extend);
 /* Eases the sliding along, rebuilding mesh and collider as it goes. */
 void        Ladder_update(Object *ladder, float dt);
 
+#define LADDER_FOLDING 1 /* variant: two sections on a hinge (unfolds); 0: telescopic (slides out) */
+
 enum { LEAN_NOTHING, LEAN_DONE, LEAN_NO };
 /* Leans the ladder who carries against what's right in front. LEAN_NO sets why.
    fit: slides it out as far as it takes (over the edge a bit) first. */
diff --git a/entropie/src/world/object.c b/entropie/src/world/object.c
index 317ab6a..eb85ccd 100644
--- a/entropie/src/world/object.c
+++ b/entropie/src/world/object.c
@@ -954,9 +954,14 @@ void Object_describe(const Object *obj, char *out, size_t size) {
     }
     case OBJ_HOSE: snprintf(out, size, "Garden hose - left click (hold) to spray: washes away puddles and dirt"); break;
     case OBJ_LADDER:
-        snprintf(out, size, obj->leaning ? "Ladder - left click: climb (W / S),  E / Q: take it down"
-                          : obj->extend_goal > 0.5f ? "Ladder (slid out) - left click at a wall: lean it there, else slide it together"
-                          : "Ladder (slid together) - left click: slide it out, then lean it against something");
+        if (obj->variant == 1) /* (folding) */
+            snprintf(out, size, obj->leaning ? "Folding ladder - left click: climb (W / S),  E / Q: take it down"
+                              : obj->extend_goal > 0.5f ? "Folding ladder (unfolded) - left click where it should lean, look down: fold it"
+                              : "Folding ladder (folded) - left click at a wall: unfold it and lean it there");
+        else
+            snprintf(out, size, obj->leaning ? "Ladder - left click: climb (W / S),  E / Q: take it down"
+                              : obj->extend_goal > 0.5f ? "Ladder (slid out) - left click at a wall: lean it there, else slide it together"
+                              : "Ladder (slid together) - left click: slide it out, then lean it against something");
         break;
     case OBJ_STONE: snprintf(out, size, kg >= 1 ? "A stone - %d kg" : "A pebble", kg); break;
     case OBJ_CAN:
diff --git a/entropie/src/world/site.c b/entropie/src/world/site.c
index cce9a02..8ee92df 100644
--- a/entropie/src/world/site.c
+++ b/entropie/src/world/site.c
@@ -1,4 +1,5 @@
 #include "world/site.h"
+#include "world/ladder.h"
 
 #include <stdio.h>
 #include <SDL3/SDL.h>
@@ -723,17 +724,19 @@ static bool furnishGarages(World *world, Vec3 origin, float yaw, float zsign) {
         if (!o || !World_add(world, o)) return false;
     }
     /* The tools: on and by the workbench in the tool room */
-    struct { ObjectType type; Vec3 at, size, rot; float kg; } TOOLS[] = {
-        { OBJ_AXE,      { 26.5f, 1.0f, 12.0f }, { 0.0f, 0.8f, 0.0f }, { 90, 0, 0 }, 2.2f },
-        { OBJ_SHEARS,   { 26.5f, 1.0f, 13.2f }, { 0.0f, 0.3f, 0.0f }, { 90, 20, 0 }, 0.4f },
-        { OBJ_CHAINSAW, { 25.6f, 0.3f, 12.0f }, { 0.0f, 0.0f, 0.0f }, { 0, 60, 0 }, 6.0f },
-        { OBJ_LADDER,   { 20.9f, 0.1f, 13.55f }, { 0.0f, 0.0f, 0.0f }, { 90, 90, 0 }, 9.5f }, /* slid together, along the end wall */
+    struct { ObjectType type; Vec3 at, size, rot; float kg; int variant; } TOOLS[] = {
+        { OBJ_AXE,      { 26.5f, 1.0f, 12.0f }, { 0.0f, 0.8f, 0.0f }, { 90, 0, 0 }, 2.2f, 0 },
+        { OBJ_SHEARS,   { 26.5f, 1.0f, 13.2f }, { 0.0f, 0.3f, 0.0f }, { 90, 20, 0 }, 0.4f, 0 },
+        { OBJ_CHAINSAW, { 25.6f, 0.3f, 12.0f }, { 0.0f, 0.0f, 0.0f }, { 0, 60, 0 }, 6.0f, 0 },
+        { OBJ_LADDER,   { 20.9f, 0.1f, 13.55f }, { 0.0f, 0.0f, 0.0f }, { 90, 90, 0 }, 9.5f, 0 }, /* slid together, along the end wall */
+        { OBJ_LADDER,   { 23.2f, 0.1f, 13.55f }, { 0.0f, 0.0f, 0.0f }, { 90, 90, 0 }, 9.8f, LADDER_FOLDING }, /* folded, beside it */
     };
     for (unsigned i = 0; i < sizeof(TOOLS) / sizeof(TOOLS[0]); i++) {
         Vec3 pos = placed(TOOLS[i].at, WALLED_CENTER, origin, yaw, zsign);
         Object *o = Object_create(TOOLS[i].type, PALETTE_WARM, pos, TOOLS[i].size, TOOLS[i].kg);
         if (!o) return false;
         Object_setRotation(o, vec3(deg2rad(TOOLS[i].rot.x), deg2rad(TOOLS[i].rot.y) + yaw, deg2rad(TOOLS[i].rot.z)));
+        if (TOOLS[i].variant) { o->variant = TOOLS[i].variant; Ladder_setExtend(o, 0.0f); }
         if (!World_add(world, o)) return false;
     }
     return true;