Add height zones, jetpack boost particles, and TigerStyle guidelines

Level generator: add vertical variety with tall levels (46 tiles).
Segment generators accept ground_row parameter, SEG_CLIMB connects
height zones, transitions inherit predecessor ground row to prevent
walkability gaps. Climb segments respect traversal direction.

Jetpack boost: add blue flame particles during dash (burst + trail)
and continuous idle glow from player back while boost timer is active.

Camera: add 30px vertical look-ahead when player velocity exceeds
50 px/s.

Fix flame vent pedestal in gen_pit to use ground-relative position
instead of map bottom (broken in tall HIGH-zone levels).

Add TigerStyle coding guidelines to AGENTS.md adapted for C11.
Add tall_test.lvl (40x46) for height zone validation.
This commit is contained in:
Thomas
2026-03-01 14:57:53 +00:00
parent 9d828c47b1
commit ad2d68a8b4
8 changed files with 813 additions and 192 deletions

View File

@@ -9,7 +9,7 @@ void camera_init(Camera *c, float vp_w, float vp_h) {
c->bounds_max = vec2(vp_w, vp_h); /* default: one screen */
c->smoothing = 5.0f;
c->deadzone = vec2(30.0f, 20.0f);
c->look_ahead = vec2(40.0f, 0.0f);
c->look_ahead = vec2(40.0f, 30.0f);
c->zoom = 1.0f;
}
@@ -28,6 +28,10 @@ void camera_follow(Camera *c, Vec2 target, Vec2 velocity, float dt) {
if (velocity.x > 10.0f) desired.x += c->look_ahead.x;
else if (velocity.x < -10.0f) desired.x -= c->look_ahead.x;
/* Vertical look-ahead: lead camera when falling or rising */
if (velocity.y > 50.0f) desired.y += c->look_ahead.y;
else if (velocity.y < -50.0f) desired.y -= c->look_ahead.y;
/* Smooth follow using exponential decay */
float t = 1.0f - expf(-c->smoothing * dt);
c->pos = vec2_lerp(c->pos, desired, t);

View File

@@ -301,6 +301,137 @@ void particle_emit_jetpack_trail(Vec2 pos, Vec2 dash_dir) {
particle_emit(&smoke);
}
void particle_emit_jetpack_boost_burst(Vec2 pos, Vec2 dash_dir) {
/* Blue flame accents mixed into the regular jetpack burst */
float exhaust_angle = atan2f(-dash_dir.y, -dash_dir.x);
/* Hot blue core — bright electric blue */
ParticleBurst core = {
.origin = pos,
.count = 10,
.speed_min = 90.0f,
.speed_max = 220.0f,
.life_min = 0.15f,
.life_max = 0.35f,
.size_min = 2.0f,
.size_max = 4.5f,
.spread = 0.4f,
.direction = exhaust_angle,
.drag = 2.5f,
.gravity_scale = 0.05f,
.color = {80, 160, 255, 255}, /* electric blue */
.color_vary = true,
};
particle_emit(&core);
/* Outer blue-white flare */
ParticleBurst flare = {
.origin = pos,
.count = 8,
.speed_min = 40.0f,
.speed_max = 130.0f,
.life_min = 0.2f,
.life_max = 0.4f,
.size_min = 1.5f,
.size_max = 3.0f,
.spread = 0.7f,
.direction = exhaust_angle,
.drag = 3.0f,
.gravity_scale = 0.1f,
.color = {140, 200, 255, 255}, /* light blue */
.color_vary = true,
};
particle_emit(&flare);
}
void particle_emit_jetpack_boost_trail(Vec2 pos, Vec2 dash_dir) {
/* Continuous blue flame trail while dashing with boost active */
float exhaust_angle = atan2f(-dash_dir.y, -dash_dir.x);
/* Blue flame sparks */
ParticleBurst sparks = {
.origin = pos,
.count = 2,
.speed_min = 50.0f,
.speed_max = 140.0f,
.life_min = 0.1f,
.life_max = 0.25f,
.size_min = 1.5f,
.size_max = 3.0f,
.spread = 0.35f,
.direction = exhaust_angle,
.drag = 3.0f,
.gravity_scale = 0.05f,
.color = {60, 140, 255, 255}, /* bright blue */
.color_vary = true,
};
particle_emit(&sparks);
/* Blue-white wisps */
ParticleBurst wisps = {
.origin = pos,
.count = 1,
.speed_min = 20.0f,
.speed_max = 50.0f,
.life_min = 0.15f,
.life_max = 0.35f,
.size_min = 2.0f,
.size_max = 3.5f,
.spread = 0.5f,
.direction = exhaust_angle,
.drag = 3.5f,
.gravity_scale = -0.05f,
.color = {160, 210, 255, 200}, /* pale blue */
.color_vary = true,
};
particle_emit(&wisps);
}
void particle_emit_jetpack_boost_idle(Vec2 pos, bool facing_left) {
/* Ambient blue flame simmering from the player's back while boost is
* active but the player isn't dashing. Exhaust drifts backward and
* slightly downward — a subtle idle glow effect. */
float exhaust_angle = facing_left ? 0.0f : (float)M_PI; /* away from facing */
/* Small blue sparks drifting backward */
ParticleBurst sparks = {
.origin = pos,
.count = 1,
.speed_min = 15.0f,
.speed_max = 45.0f,
.life_min = 0.12f,
.life_max = 0.3f,
.size_min = 1.0f,
.size_max = 2.5f,
.spread = 0.8f,
.direction = exhaust_angle,
.drag = 4.0f,
.gravity_scale = 0.15f,
.color = {50, 130, 255, 220}, /* medium blue */
.color_vary = true,
};
particle_emit(&sparks);
/* Faint blue-white wisps floating up */
ParticleBurst wisps = {
.origin = pos,
.count = 1,
.speed_min = 8.0f,
.speed_max = 25.0f,
.life_min = 0.15f,
.life_max = 0.35f,
.size_min = 1.5f,
.size_max = 2.5f,
.spread = 1.0f,
.direction = exhaust_angle - 0.3f, /* slightly upward */
.drag = 4.5f,
.gravity_scale = -0.1f,
.color = {140, 200, 255, 160}, /* pale blue, translucent */
.color_vary = true,
};
particle_emit(&wisps);
}
void particle_emit_muzzle_flash(Vec2 pos, Vec2 shoot_dir) {
float angle = atan2f(shoot_dir.y, shoot_dir.x);

View File

@@ -73,6 +73,16 @@ void particle_emit_jetpack_burst(Vec2 pos, Vec2 dash_dir);
/* Jetpack exhaust trail (call each frame while dashing) */
void particle_emit_jetpack_trail(Vec2 pos, Vec2 dash_dir);
/* Blue flame burst (mixed into jetpack burst when boost is active) */
void particle_emit_jetpack_boost_burst(Vec2 pos, Vec2 dash_dir);
/* Continuous blue flame trail during jetpack boost (call each frame while dashing + boosted) */
void particle_emit_jetpack_boost_trail(Vec2 pos, Vec2 dash_dir);
/* Ambient blue glow from jetpack while boost is active but not dashing
* (call each frame; facing_left determines exhaust side) */
void particle_emit_jetpack_boost_idle(Vec2 pos, bool facing_left);
/* Muzzle flash (short bright burst in shoot direction) */
void particle_emit_muzzle_flash(Vec2 pos, Vec2 shoot_dir);

File diff suppressed because it is too large Load Diff

View File

@@ -288,6 +288,11 @@ void player_update(Entity *self, float dt, const Tilemap *map) {
);
particle_emit_jetpack_trail(exhaust_pos, pd->dash_dir);
/* Blue flame trail when boost is active */
if (pd->jetpack_boost_timer > 0) {
particle_emit_jetpack_boost_trail(exhaust_pos, pd->dash_dir);
}
/* Skip normal movement during dash */
physics_update(body, dt, map);
animation_update(&self->anim, dt);
@@ -330,10 +335,28 @@ void player_update(Entity *self, float dt, const Tilemap *map) {
);
particle_emit_jetpack_burst(exhaust_pos, pd->dash_dir);
/* Blue flame accents when boost powerup is active */
if (pd->jetpack_boost_timer > 0) {
particle_emit_jetpack_boost_burst(exhaust_pos, pd->dash_dir);
}
audio_play_sound(s_sfx_dash, 96);
return;
}
/* ── Jetpack boost idle glow ─────────────── */
/* Ambient blue flame from the player's back while boost is active
* and not dashing. Emits from the rear center of the sprite. */
if (pd->jetpack_boost_timer > 0) {
bool facing_left = (self->flags & ENTITY_FACING_LEFT) != 0;
Vec2 back_pos = vec2(
facing_left ? body->pos.x + body->size.x - 1.0f
: body->pos.x + 1.0f,
body->pos.y + body->size.y * 0.45f
);
particle_emit_jetpack_boost_idle(back_pos, facing_left);
}
/* ── Horizontal movement ─────────────────── */
float target_vx = 0.0f;
if (hold_left) target_vx -= PLAYER_SPEED;