From 5bba7577857b771f7315e567707b5064e3f2b4e7 Mon Sep 17 00:00:00 2001 From: "local-model/engine-L2-grok-4.5" Date: Sat, 19 Sep 2026 13:03:33 +0200 Subject: [PATCH] engine-c: infantry/mutant walk bob via Unit.anim --- engine-c/src/render.c | 11 ++++++++++- engine-c/src/snapshot.c | 8 +++++++- engine-c/src/units.c | 22 +++++++++++++++++----- 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/engine-c/src/render.c b/engine-c/src/render.c index 34efde8..d85dd5f 100644 --- a/engine-c/src/render.c +++ b/engine-c/src/render.c @@ -249,6 +249,9 @@ static int live_unit_tracked(UnitType t) { static int live_unit_rolling(UnitType t) { return live_unit_wheeled(t) || live_unit_tracked(t); } +static int live_unit_footed(UnitType t) { + return t == UT_INFANTRY || t == UT_MUTANT; +} /* Spoke (wheeled) / hash (tracked) on tyre discs; phase = Unit.anim rad. * Local 30×30 chassis coords match snapshot LX/LY, scaled to live sz. */ @@ -333,8 +336,14 @@ void render_units(Game *g) { int cx = (int)(u->x - g->cam.x), cy = (int)(u->y - g->cam.y); /* Ground shadow before rotated body so units lift off sand. */ draw_unit_shadow(g->ren, cx, cy, sz / 4 + 4); + /* Foot infantry/mutant: vertical body bob; shadow stays grounded. */ + float bob_y = 0.0f; + if (live_unit_footed(u->type)) { + bob_y = sinf(u->anim) * 2.5f; + cy += (int)(bob_y + (bob_y >= 0.0f ? 0.5f : -0.5f)); + } SDL_Texture *tex = g->spr.unit[u->type][u->faction]; - draw_tex_world(g, tex, u->x, u->y, sz, sz, u->heading, 255); + draw_tex_world(g, tex, u->x, u->y + bob_y, sz, sz, u->heading, 255); /* rotating spokes / tread hashes (parity with snapshot tyre_mark) */ live_draw_tyre_marks(g->ren, u, cx, cy, sz); /* cream heading tick (parity with snapshot LX/LY overlay) */ diff --git a/engine-c/src/snapshot.c b/engine-c/src/snapshot.c index 4260bb5..0ca6aa7 100644 --- a/engine-c/src/snapshot.c +++ b/engine-c/src/snapshot.c @@ -130,7 +130,7 @@ static void draw_unit(Unit *u, int sx, int sy) { uint8_t mr = 70, mg = 65, mb = 58; /* tyre */ float c = cosf(u->heading), s = sinf(u->heading); const float sc = SHOT_UNIT_SCALE; - float ph = u->anim; /* continuous roll phase for wheeled/tracked */ + float ph = u->anim; /* continuous roll/walk phase for wheeled/tracked/footed */ #define SI(n) ((int)((n) * sc + 0.5f)) #define LX(lx, ly) (sx + (int)((((lx)-15)*sc)*c - (((ly)-15)*sc)*s)) #define LY(lx, ly) (sy + (int)((((lx)-15)*sc)*s + (((ly)-15)*sc)*c)) @@ -138,6 +138,12 @@ static void draw_unit(Unit *u, int sx, int sy) { /* Ground shadow — depth cue so bodies don't melt into sand. */ disc(sx + SI(2), sy + SI(3), SI(9), 55, 48, 28); + /* Foot infantry/mutant: vertical body bob; shadow stays grounded. */ + if (u->type == UT_INFANTRY || u->type == UT_MUTANT) { + float bob = sinf(ph) * 2.5f * sc; + sy += (int)(bob + (bob >= 0.0f ? 0.5f : -0.5f)); + } + switch (u->type) { case UT_INFANTRY: disc(sx, sy, SI(9), ok, og, ob); diff --git a/engine-c/src/units.c b/engine-c/src/units.c index 4ccc20f..2cd02dd 100644 --- a/engine-c/src/units.c +++ b/engine-c/src/units.c @@ -17,11 +17,20 @@ static int unit_tracked(UnitType t) { static int unit_rolling(UnitType t) { return unit_wheeled(t) || unit_tracked(t); } +/* Foot infantry/mutants: Unit.anim is continuous walk phase (rad) for body bob. */ +static int unit_footed(UnitType t) { + return t == UT_INFANTRY || t == UT_MUTANT; +} static void unit_roll_phase(Unit *u, float sp) { /* ~1 revolution per ~40 world px */ u->anim = fmodf(u->anim + (sp / 40.0f) * (float)(2.0 * M_PI), (float)(2.0 * M_PI)); if (u->anim < 0.0f) u->anim += (float)(2.0 * M_PI); } +static void unit_walk_phase(Unit *u, float sp) { + /* ~1 cycle per ~28 world px (faster bob than tyre roll) */ + u->anim = fmodf(u->anim + (sp / 28.0f) * (float)(2.0 * M_PI), (float)(2.0 * M_PI)); + if (u->anim < 0.0f) u->anim += (float)(2.0 * M_PI); +} Unit *unit_by_id(Game *g, int id) { for (int i = 0; i < g->unit_count; i++) @@ -353,8 +362,8 @@ static void unit_combat_step(Game *g, Unit *u, float dt) { particle_burst(g, mx, my, 3, 1, 255, 160, 50); u->atk_cd = ((u->type == UT_ARTILLERY) ? 2.2f : (u->type == UT_INFANTRY ? 0.8f : 1.0f)) / u->reload_mul; - /* rolling chassis keep continuous tyre phase — no fire pulse */ - if (!unit_rolling(u->type)) u->anim = 1.0f; + /* rolling/footed keep continuous phase — no fire pulse */ + if (!unit_rolling(u->type) && !unit_footed(u->type)) u->anim = 1.0f; audio_sfx(1); } return; @@ -423,7 +432,7 @@ static void unit_harvester_step(Game *g, Unit *u, float dt) { int add = (d->cargo - u->cargo > take) ? take : (int)(d->cargo - u->cargo); u->cargo += add; map_deplete_scrap(&g->map, tx, ty, add); - if (!unit_rolling(u->type)) u->anim = 1.0f; + if (!unit_rolling(u->type) && !unit_footed(u->type)) u->anim = 1.0f; } if (u->cargo >= d->cargo) { int rid = harvester_find_refinery(g, u); @@ -523,8 +532,9 @@ void unit_update(Game *g, float dt) { for (int i = 0; i < g->unit_count; i++) { Unit *u = &g->units[i]; if (u->dead) continue; - /* infantry/pulse anim decays; wheeled/tracked keep phase radians */ - if (!unit_rolling(u->type) && u->anim > 0) u->anim -= dt * 3; + /* pulse anim decays; wheeled/tracked/footed keep phase radians */ + if (!unit_rolling(u->type) && !unit_footed(u->type) && u->anim > 0) + u->anim -= dt * 3; if (u->repath_timer > 0) u->repath_timer -= dt; if (u->ability_cd > 0) u->ability_cd -= dt; @@ -596,6 +606,7 @@ void unit_update(Game *g, float dt) { u->x += (wx - u->x) / d * sp; u->y += (wy - u->y) / d * sp; if (unit_rolling(u->type)) unit_roll_phase(u, sp); + else if (unit_footed(u->type)) unit_walk_phase(u, sp); else u->anim = 1.0f; } } @@ -609,6 +620,7 @@ void unit_update(Game *g, float dt) { u->x += (u->dest_x - u->x)/d * sp; u->y += (u->dest_y - u->y)/d * sp; if (unit_rolling(u->type)) unit_roll_phase(u, sp); + else if (unit_footed(u->type)) unit_walk_phase(u, sp); else u->anim = 1.0f; } }