Nimbin[12]?Games / Entropie / commits / 03beb3d

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.69.0: faster with a crowd (750 people: 22 -> 14 ms a frame)

Christian Immanuel · 2026-10-15 04:47 · 03beb3d62208e932357afbdc791e3daa21093df8

- The scans everybody does each frame (fire near, stink, sleepers, who's
  around) look only at people, fireplaces or stink, or ask the cell grid
- People more than 30 m from you don't check their feet and hands against
  things (nobody sees that far)

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               |  6 +++---
 entropie/commit_message.txt     | 12 +++++++++---
 entropie/src/people/ai.c        |  7 +++++--
 entropie/src/people/condition.c |  8 ++++++++
 entropie/src/people/condition.h |  4 ++++
 entropie/src/world/camp.c       |  8 ++++++--
 entropie/src/world/object.c     | 35 +++++++++++++++++++++++++++++++++++
 entropie/src/world/object.h     |  9 +++++++++
 entropie/src/world/world.c      | 15 +++++++++++++--
 10 files changed, 93 insertions(+), 13 deletions(-)

diff --git a/entropie/Makefile b/entropie/Makefile
index b3570f9..27c20b9 100644
--- a/entropie/Makefile
+++ b/entropie/Makefile
@@ -1,5 +1,5 @@
 APP       := entropie
-VERSION   := 1.68.0
+VERSION   := 1.69.0
 
 PREFIX    ?= /usr
 BINDIR    ?= $(PREFIX)/bin
diff --git a/entropie/STATE.md b/entropie/STATE.md
index 211b19d..31a6063 100644
--- a/entropie/STATE.md
+++ b/entropie/STATE.md
@@ -132,7 +132,7 @@ Ground: meadow wears into paths; tar, concrete, gravel and tracks are part of th
 
 ## Speed
 
