Implement run mechanic and level progression with top-left HUD indicator

Run (B + direction):
- player_logic.c: while holding B + direction with stamina >= 10, each tile
  takes 8 frames instead of 16 (move_progress += 2, LERP >>4 unchanged) and
  costs 10 stamina per tile. DAS_REPEAT_RUN=2 chains tiles fluidly.
- Falls back silently to normal walk when stamina < 10.

Level progression:
- New global 'level' (starts at 1). title->game resets to 1; reaching the
  hatch (victory/Going Deeper) + START increments before engine_init (new
  maze); defeat + START resets to 1.
- Top-left HUD 'L<n>' via 3 sprites (OAM 23-25) from new level.png asset
  (glyphs L, 0-9), generated by scripts/generate_level.py and converted
  with png2asset -keep_duplicate_tiles for predictable tile ordering.
- VRAM base aligned to an EVEN index (8x16 hardware ignores tile LSB) and
  placed after the stamina load claim to avoid the shared BG tile block.
- update_level_display() called every frame; hides during game over/title.

Docs: README, doc/gameplay.md and the technical report updated.
This commit is contained in:
2026-06-24 11:56:19 +02:00
parent ae0706ccfb
commit 36e52d4d1a
15 changed files with 506 additions and 12 deletions
+26 -4
View File
@@ -9,6 +9,9 @@ static uint8_t das_timer = 0;
static uint8_t das_active = 0;
#define DAS_DELAY 12
#define DAS_REPEAT 6
#define DAS_REPEAT_RUN 2 // DAS piu' rapido mentre si corre (il passo di corsa dura 8 frame)
#define RUN_STAMINA_COST 10
#define RUN_MIN_STAMINA 10
/**
* Gestisce la logica di movimento del giocatore, i salti e le collisioni.
@@ -23,6 +26,10 @@ static uint8_t das_active = 0;
* 3. Meccanica del Salto: Premendo A+Direzione, il giocatore può saltare *esattamente* 2 celle
* più in là. Il codice verifica se la cella intermedia (+1) è un ostacolo (0) e se
* la cella di atterraggio (+2) è calpestabile. Consuma 60 di stamina per bilanciare il vantaggio tattico.
* 4. Meccanica della Corsa: Premendo B+Direzione (con stamina >= 10) il giocatore si mette a correre:
* il passo dura 8 frame invece di 16 (move_progress incrementato di 2) e consuma 10 stamina per tile.
* Il DAS diventa piu' rapido (DAS_REPEAT_RUN) per incatenare i tile fluidamente. Se la stamina
* non basta, ripiega silenziosamente su camminata normale senza consumo.
*/
void update_player_movement(uint8_t keys, uint8_t prev_keys) {
// --- Gestione della Stamina ---
@@ -39,7 +46,9 @@ void update_player_movement(uint8_t keys, uint8_t prev_keys) {
// --- State Machine: Transizione Animazione ---
// Se siamo nel mezzo di uno spostamento tra tile, calcoliamo i pixel interpolati
if (is_moving) {
move_progress++;
// Corsa: il passo dura 8 frame (incremento 2) invece di 16 (incremento 1).
// La formula LERP resta a punto fisso (>>4 == /16): move_progress raggiunge 16 in meta' tempo.
move_progress += is_running ? 2 : 1;
// Interpolazione lineare (LERP) da start a target
int16_t px = start_px + (((target_px - start_px) * (int16_t)move_progress) >> 4); // >> 4 equivale a / 16
@@ -62,6 +71,7 @@ void update_player_movement(uint8_t keys, uint8_t prev_keys) {
if (move_progress == 16) {
is_moving = 0;
is_jumping = 0;
is_running = 0;
player_lx = target_lx;
player_ly = target_ly;
@@ -91,9 +101,11 @@ void update_player_movement(uint8_t keys, uint8_t prev_keys) {
// --- Lettura Controlli (Polling) con DAS ---
uint8_t keys_pressed = keys & ~prev_keys; // Tasti premuti ESATTAMENTE in questo frame
// La corsa (B tenuto + stamina) usa un DAS piu' corto per incatenare i tile fluidamente.
uint8_t wants_run = (keys & J_B) && (stamina >= RUN_MIN_STAMINA);
if (keys_pressed) {
das_timer = DAS_DELAY;
das_timer = wants_run ? DAS_REPEAT_RUN : DAS_DELAY;
das_active = 1;
} else if (keys && das_active) {
// Se si tiene premuto un tasto...
@@ -102,7 +114,7 @@ void update_player_movement(uint8_t keys, uint8_t prev_keys) {
}
// Se il timer arriva a 0, emuliamo una pressione ripetuta
if (das_timer == 0 && !is_moving) {
das_timer = DAS_REPEAT;
das_timer = wants_run ? DAS_REPEAT_RUN : DAS_REPEAT;
keys_pressed = keys;
}
} else {
@@ -168,7 +180,7 @@ void update_player_movement(uint8_t keys, uint8_t prev_keys) {
// Se le condizioni di salto falliscono, aggiorna semplicemente il frame per far girare il personaggio
update_player_sprite();
}
// 2. Movimento standard (Camminata)
// 2. Movimento standard (Camminata o Corsa)
else {
int8_t new_lx = player_lx + move_lx;
int8_t new_ly = player_ly + move_ly;
@@ -178,6 +190,16 @@ void update_player_movement(uint8_t keys, uint8_t prev_keys) {
if (maze[new_ly][new_lx] == 1 || maze[new_ly][new_lx] == 2) {
is_moving = 1;
move_progress = 0;
// Corsa: B + direzione con stamina sufficiente. Il passo dura 8 frame
// (vedi l'incremento di move_progress sopra) e costa 10 stamina per tile.
// Se la stamina non basta, ripiega silenziosamente su camminata normale.
if ((keys & J_B) && stamina >= RUN_MIN_STAMINA) {
is_running = 1;
stamina -= RUN_STAMINA_COST; // 10 stamina per tile (guardia sopra: stamina >= 10)
update_stamina_display();
} else {
is_running = 0;
}
start_lx = player_lx;
start_ly = player_ly;
target_lx = new_lx;