junkers devlog 03 · @martinthebad
c64.jpg ─ □ ✕
thank you
bannerz.sys ─ □ ✕
libre culture open source it all make art encrypt everything junkerz
nav.exe ─ □ ✕
junkers devlog 03: parkour as a state machine

Phase 2: the move set

Wall-slide, wall-jump, an eight-direction dash, ledge-grab into a mantle, and a vault all became states in a plain enum and switch state machine. I went with that over function pointers for practical reasons. Every transition shows up in the debug overlay as text like state:WALL_SLIDE, it steps in gdb without a symbol lookup, and all the logic stays in one block I can read top to bottom.

The state machine and its enter hooks

enter_state is the one place that resets the timers and flags a state owns, so a new way into a state cannot forget to set them up.

static void enter_state(Player *p, PlayerState ns) {
    p->state = ns;
    switch (ns) {
    case PS_DASH:
        p->dash_available    = false;
        p->dash_timer        = PLR_DASH_DURATION;
        p->dash_buffer_timer = 0.0f;
        break;
    case PS_MANTLE: p->mantle_timer = PLR_MANTLE_DURATION; break;
    case PS_VAULT:  p->vault_timer  = PLR_VAULT_DURATION;  break;
    /* ... grounded / wall-slide / ledge-hang ... */
    }
}

Velocity is not set in the hook. It is worked out by the caller from context (the dash direction, the wall-jump kick, the vault arc), not from the destination state on its own.

Normalising the diagonal dash

The dash aims from the movement input. A raw diagonal with both axes at 1 would be about 1.41 times faster than a straight dash, so diagonals get scaled by 1/sqrt(2), about 0.707.

float dh = h, dv = v;
if (dh == 0.0f && dv == 0.0f) dh = (float)p->facing;            /* no aim, use facing */
if (dh != 0.0f && dv != 0.0f) { dh *= 0.707f; dv *= 0.707f; }   /* normalise diagonal */
p->vel_x = dh * PLR_DASH_SPEED;
p->vel_y = dv * PLR_DASH_SPEED;

The wall-jump applies a 0.13s input lockout (around 8 ticks) after the transition runs, zeroing the horizontal input long enough for the sideways kick to set up a trajectory. Without it, holding toward the wall cancels the jump and it feels sticky.

The vault arc

The vault is a scripted parabola. X moves linearly across the obstacle while Y follows a quadratic that peaks in the middle.

box.y = vault_start_y - vault_arc * 4 * frac * (1 - frac);   /* frac in [0,1] */

At frac = 0.5 the term 4 * 0.25 = 1.0, so the rise is exactly vault_arc px, and it is zero at both ends. Setting vault_arc to the obstacle height plus 8 leaves 8px of clearance over the lip.

The art so far

The player is still a debug rectangle in the running game. Sprite animation is the next real milestone (Phase 3b), but the animation itself is being worked out on paper.

asuka_sprite.png ─ □ ✕
Asuka sprite

Asuka, sprite work.

running.gif ─ □ ✕
running animation

Working out the running animation.

goggles.gif ─ □ ✕
goggles activate animation

Standing animation, goggles activating.

← movement and collision all posts next: camera and a bigger world →

Ignorance is death
gb.log
Guestbook
rollin.ini ─ □ ✕
solarpunk.bat ─ □ ✕
garden wizard