-A cell grid of the site (2 m cells, `src/world/grid.c`, built once a frame) answers "what's near here" for the ground, legs, arms, physics and steering. World update per frame (no drawing): 5 people 0.5 ms, 150 people 3.2 ms, 300 people 7.8 ms (was 3.4 / 108 / ~215 ms).
+A cell grid of the site (2 m cells, `src/world/grid.c`, built once a frame) answers "what's near here" for the ground, legs, arms, physics and steering. World update per frame (no drawing): 5 people 0.5 ms, 150 people 3.2 ms, 300 people 7.8 ms (was 3.4 / 108 / ~215 ms). For the festival (750 people, -O2, crowded round the yard): 22.3 -> 14.2 ms - a per-frame index of the people, fireplaces and stink (`object.c` Object_indexKinds / Object_kind: valid for that objects array and count, else everything is looked at) for the scans everybody does (fire near, stink, sleepers, who's watching, scorching, grilling), who's around for the mood from the cell grid (`Condition_useGrid`), and people more than 30 m from the player (25 m to come back) don't keep their feet and hands out of things (`far_off`: no clutter / push-out questions, which were a third of the update). Memory: 383 MB with 5 people, 408 MB with 750. Drawing (software GL here, so only relative): 5 people 216 ms, 300 people 867 ms a frame - each figure is a card whose fragment shader draws the body, so it costs by how much of the screen they cover.
 
 ## Open tasks
 
@@ -156,7 +156,7 @@ Most important first.
 - Optional local AI (llama.cpp, small model) for talking and tasks; the menu stays as fallback
 
 **Scale**
-- Memory and speed for 750 people (the whole festival); drawing 300 figures not measured yet
+- 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.68.0 (build b9bdda1)`
+State of `entropie 1.69.0 (build 97ae07e)`
diff --git a/entropie/commit_message.txt b/entropie/commit_message.txt
index f274471..0e61efe 100644
--- a/entropie/commit_message.txt
+++ b/entropie/commit_message.txt
@@ -1,7 +1,13 @@
-entropie 1.68.0: walking map updated in place (0.3 ms instead of 10 ms)
+entropie 1.69.0: faster with a crowd (750 people: 22 -> 14 ms a frame)
 
-- When fixed things change (a tree felled, a ladder put up) only the
-  cells round them are worked out again; same result as a whole rebuild
+- The scans everybody does each frame (fire near, stink, sleepers, who's
+  around) look only at people, fireplaces or stink, or ask the cell grid
+- People more than 30 m from you don't check their feet and hands against
+  things (nobody sees that far)
+
+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
diff --git a/entropie/src/people/ai.c b/entropie/src/people/ai.c
index a33fd88..0c347ac 100644
--- a/entropie/src/people/ai.c
+++ b/entropie/src/people/ai.c
@@ -628,8 +628,10 @@ static bool findPeeSpot(Object *self, Object *const *objects, int count) {
     /* who to hide from: the others around */
     Vec3 crowd = vec3_zero();
     int seen = 0;
-    for (int i = 0; i < count; i++) {
-        const Object *o = objects[i];
+    int people = 0;
+    Object *const *everyone = Object_kind(KIND_CHARACTERS, objects, count, &people);
+    for (int i = 0; i < people; i++) {
+        const Object *o = everyone[i];
         if (o == self || !o->is_character || o->type == OBJ_DOG) continue;
         if (flatDistance(o->position, self->position) > CROWD_RANGE) continue;
         crowd = vec3_add(crowd, o->position);
@@ -739,6 +741,7 @@ static Vec3 chestOf(const Object *o) {
 
 /* Somebody sleeping it off for a while that nobody is waking yet. */
 static Object *findSleeper(const Object *self, Object *const *objects, int count) {
+    objects = Object_kind(KIND_CHARACTERS, objects, count, &count); /* (only people sleep) */
     for (int i = 0; i < count; i++) {
         Object *o = objects[i];
         if (o == self || !o->is_character || !o->body.asleep || o->body.pose != POSE_DOWN) continue;
diff --git a/entropie/src/people/condition.c b/entropie/src/people/condition.c
index 48fdd59..2ff36b9 100644
--- a/entropie/src/people/condition.c
+++ b/entropie/src/people/condition.c
@@ -1,5 +1,6 @@
 #include "people/condition.h"
 #include "world/object.h"
+#include "world/grid.h"
 
 #define SOBER_PER_HOUR   0.4f   /* drunkenness wearing off per game hour */
 #define SLEEP_SOBERING   5.0f   /* ... this many times faster while asleep */
@@ -121,6 +122,9 @@ static void needs(Object *self, float dt, float game_dt) {
 }
 
 /* Temperament, nudged by drink and company. */
+static struct Grid *g_grid;
+void Condition_useGrid(struct Grid *grid) { g_grid = grid; }
+
 static void showMood(Object *self, Object *const *objects, int count) {
     Mood m = self->temperament;
     float d = self->drunk;
@@ -136,6 +140,10 @@ static void showMood(Object *self, Object *const *objects, int count) {
 
     /* Somebody close: the shy shrink, the proud stand taller, everyone is a bit more alert */
     float nearest = 1e9f;
+    Object *near[256];
+    int found = g_grid ? Grid_nearObjects(g_grid, objects, count, self->position.x, self->position.z, COMPANY_DISTANCE, near, 256) : -1;
+    if (found >= 0) { objects = near; count = found; } /* (only who's around, from the cell grid) */
+    else objects = Object_kind(KIND_CHARACTERS, objects, count, &count);
     for (int i = 0; i < count; i++) {
         const Object *o = objects[i];
         if (o == self || !o->is_character) continue;
diff --git a/entropie/src/people/condition.h b/entropie/src/people/condition.h
index dd532d0..0752bac 100644
--- a/entropie/src/people/condition.h
+++ b/entropie/src/people/condition.h
@@ -10,6 +10,10 @@
 struct Object;
 
 /* Before the hands and the body are animated. */
+/* The cell grid to ask for who's around (or NULL: everyone is looked at). */
+struct Grid;
+void Condition_useGrid(struct Grid *grid);
+
 void Condition_update(struct Object *self, struct Object *const *objects, int count,
                       float dt, float game_dt);
 
diff --git a/entropie/src/world/camp.c b/entropie/src/world/camp.c
index 86498b5..9bf5a78 100644
--- a/entropie/src/world/camp.c
+++ b/entropie/src/world/camp.c
@@ -54,6 +54,7 @@ static void burn(Object *fire, Object *const *objects, int count, float game_dt)
 /* Someone walked into the fire, or is burning: rolling on the ground puts it out. */
 static void scorch(Object *who, Object *const *objects, int count, float dt) {
     if (who->on_fire <= 0.0f) {
+        objects = Object_kind(KIND_FIREPLACES, objects, count, &count);
         for (int i = 0; i < count; i++) {
             const Object *f = objects[i];
             if (f->type == OBJ_FIREPLACE && f->burning && flatDist(who->position, f->position) < CATCH_RADIUS(f) &&
@@ -154,8 +155,10 @@ Object *Camp_update(Object *const *objects, int count, float dt, float game_dt)
         if (fire->fuel <= 0.01f) fire->burning = false; /* burnt out */
         if (!fire->burning) continue;
         /* Food in a hand close over the fire */
-        for (int k = 0; k < count; k++) {
-            Object *who = objects[k];
+        int people = 0;
+        Object *const *who_all = Object_kind(KIND_CHARACTERS, objects, count, &people);
+        for (int k = 0; k < people; k++) {
+            Object *who = who_all[k];
             if (!who->is_character) continue;
             for (int h = 0; h < 2; h++) {
                 Object *food = who->hands.item[h];
@@ -172,6 +175,7 @@ Object *Camp_update(Object *const *objects, int count, float dt, float game_dt)
 
 Object *Camp_fireNear(Object *const *objects, int count, Vec3 pos, float range, bool burning_only) {
     Object *best = NULL;
+    objects = Object_kind(KIND_FIREPLACES, objects, count, &count);
     for (int i = 0; i < count; i++) {
         Object *o = objects[i];
         if (o->type != OBJ_FIREPLACE || (burning_only && !o->burning)) continue;
diff --git a/entropie/src/world/object.c b/entropie/src/world/object.c
index 9b838fd..23bf799 100644
--- a/entropie/src/world/object.c
+++ b/entropie/src/world/object.c
@@ -407,8 +407,43 @@ Object *Object_createDecal(Mesh mesh, Vec3 position) {
 
 #define STINK_RADIUS 2.2f /* how far a fresh puddle can be smelled */
 
+static Object       **g_kinds[KIND_COUNT];
+static int            g_kind_n[KIND_COUNT], g_kind_cap[KIND_COUNT];
+static Object *const *g_kind_src;
+static int            g_kind_count = -1;
+
+void Object_indexKinds(Object *const *objects, int count) {
+    g_kind_src = NULL;
+    for (int k = 0; k < KIND_COUNT; k++) {
+        g_kind_n[k] = 0;
+        if (g_kind_cap[k] < count) {
+            Object **grown = realloc(g_kinds[k], (size_t)(count + 64) * sizeof *grown);
+            if (!grown) return;
+            g_kinds[k] = grown;
+            g_kind_cap[k] = count + 64;
+        }
+    }
+    for (int i = 0; i < count; i++) {
+        Object *o = objects[i];
+        int k = o->is_character ? KIND_CHARACTERS : o->type == OBJ_FIREPLACE ? KIND_FIREPLACES : o->type == OBJ_STINK ? KIND_STINKS : -1;
+        if (k >= 0) g_kinds[k][g_kind_n[k]++] = o;
+    }
+    g_kind_src = objects;
+    g_kind_count = count;
+}
+
+Object *const *Object_kind(int kind, Object *const *objects, int count, int *n) {
+    if (objects && objects == g_kind_src && count == g_kind_count && kind >= 0 && kind < KIND_COUNT) {
+        *n = g_kind_n[kind];
+        return g_kinds[kind];
+    }
+    *n = count;
+    return objects;
+}
+
 float Object_stinkAt(Object *const *objects, int count, Vec3 pos) {
     float worst = 0.0f;
+    objects = Object_kind(KIND_STINKS, objects, count, &count);
     for (int i = 0; i < count; i++) {
         const Object *o = objects[i];
         if (o->type != OBJ_STINK || o->broken) continue;
diff --git a/entropie/src/world/object.h b/entropie/src/world/object.h
index 3e3cfd7..c0b90ad 100644
--- a/entropie/src/world/object.h
+++ b/entropie/src/world/object.h
@@ -171,6 +171,7 @@ typedef struct Object {
     bool       grilled;       /* food: done over the fire */
     bool       burnt;         /* food: left on too long, charcoal */
     bool       sitting;       /* characters: squatting, e.g. by the fire */
+    bool       far_off;       /* characters: far from the player - feet and hands don't look out for things */
     struct Object *seat;      /* characters: sitting on this (no collisions with it meanwhile) */
     Vec3       sit_at;        /* ... stepping over in front of it, turning round and back to here first, */
     Vec3       sit_dir;       /* ... facing this way */
@@ -326,6 +327,14 @@ Object *Object_createOnGround(ObjectType type, Palette palette, Vec3 position, V
 Object *Object_createDecal(Mesh mesh, Vec3 position);
 
 /* How bad it smells at pos (0 = fine .. 1 = right over something fresh). */
+/* Per-frame index of a few kinds that everybody looks for (the world builds it
+   at the start of each update; objects are only ever added, so it holds for that
+   array as long as the count is the same). */
+enum { KIND_CHARACTERS, KIND_FIREPLACES, KIND_STINKS, KIND_COUNT };
+void    Object_indexKinds(Object *const *objects, int count);
+/* Those of that kind in objects (*n of them) - or all of objects if not indexed for it. */
+Object *const *Object_kind(int kind, Object *const *objects, int count, int *n);
+
 float   Object_stinkAt(Object *const *objects, int count, Vec3 pos);
 
 /* Woodpiles: puts a quarter of firewood on the next place of the stack, as
diff --git a/entropie/src/world/world.c b/entropie/src/world/world.c
index 33d483f..4883aaa 100644
--- a/entropie/src/world/world.c
+++ b/entropie/src/world/world.c
@@ -545,6 +545,8 @@ static void overFeet(Object *o) {
     if (o->over_t >= 1.0f || o->over_t <= 0.0f) o->body.replant = false;
 }
 
+#define FAR_DETAIL 30.0f /* m from the player: beyond, feet and hands don't look out for things */
+
 /* Hands, feet and skeleton, then carried items follow the hands. */
 static void animateCharacter(World *world, Object *o, float dt) {
     Hands_update(o, dt);
@@ -568,8 +570,8 @@ static void animateCharacter(World *world, Object *o, float dt) {
         .dt         = dt,
         .ground     = groundQuery,
         .ground_ctx = world,
-        .clutter    = clutterQuery,
-        .push_out   = pushOutQuery,
+        .clutter    = o->far_off ? NULL : clutterQuery,  /* (far off nobody sees a foot on a log) */
+        .push_out   = o->far_off ? NULL : pushOutQuery,
     };
     Body_update(&o->body, &frame);
     Ladder_poseFeet(o, dt); /* ... and the feet */
@@ -814,6 +816,7 @@ static void handOutBag(World *world, Object *o) {
 }
 
 void World_update(World *world, float dt, float game_dt) {
+    Object_indexKinds(world->objects, world->count); /* (who's a person, where the fires are: looked up again and again) */
     for (int i = 0; i < world->count; i++) {
         Object *o = world->objects[i];
         if (o->type == OBJ_DOG) Dog_update(o, world->objects, world->count, dt);
@@ -827,6 +830,7 @@ void World_update(World *world, float dt, float game_dt) {
                         0.015f * (float)(o->work_state.jobs_done - o->jobs_seen));
             o->jobs_seen = o->work_state.jobs_done;
         }
+        Condition_useGrid(&world->grid);
         Condition_update(o, world->objects, world->count, dt, game_dt);
         if (o->type == OBJ_HUMAN) { Ai_useGrid(&world->grid); Ai_update(o, world->objects, world->count, dt); }
         if (o->type == OBJ_HUMAN && o->ai.wants_bag) handOutBag(world, o);
@@ -880,9 +884,16 @@ void World_update(World *world, float dt, float game_dt) {
     if (world->has_terrain) Nav_update(world->objects, world->count, &world->terrain);
 
     /* Walking characters move their legs and trample the meadow */
+    const Object *player = NULL;
+    for (int i = 0; i < world->count && !player; i++)
+        if (world->objects[i]->type == OBJ_PLAYER) player = world->objects[i];
     for (int i = 0; i < world->count; i++) {
         Object *o = world->objects[i];
         if (!o->is_character) continue;
+        if (player) { /* (far off: less detail; a bit of give either way, no flicking back and forth) */
+            float d = hypotf(o->position.x - player->position.x, o->position.z - player->position.z);
+            o->far_off = o->far_off ? d > FAR_DETAIL - 5.0f : d > FAR_DETAIL;
+        }
         takeKnock(o);
         takeBump(world, o, dt);
         halfOver(o, dt);