@@ -0,0 +1,642 @@
# Arquitectura de **Coffee Crisis Arcade Edition**
> Guía de orientación para un desarrollador nuevo en el proyecto.
>
> Cada afirmación está anclada a código real: se cita el fichero (y, cuando
> ayuda, la función o el número de línea) que la respalda. Donde no he
> encontrado algo, lo digo explícitamente en lugar de inventarlo.
>
> **Coffee Crisis Arcade Edition** es un *shooter* arcade cooperativo de 2
> jugadores escrito en **C++20 sobre SDL3**: los jugadores defienden el café
> de globos gigantes. Apunta a Windows, Linux, macOS (Intel/Apple Silicon),
> Raspberry Pi, Anbernic y web (Emscripten). Los comentarios del código están
> en español/valenciano; este documento está en castellano.
---
## Índice
1. [Visión general ](#1-visión-general )
2. [Punto de entrada y bucle principal ](#2-punto-de-entrada-y-bucle-principal )
3. [Secciones y flujo de la aplicación ](#3-secciones-y-flujo-de-la-aplicación )
4. [Renderizado: de la lógica al píxel ](#4-renderizado-de-la-lógica-al-píxel )
5. [Entrada ](#5-entrada )
6. [Lógica del juego: la clase `Game` ](#6-lógica-del-juego-la-clase-game )
7. [Entidades y managers de gameplay ](#7-entidades-y-managers-de-gameplay )
8. [Modo demo y attract mode ](#8-modo-demo-y-attract-mode )
9. [Recursos ](#9-recursos )
10. [Audio ](#10-audio )
11. [Configuración, parámetros y constantes ](#11-configuración-parámetros-y-constantes )
12. [Localización ](#12-localización )
13. [Convenciones y patrones recurrentes ](#13-convenciones-y-patrones-recurrentes )
14. [Guía de navegación: "si quieres tocar X, mira Y" ](#14-guía-de-navegación-si-quieres-tocar-x-mira-y )
---
## 1. Visión general
El árbol `source/` separa **motor ** y **juego ** :
- **`source/core/` ** — motor genérico: `system` (arranque, secciones, demo,
eventos globales), `rendering` (+ `sdl3gpu` , `sprite` ), `input` , `resources` ,
`audio` , `locale` .
- **`source/game/` ** — el juego concreto: `scenes` (las secciones), `gameplay`
(managers), `entities` (jugador, globos, balas, ítems…), `ui` , y `options` .
- **`source/utils/` ** — `color` , `param` , `utils` , `defines` .
- **`source/external/` ** — vendorizado: `nlohmann/json` , `fkyaml` , `stb_image` ,
`stb_vorbis` .
Los `#include` son **absolutos respecto a `source/` ** (p.ej.
`#include "core/audio/audio.hpp"` ); CMake añade un único `-I.../source`
(ver `CLAUDE.md` ). ~150 ficheros C++, ~32.000 líneas.
**Cuatro ideas-fuerza que conviene interiorizar: **
1. **El flujo de la aplicación se conduce con una variable global **
(`Section::name` ), no con objetos de transición. El `Director` reacciona a
sus cambios (§3).
2. **El render usa texturas GPU vía `SDL_Renderer` ** dibujadas sobre una
* render-target texture * , con post-procesado opcional vía un backend SDL3
GPU. No es un blitter de software (§4).
3. **El gameplay se organiza con managers/pools ** (`BalloonManager` ,
`BulletManager` , `StageManager` ) coordinados por una clase `Game` muy grande
(§6, §7).
4. **Sí hay modo demo ** (*attract mode*): es **reproducción de input grabado ** ,
no IA (§8).
``` mermaid
graph TD
SDL[SDL3 callbacks · main.cpp] --> DIR[Director]
DIR -->|lee Section::name| ST{handleSectionTransition}
ST --> SEC["Section activa (Logo/Intro/Title/Game/…)"]
DIR --> GE[GlobalEvents] --> INP[Input] & SVC[ServiceMenu]
SEC --> GAME[Game]
GAME --> BM[BalloonManager] & BLM[BulletManager] & STG[StageManager]
GAME --> PL[Players] -->|Input::Action| INP
GAME --> DEMO["Demo (playback de input grabado)"] -.->|setInput| PL
GAME -->|SDL_RenderTexture| CV["game_canvas_ (render target)"]
CV -->|RenderReadPixels → uploadPixels| SB[ShaderBackend SDL3 GPU] --> WIN[Ventana]
RES["Resource / Asset"] -.-> GAME & SEC
```
---
## 2. Punto de entrada y bucle principal
### 2.1. SDL conduce el bucle (callbacks)
`source/main.cpp` define `SDL_MAIN_USE_CALLBACKS` : no hay `while` propio. SDL
llama a cuatro funciones, todas delegando en el `Director` :
``` cpp
SDL_AppInit → * appstate = new Director ( argc , argv ) ; // main.cpp:15
SDL_AppIterate → Director : : iterate ( ) ; // un frame
SDL_AppEvent → Director : : handleEvent ( event ) ; // un evento
SDL_AppQuit → delete Director ;
```
### 2.2. El `Director`
`source/core/system/director.{hpp,cpp}` es el orquestador. Mantiene **un
`unique_ptr` por cada sección** (`preload_` , `logo_` , `intro_` , `title_` ,
`game_` , …) de los que **solo uno está vivo ** a la vez (`director.hpp:60` ).
**Constructor (`director.cpp:82`) ** : fija la semilla aleatoria, crea la carpeta
de sistema (`jailgames/coffee_crisis_arcade_edition` ), decide la sección
inicial según el build (`RECORDING` → GAME; `_DEBUG` → lee `debug.yaml` ; Release
→ LOGO) y llama a `init()` .
* * `init()` (`director.cpp:131` )** inicializa en orden: `Asset` (índice de
ficheros), sistema de recursos (`resources.pack` , con/ sin * fallback * a disco
según build), `Input` , `Options` (config.yaml, controllers.json, presets de
shaders), parámetros y * scores * , `Lang` , `Screen` , `Audio` y `Resource` .
### 2.3. Arranque NO bloqueante (PRELOAD incremental)
Un detalle importante: si el modo de carga es `PRELOAD` , **no se cargan todos
los recursos de golpe**. `init()` redirige el arranque a la sección `PRELOAD`
guardando el destino real en `Section::post_preload` (`director.cpp:191` ):
``` cpp
Section : : post_preload = Section : : name ; // destino real
Section : : name = Section : : Name : : PRELOAD ; // mientras tanto, barra de progreso
Resource : : get ( ) - > beginLoad ( ) ;
```
Cada frame, `Director::iterate()` (`director.cpp:489` ) llama a
`Resource::loadStep(50 /*ms*/)` : carga recursos hasta agotar un presupuesto de
50 ms por frame, manteniendo la ventana y el bucle vivos. Cuando termina,
`finishBoot()` inicializa lo que depende de los recursos (`ServiceMenu` ,
`Notifier` , singletons de `Screen` ) y salta a `post_preload` .
### 2.4. Orden del frame y gestión del tiempo
`Director::iterate()` (`director.cpp:489` ):
``` mermaid
sequenceDiagram
participant SDL
participant Dir as Director::iterate
participant Sec as Section activa
SDL->>Dir: SDL_AppIterate
alt boot_loading_
Dir->>Dir: Resource::loadStep(50ms) → finishBoot() al terminar
end
Dir->>Dir: handleSectionTransition() (destruye/crea sección)
Dir->>Sec: iterate() (la sección hace su propio update+render)
```
El tiempo es **time-based ** : cada sección calcula su propio `delta_time` . En
`Game` es `calculateDeltaTime()` (`game.hpp:210` ), usado en todos los `update` .
Los eventos llegan por separado vía `Director::handleEvent` →
`GlobalEvents::handle` + reenvío a la sección activa (`director.cpp:539` ).
### 2.5. Reinicio en caliente
Igual que su proyecto hermano, soporta reinicio real vía `execv`
(`Director::relaunch()` , `director.cpp:254` ): la sección `RESET` (p.ej. tras
F10 o cambio de idioma) reemplaza el proceso por sí mismo; si no se puede
(Emscripten, `argv` inválido), cae a un `reset()` interno que recarga recursos y
vuelve a `LOGO` .
---
## 3. Secciones y flujo de la aplicación
### 3.1. Variables globales de estado
`source/core/system/section.hpp` define el estado del flujo como **variables
globales `inline` ** en el namespace `Section` :
``` cpp
inline Name name = Name : : RESET ; // sección a la que ir
inline Name post_preload = Name : : LOGO ; // destino tras PRELOAD
inline Options options = Options : : NONE ; // 1P/2P/BOTH, timeouts, etc.
inline AttractMode attract_mode = AttractMode : : TITLE_TO_DEMO ;
```
`Name` enumera: `RESET, PRELOAD, LOGO, INTRO, TITLE, GAME, HI_SCORE_TABLE,
GAME_DEMO, INSTRUCTIONS, CREDITS, QUIT` . Cualquier parte del código cambia el
flujo simplemente asignando `Section::name = ...` .
### 3.2. La transición de secciones
`Director::handleSectionTransition()` (`director.cpp:390` ) se ejecuta cada
frame:
- Si `Section::name == RESET` : intenta `relaunch()` ; si vuelve, hace el reset
interno.
- Si `Section::name == last_built_section_name_` : no hace nada (ya está viva).
- Si cambió: `resetActiveSection()` (libera todos los `unique_ptr` ) y un
`switch` construye la nueva sección. Para `GAME` traduce `Section::options`
a `Player::Id` (1P/2P/BOTH); para `GAME_DEMO` construye `Game(..., DEMO_ON)`
con jugador aleatorio (`director.cpp:448` ).
Cada sección (`source/game/scenes/` ) es autónoma y expone `iterate()` +
`handleEvent()` . `Director::iterate` despacha al puntero vivo con una cadena de
`if/else if` (`director.cpp:517` ).
``` mermaid
graph LR
RESET --> PRELOAD --> LOGO --> INTRO --> TITLE
TITLE -->|jugar| GAME --> HI_SCORE_TABLE --> TITLE
TITLE -->|timeout / attract| GAME_DEMO --> TITLE
TITLE -->|attract alterno| LOGO
TITLE --> INSTRUCTIONS & CREDITS
```
### 3.3. Attract mode
El * attract mode * alterna entre mostrar la demo y volver al logo. En
`title.cpp:51` el Title, al construirse, mira `Section::attract_mode` y fija su
`next_section_` (a `GAME_DEMO` o `LOGO` ), **invirtiendo el modo ** para la
próxima vez. Cuando el Title agota su * timeout * sin input, salta a esa sección
(ver §8).
---
## 4. Renderizado: de la lógica al píxel
A diferencia de un blitter de software, aquí **los sprites son texturas GPU **
compuestas por `SDL_Renderer` , y el post-procesado se hace en un backend SDL3
GPU.
### 4.1. `Texture` y la jerarquía de sprites
- `source/core/rendering/texture.hpp` — `Texture` envuelve un `SDL_Texture*` ,
con `loadFromFile` , `createBlank` , `setAsRenderTarget` y un `render(x, y,
clip, zoom, angle, …)` . Es la unidad de dibujo.
- `source/core/rendering/sprite/` — jerarquía sobre `Sprite`
(`sprite.hpp:13` ), que guarda **varias texturas ** (`textures_` ), un
`sprite_clip_` y posición/zoom:
- `AnimatedSprite` — animación por fotogramas (clip que avanza).
- `MovingSprite` — con velocidad.
- `PathSprite` — sigue rutas precalculadas (`Path` ).
- `SmartSprite` — sprite con lógica propia (p.ej. el café que salta al ser
golpeado).
- `CardSprite` — variante para "cartas"/paneles.
El `Player` , por ejemplo, compone un `AnimatedSprite player_sprite_` y un
`power_sprite_` para el aura (`player.hpp:250` ).
### 4.2. Dos render-targets
Hay **dos texturas de destino ** encadenadas:
1. * * `Game::canvas_` ** — textura de la * zona de juego * . `Game::fillCanvas()`
(`game.cpp` ) fija el render-target a `canvas_` y dibuja, **en este orden ** :
`background → smart_sprites → items → balloons → tabe → players → bullets →
path_sprites` . Ese orden es el z-order del playfield.
2. * * `Screen::game_canvas_` ** — textura de * pantalla completa * (ARGB8888 a la
resolución de juego; `screen.cpp:92` , `SDL_TEXTUREACCESS_TARGET` ).
`Game::render()` hace `screen_->start()` (que pone el target en
`game_canvas_` ), copia `canvas_` sobre él, y encima dibuja **scoreboard ** y
los **fades ** de entrada/salida; finalmente `screen_->render()` .
### 4.3. Post-procesado y presentación
`Screen::renderPresent()` (`screen.cpp:156` ) decide cómo llega a la ventana:
- **Con backend GPU acelerado**: lee los píxeles de `game_canvas_` con
`SDL_RenderReadPixels` a un `pixel_buffer_` CPU, los sube al backend
(`shader_backend_->uploadPixels(...)` ) y este los renderiza con el shader
activo (`shader_backend_->render()` ).
- **Sin backend** (fallback): vuelca `game_canvas_` directamente a la ventana
con `SDL_RenderTexture` .
El backend vive en `source/core/rendering/sdl3gpu/` (SDL3 GPU API; shaders
SPIR-V compilados offline desde GLSL en `data/shaders/` , embebidos en cabeceras
`spv/` /`msl/` ). Hay dos shaders seleccionables, **PostFX ** y **CrtPi ** , con
presets en `postfx.yaml` /`crtpi.yaml` (`screen.cpp:382` ). El build `NO_SHADERS`
(Anbernic) desactiva todo el pipeline de shaders.
``` mermaid
graph TD
OBJ["background, balloons, players, bullets…"] -->|SDL_RenderTexture| CANVAS["Game::canvas_ (zona de juego)"]
CANVAS -->|copiar| GC["Screen::game_canvas_ (pantalla)"]
SB2[scoreboard] --> GC
FADE[fades de entrada/salida] --> GC
GC -->|RenderReadPixels → uploadPixels| SHADER["ShaderBackend (PostFX/CrtPi)"]
SHADER --> WIN[Ventana]
GC -.fallback sin GPU.-> WIN
```
### 4.4. Efectos de pantalla
`Screen` integra efectos globales sobre la presentación: **shake **
(`ShakeEffect` , desplaza el rect), **flash ** (`FlashEffect` , tinte temporal) y
**attenuate ** (atenuación) — todos definidos como structs internos en
`screen.hpp:108` – `205` . Las transiciones entre estados usan
`source/core/rendering/fade.*` (cuadrícula de cuadros).
---
## 5. Entrada
### 5.1. `Input` singleton
`source/core/input/input.hpp` . Centraliza teclado y mandos bajo un enum de
**acciones ** (`Input::Action` , alias de `InputAction` ). El mapeo por defecto
vive en structs:
- **`Keyboard` ** (`input.hpp:54` ): flechas = mover; **Q/W/E ** = disparar
izquierda/centro/derecha; Enter = START; F12 = SERVICE; P = PAUSE; ESC = EXIT;
**F1– F11 ** = ventana/vídeo/audio/idioma/reset/info.
- **`Gamepad` ** (`input.hpp:99` ): cruceta = mover; WEST/NORTH/EAST = disparar;
START/ BACK = start/service. Cada mando es un `shared_ptr<Gamepad>` con su
propio `bindings` , nombre y * path * .
La consulta principal es `checkAction(action, repeat, check_keyboard, gamepad)`
(`input.hpp:168` ). Hay además detección de ejes y triggers como botones, y
gestión de configuraciones de mando persistidas (`gamepad_config_manager` ,
`controllers.json` ).
### 5.2. Eventos globales y hotkeys
- `source/core/system/global_events.cpp` — `GlobalEvents::handle(event)` trata
lo común a cualquier sección: `SDL_EVENT_QUIT` , * resize * , render target reset,
hot-plug de mandos (con notificación tras `markStartupComplete` ), el toggle
del menú de servicio y el ratón.
- `source/core/input/global_inputs.cpp` — hotkeys de sistema, despachadas con
un mapa acción→lambda (`global_inputs.cpp:187` ): fullscreen, zoom ±, audio,
autofire, idioma (CHANGE_LANG → `Section::RESET` , reinicia), VSync, integer
scale, info; más PostFX/shader/preset. **F10/RESET ** pone `Section::name =
RESET` (reinicio) y **ESC/EXIT ** pone `QUIT` .
### 5.3. Cómo llega la entrada a un jugador
Dentro de `Game` , `checkInput()` → `handlePlayersInput()` →
`handleNormalPlayerInput(player)` consulta `Input` y traduce a
`player->setInput(Input::Action)` y disparos (`handleFireInput` ). El `Player`
sabe si usa teclado o un mando concreto (`uses_keyboard_` , `gamepad_` ;
`player.hpp:217` ). En modo demo, esta misma vía se alimenta de datos grabados
(§8).
---
## 6. Lógica del juego: la clase `Game`
`source/game/scenes/game.{hpp,cpp}` es la sección de gameplay y la clase más
grande del proyecto. Coordina jugadores, globos, balas, ítems, fases,
puntuación y efectos.
### 6.1. FSM de la partida
`Game::State` (`game.hpp:75` ): `FADE_IN → ENTERING_PLAYER →
SHOWING_GET_READY_MESSAGE → PLAYING → COMPLETED / GAME_OVER` . `checkState()`
(`game.cpp` ) detecta fin de juego (`stage_manager_->isGameCompleted()` ) o game
over (`allPlayersAreGameOver()` ); `setState()` hace la transición.
### 6.2. El frame de `Game`
``` cpp
void Game : : iterate ( ) { // game.cpp
const float DELTA_TIME = calculateDeltaTime ( ) ;
checkInput ( ) ;
update ( DELTA_TIME ) ;
render ( ) ;
}
void Game : : update ( float dt ) {
screen_ - > update ( dt ) ;
Audio : : update ( ) ;
updateDemo ( dt ) ; // no-op si no es demo
updateGameStates ( dt ) ; // dispatch por State
fillCanvas ( ) ; // dibuja la zona de juego en canvas_
}
```
`updateGameStates` despacha a un `updateGameState<Estado>` por cada valor de la
FSM (`game.hpp:218` ).
### 6.3. Sistemas que orquesta
`Game` posee (vía `unique_ptr` ) los managers y objetos del nivel
(`game.hpp:136` ):
- **`StageManager` ** — progresión por "poder": cada fase necesita
`power_to_complete` ; el juego se completa al acumular el poder total. También
define umbrales de **amenaza ** (`menace` ) por fase. Implementa `IStageInfo`
(`stage.hpp:51` ), interfaz mínima que se inyecta a jugador y globos.
- **`BalloonManager` ** — despliega formaciones (`deployRandomFormation` ),
globos hijos al explotar uno, * power balls * , ajusta velocidad
(`Balloon::GAME_TEMPO` ) y calcula la amenaza en pantalla (§7).
- **`BulletManager` ** — pool de balas; las colisiones se resuelven con
**callbacks ** que registra `Game` (`setBalloonCollisionCallback` ,
`setTabeCollisionCallback` , `setOutOfBoundsCallback` ; `bullet_manager.hpp:49` ),
manteniendo la lógica de juego dentro de `Game` .
- **`Background` **, * * `Fade` × 2**, * * `Tabe` ** (enemigo especial volador),
* * `Scoreboard` **, * * `PauseManager` **.
- Listas de `Item` (power-ups y puntos), `SmartSprite` y `PathSprite` .
### 6.4. Mecánicas destacadas
- **Ítems / power-ups** (`game.hpp:268` ): café (toque extra),
máquina de café (power-up), * power ball * , reloj (= **detener el tiempo ** ,
`enableTimeStopItem` ), e ítems de puntos. La probabilidad de soltar ítem la
decide `dropItem()` con * odds * configurables (`Helper` , `game.hpp:100` ).
- **Sistema de amenaza** (`updateMenace` /`setMenace` ): si la amenaza cae por
debajo de un umbral, se generan más globos.
- **Multijugador**: `players_` es un `vector<shared_ptr<Player>>` (1 o 2). Un
`players_draw_list_` separado mantiene el z-order de dibujo
(`buildPlayerDrawList` , `sendPlayerToBack` /`bringPlayerToFront` ;
`game.hpp:338` ).
- **Récords**: al perder con buena puntuación, el jugador entra en
`ENTERING_NAME` (`enter_name.*` , `manage_hiscore_table.*` ).
---
## 7. Entidades y managers de gameplay
`source/game/entities/` :
- **`Player` ** (`player.hpp` ) — la entidad más compleja. Hereda de
`AnimatedSprite` . Tiene **tres ejes de estado ** independientes:
`walking_state_` , `firing_state_` y `playing_state_` (`player.hpp:266` ). El
disparo usa un **sistema de dos líneas ** : una funcional (cooldown, `canFire` )
y otra visual (animaciones `NORMAL→AIMING→RECOILING→THREAT_POSE` ,
`player.hpp:296` ). Soporta power-up, invulnerabilidad con parpadeo, * continue *
y entrada de nombre. Puede controlarse por teclado o por un `Gamepad`
concreto.
- **`Balloon` ** (`balloon.hpp` ) — el enemigo básico; al explotar puede crear
globos hijos. `Balloon::GAME_TEMPO` define velocidades por progreso.
- **`Bullet` ** (`bullet.hpp` ) — proyectil con tipo (UP/LEFT/RIGHT) y color
(según power-up del jugador).
- **`Item` ** — power-ups y objetos de puntos que caen.
- **`Explosions` ** — efectos de explosión (lo posee `BalloonManager` ).
- **`Tabe` ** — enemigo/objeto especial volador con su propia lógica de impacto.
`source/game/gameplay/` contiene los **managers y sistemas ** (no entidades en
sí): `BalloonManager` , `BulletManager` , `StageManager` , `Difficulty` ,
`Scoreboard` , `balloon_formations` (lee `formations.txt` ), `cooldown` ,
`enter_name` , `manage_hiscore_table` , `game_logo` .
El patrón general aquí **no es un `EntityManager` polimórfico único ** (como en
el proyecto hermano), sino **managers especializados por tipo ** + listas
homogéneas en `Game` , con **callbacks ** para cruzar responsabilidades sin
acoplar (caso de `BulletManager` ).
---
## 8. Modo demo y attract mode
> **A diferencia de muchos juegos, aquí el modo demo SÍ existe — pero NO es
> IA.** Es **reproducción de input pregrabado**.
### 8.1. Formato de los datos
`source/core/system/demo.hpp` : cada fotograma de demo es un `DemoKeys` con seis
banderas (`left` , `right` , `no_input` , `fire` , `fire_left` , `fire_right` ). Una
demo es un `vector<DemoKeys>` de `TOTAL_DEMO_DATA = 2000` fotogramas. Hay tres
ficheros: `data/demo/demo{1,2,3}.bin` , leídos por `loadDemoDataFromFile`
(`demo.cpp:11` ).
### 8.2. Reproducción
Cuando `Game` se construye con `DEMO_ON` (sección `GAME_DEMO` ), `initDemo()`
(`game.cpp` ) carga todas las demos del `Asset` y **asigna una a cada jugador de
forma aleatoria** (`shuffle` ), elige una fase de inicio al azar, da cafés
aleatorios, pone los marcadores en modo `DEMO` y silencia los globos. Luego cada
frame, `demoHandlePlayerInput()` lee el fotograma actual y lo inyecta por la
**misma vía de input que un humano ** :
``` cpp
// game.cpp — demoHandlePlayerInput
const auto & demo_data = demo_data_vec . at ( demo_ . index % demo_data_vec . size ( ) ) ;
if ( demo_data . left = = 1 ) player - > setInput ( Input : : Action : : LEFT ) ;
else if ( demo_data . right = = 1 ) player - > setInput ( Input : : Action : : RIGHT ) ;
. . .
if ( demo_data . fire = = 1 ) handleFireInput ( player , Bullet : : Type : : UP ) ;
```
No hay toma de decisiones: el jugador "demo" repite exactamente las pulsaciones
grabadas. `demoHandlePassInput()` permite **salir ** con cualquier botón,
volviendo a `TITLE` y dejando armado el siguiente attract (`TITLE_TO_DEMO` ).
### 8.3. Attract mode
El Title alterna `TITLE_TO_DEMO` ↔ `TITLE_TO_LOGO` (`title.cpp:51` ): tras su
* timeout * , una vez lanza la demo y la siguiente vuelve al logo, en bucle de
atracción típico de recreativa.
### 8.4. Grabación y autoplay
- **`#ifdef RECORDING` ** (`demo.cpp:43` , `game.hpp:349` ): build especial que
arranca directamente en GAME y **graba ** las pulsaciones a un `.bin` con
`saveDemoFile` . Así se generan las demos.
- **`autoplay` ** (debug): en `_DEBUG` , `debug_config.autoplay` permite alimentar
datos de demo a los jugadores en una partida normal (`game.hpp:354` ,
`initDemo` ).
---
## 9. Recursos
### 9.1. `Resource` (singleton)
`source/core/resources/resource.hpp` . Cachea por nombre texturas, música,
sonidos, ficheros de texto/fuentes, animaciones y **datos de demo **
(`getDemoData` ). Dos modos (`LoadingMode` ):
- **`PRELOAD` ** — carga incremental al arranque con `beginLoad()` +
`loadStep(budget_ms)` + `renderProgress()` (la sección Preload pinta la
barra). Es el modo por defecto.
- **`LAZY_LOAD` ** — carga bajo demanda (seleccionable vía `debug.yaml` ).
### 9.2. `Asset` y el pack
`source/core/resources/asset.*` mantiene el **índice de ficheros ** a partir del
manifiesto `data/config/assets.txt` (`director.cpp:296` ) y valida que no falte
ninguno (`Asset::check()` ). La lectura física pasa por
`ResourceHelper::loadFile` , que sirve desde * * `resources.pack` ** y hace
* fallback * al filesystem en builds de desarrollo (en Release nativo es estricto:
solo el pack; `director.cpp:143` ). `resource_pack.*` y `resource_loader.*`
implementan el formato del pack y la carga.
Formatos: imágenes vía `stb_image` + `gif` propio; audio `.ogg` vía
`stb_vorbis` ; texto/JSON vía `nlohmann/json` ; configs vía `fkyaml` .
---
## 10. Audio
`source/core/audio/audio.hpp` — `Audio` singleton sobre `jail_audio`
(primer-party, no librería externa) y `audio_adapter` . API: `playMusic(name,
loop, crossfade_ms)` , `playSound(name, Group)` , control de volumen por **grupos **
(`Group::GAME` , `INTERFACE` , …), `pause/resume/stop` , * crossfade * y * fade out *
(`audio.hpp:49` ). `Audio::update()` se llama cada frame desde las secciones
(p.ej. `Game::update` ). El build `NO_AUDIO` compila sin audio.
---
## 11. Configuración, parámetros y constantes
El proyecto tiene **tres capas de configuración ** que conviene no confundir:
1. * * `Options` ** (`source/game/options.hpp` ) — opciones persistentes del
usuario, agrupadas en structs (`Window` , `Video` , `Audio` , `Settings` ,
`Gamepad` , `Keyboard` ) más presets de shaders (`PostFXPreset` ,
`CrtPiPreset` ). Se guardan en `config.yaml` / `controllers.json` /
`postfx.yaml` / `crtpi.yaml` . Incluye `params_preset` ∈ {`classic` ,
`arcade` , `red` } (`options.hpp:339` ).
2. * * `param` / `Param` ** (`source/utils/param.hpp` ) — parámetros de *gameplay y
layout* (dimensiones del juego, `play_area` , ajustes de globos, scoreboard,
título, fades, jugador, Tabe…). Se cargan de ficheros de texto
`param_320x240.txt` / `param_320x256.txt` / `classic.txt` con
`loadParamsFromFile` (`director.cpp:275` ). El objeto global `param` lo leen
casi todos los subsistemas.
3. **Otros datos de juego ** : `stages.txt` (fases, lo lee `StageManager` ),
`formations.txt` (formaciones de globos), `assets.txt` (manifiesto).
Los **valores de fallback compilados ** viven en
`source/core/system/defaults.hpp` (y `source/utils/defines.hpp` para constantes
base). En Debug, `debug.yaml` controla sección inicial, opciones, fase, modo de
carga, `autoplay` e `invincibility` (`director.cpp:318` ).
### Builds condicionales (multiplataforma)
El juego se adapta por * defines * (ver `CLAUDE.md` ): `WINDOWS_BUILD` /
`LINUX_BUILD` / `MACOS_BUILD` , `RELEASE_BUILD` , `MACOS_BUNDLE` , `ANBERNIC`
(handheld), `__EMSCRIPTEN__` (web), `NO_SHADERS` , `NO_AUDIO` , `RECORDING` ,
`ARCADE` , `DEBUG` /`VERBOSE` . Aparecen sobre todo en `Director::init` y `Screen` .
---
## 12. Localización
`source/core/locale/lang.*` — `Lang` carga el idioma desde **JSON ** en
`data/lang/` . `Lang::setLanguage(Options::settings.language)` se llama en
`init()` y en cada `reset()` . Cambiar idioma en caliente (F9 / `CHANGE_LANG` )
fuerza un `Section::RESET` (reinicio del proceso) para recargarlo todo
(`global_inputs.cpp` ).
---
## 13. Convenciones y patrones recurrentes
- **Naming** (de `.clang-format` /`.clang-tidy` , resumido en `CLAUDE.md` ):
clases `CamelCase` , métodos `camelBack` , variables/params `snake_case` ,
miembros privados `snake_case_` (guion bajo final), constantes `UPPER_CASE` ,
namespaces `CamelCase` , valores de enum `UPPER_CASE` . clang-tidy trata los
warnings como errores.
- **Singletons con init/destroy manual**: `Screen` , `Resource` , `Audio` ,
`Input` , `Asset` , `ServiceMenu` , `Notifier` , `Lang` . Se crean/destruyen en
orden explícito en `Director::init` / `shutdownSubsystems` (`director.cpp:227` )
— no se confía en destructores estáticos.
- **Estado de flujo por variable global** (`Section::name` ): patrón central; no
hay objetos de transición tipados.
- **Time-based en todo**: cada sistema recibe `delta_time` ; las constantes de
tiempo se documentan como "N frames a 60fps → segundos" (p.ej.
`game.hpp:85` ).
- **Callbacks para desacoplar**: `BulletManager` y `StageManager` reciben
`std::function` desde `Game` para no conocer la lógica de colisión/puntuación.
- **Interfaz mínima `IStageInfo` ** (`stage_interface.hpp` ): se inyecta a
jugador y globos para que consulten amenaza/poder sin ver todo el
`StageManager` .
- **Managers por tipo + listas homogéneas** en `Game` , en vez de un contenedor
de entidades polimórfico.
- **Comentarios** en español/valenciano; muchos `#include` llevan comentario
"// Para X" indicando qué símbolo justifican (estilo IWYU).
- **Multiplataforma por `#ifdef` ** (ver §11). Conviene leerlos al tocar
arranque o render.
---
## 14. Guía de navegación: "si quieres tocar X, mira Y"
| Quiero… | Empieza por… |
|---|---|
| Entender el arranque | `source/core/system/director.cpp` (`init` , `iterate` ) |
| Cambiar el flujo de pantallas | `source/core/system/section.hpp` + `handleSectionTransition` |
| Añadir/editar una pantalla | `source/game/scenes/` (crea `iterate()` +`handleEvent()` ) y un `case` en `director.cpp` |
| La barra de carga / arranque lento | `Resource::beginLoad/loadStep` + `scenes/preload.*` |
| Cómo se dibuja todo | `Game::fillCanvas` + `Game::render` + `Screen::renderPresent` (`screen.cpp:156` ) |
| Sprites / animaciones | `source/core/rendering/sprite/` + `texture.hpp` |
| Shaders / CRT / post-FX | `source/core/rendering/sdl3gpu/` + `data/shaders/` + `Options` presets |
| Efectos (shake/flash/fade) | `screen.hpp` (structs) + `core/rendering/fade.*` |
| Controles / mandos | `source/core/input/input.hpp` (`Keyboard` /`Gamepad` /`checkAction` ) |
| Hotkeys F1– F12 / reset | `source/core/input/global_inputs.cpp` |
| Eventos globales / hot-plug | `source/core/system/global_events.cpp` |
| Lógica de partida y estados | `source/game/scenes/game.cpp` (`updateGameState*` ) |
| Globos y formaciones | `gameplay/balloon_manager.*` + `entities/balloon.*` + `formations.txt` |
| Balas y colisiones | `gameplay/bullet_manager.*` (callbacks) + `entities/bullet.*` |
| Fases / progresión / dificultad | `gameplay/stage.*` (`StageManager` ) + `stages.txt` + `gameplay/difficulty.*` |
| El jugador (movimiento, disparo) | `entities/player.*` (sistema de disparo de dos líneas) |
| Ítems y power-ups | `entities/item.*` + `Game::dropItem/createItem` |
| Récords / entrada de nombre | `gameplay/manage_hiscore_table.*` , `gameplay/enter_name.*` |
| **Modo demo / attract ** | `core/system/demo.*` , `Game::initDemo/demoHandle*` , `scenes/title.cpp` |
| Grabar nuevas demos | build con `RECORDING` + `Game::updateRecording` |
| Cargar un recurso | `core/resources/resource.hpp` + `asset.*` + `assets.txt` |
| Audio (música/SFX) | `core/audio/audio.hpp` |
| Opciones del usuario | `game/options.hpp` (+ `config.yaml` ) |
| Parámetros de gameplay/layout | `utils/param.hpp` + `param_320x*.txt` |
| Valores por defecto | `core/system/defaults.hpp` , `utils/defines.hpp` |
| Idiomas | `core/locale/lang.*` + `data/lang/` |
| Menú de servicio / notificaciones | `game/ui/service_menu.*` , `game/ui/notifier.*` |
| Empaquetar datos | `tools/` (`pack_resources` ) + `make resources.pack` |
---
*Documento generado a partir de la lectura directa del código en el commit
actual de la rama `main` . Si algo aquí no cuadra con el código, el código
manda: actualiza este documento.*