Files
coffee-crisis/docs/ARQUITECTURA.md
T

543 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Arquitectura de **Coffee Crisis**
> 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, o donde el código contradice a la documentación previa, lo
> digo explícitamente en lugar de inventarlo.
>
> **Coffee Crisis** es un arcade en C++20 + SDL3: el jugador defiende la UPV de
> globos de café rebotantes a lo largo de 10 fases. Soporta 12 jugadores,
> teclado y mando, y varios idiomas. Es el **predecesor** de *Coffee Crisis
> Arcade Edition*; al final del documento ([§15](#15-diferencias-frente-a-la-arcade-edition))
> hay un resumen de las diferencias entre ambos. 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](#7-entidades)
8. [Modo demo y attract mode](#8-modo-demo-y-attract-mode)
9. [Recursos](#9-recursos)
10. [Audio](#10-audio)
11. [Configuración y constantes](#11-configuración-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)
15. [Diferencias frente a la Arcade Edition](#15-diferencias-frente-a-la-arcade-edition)
---
## 1. Visión general
El árbol `source/` separa **motor** y **juego**:
- **`source/core/`** — motor genérico: `system` (`director`, `delta_time`,
`demo`), `rendering` (+ `sdl3gpu`, sprites), `input`, `resources`, `audio`,
`locale`.
- **`source/game/`** — el juego concreto: `game.*` (el hub de gameplay),
`entities/` (player, balloon, bullet, item), `scenes/` (logo, intro, title,
instructions), `ui/` (menu), `options.*` y `defaults.hpp`.
- **`source/utils/`** — `utils.*` (helpers, `struct Section`, `Color`,
dificultad…) y `defines.hpp` (macros de build).
- **`source/external/`** — vendorizado: `stb_image`, `stb_vorbis` (y headers
YAML/JSON).
~51 ficheros C++ y ~16.000 líneas. **Nota sobre cabeceras**: los módulos
antiguos usan extensión **`.h`** (p.ej. `director.h`, `game.h`, `screen.h`); los
módulos nuevos usan **`.hpp`** (p.ej. `demo.hpp`, `options.hpp`,
`delta_time.hpp`). Es un proyecto en migración, y eso se nota en varias capas.
**Ideas-fuerza que conviene interiorizar:**
1. El flujo se controla con un **`struct Section { name, subsection }`** que el
`Director` lee cada frame (§3).
2. El render dibuja con **texturas GPU** (`SDL_Renderer`) sobre un **canvas
virtual de 256×192**, con post-procesado opcional vía un backend SDL3 GPU
(§4).
3. El gameplay es **monolítico**: casi todo vive en la clase `Game`, con
vectores de **punteros crudos** y `new`/`delete` manual (§6, §7).
4. **Sí hay modo demo** (*attract mode*): **reproducción de input grabado**, no
IA, orquestada desde la pantalla de título (§8).
5. El proyecto está **migrando de frame-based a time-based** y de `config.txt`
a YAML; conviven ambos mundos (§2, §11).
```mermaid
graph TD
SDL[SDL3 callbacks · main.cpp] --> DIR[Director]
DIR -->|struct Section| ST{handleSectionTransition}
ST --> SEC["Logo / Intro / Title / Game"]
SEC --> TITLE[Title] -.attract.-> NESTED["Game anidado en demo + Instructions"]
SEC --> GAME["Game (monolítico)"]
GAME --> ENT["Player* / Balloon* / Bullet* / Item* (punteros crudos)"]
GAME --> DEMOSYS["Demo (playback grabado)"] -.-> ENT
GAME -->|SDL_RenderTexture| CANVAS["game_canvas_ 256×192"]
CANVAS --> SCREEN[Screen] --> SB["ShaderBackend PostFX/CrtPi"] --> WIN[Ventana]
RES["Asset / Resource"] -.-> 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 new Director(argc, argv);
SDL_AppIterate Director::iterate(); // un frame
SDL_AppEvent Director::handleEvent(event);
SDL_AppQuit delete Director;
```
### 2.2. El `Director`
`source/core/system/director.h` / `.cpp`. Inicializa SDL, ventana y renderer,
crea la carpeta de sistema, monta input/audio/recursos, y mantiene **un
`unique_ptr` por sección** (`logo_`, `intro_`, `title_`, `game_`) de los que
**solo uno está vivo** (`director.h:55`). Guarda un puntero a la
`struct Section* section_` que comparte con la sección activa.
`Director::iterate()` cada frame: comprueba salida (doble ESC vía
`GlobalInputs::wantsQuit()`), actualiza la visibilidad del cursor, llama a
`handleSectionTransition()` y despacha `iterate()` a la sección activa
(`director.cpp`, `switch (active_section_)`).
### 2.3. Gestión del tiempo (en migración)
El reloj central es `source/core/system/delta_time.*`: `DeltaTime::tick()`
devuelve el delta en segundos consumido al inicio de cada frame de la sección
(`game.cpp`, `Game::iterate`). El proyecto **está migrando de frame-based a
time-based**: en `game.h` se ven contadores duplicados, el viejo frame-based
(`Uint16 death_counter_`) y el nuevo time-based (`float death_counter_s_`),
documentados como tales (`game.h:347`). El playback de la demo también es
time-based: `index = elapsed_s * 60` (`demo.hpp:11`).
---
## 3. Secciones y flujo de la aplicación
### 3.1. `struct Section`
`source/utils/utils.h:58` define un POD minimalista:
```cpp
struct Section {
Uint8 name; // SECTION_PROG_* (LOGO/INTRO/TITLE/GAME/QUIT)
Uint8 subsection; // SUBSECTION_* (p.ej. GAME_PLAY_1P, GAME_PAUSE, TITLE_INSTRUCTIONS…)
};
```
Los valores son **constantes `constexpr int` en `source/game/defaults.hpp`**
(`SECTION_PROG_LOGO = 0`, …, `SECTION_PROG_QUIT = 4`; `SUBSECTION_GAME_PLAY_1P`,
`SUBSECTION_GAME_PAUSE`, `SUBSECTION_GAME_GAMEOVER`, `SUBSECTION_TITLE_INSTRUCTIONS`,
etc.; `defaults.hpp:90`). Cualquier parte del código cambia el flujo asignando
`section_->name = ...` / `section_->subsection = ...`.
### 3.2. Transición de secciones
`Director::handleSectionTransition()` (`director.cpp`):
- Traduce `section_->name` a un `enum class ActiveSection` (`director.h:24`).
- Si coincide con la activa, no hace nada.
- Si cambió: libera las cuatro secciones (`reset()`) y construye la nueva. Para
`GAME` decide el nº de jugadores según `section_->subsection`
(`SUBSECTION_GAME_PLAY_1P` → 1, si no → 2) y crea
`Game(NUM_PLAYERS, 0, renderer_, /*demo=*/false, section_)`.
Cada sección recibe el `renderer_` y el `Section*`, y expone `iterate()` (y, en
`Game`, `handleEvent()`).
```mermaid
graph LR
LOGO --> INTRO --> TITLE
TITLE -->|jugar| GAME --> TITLE
TITLE -->|attract / manual| INSTR["Instructions (dentro de Title)"]
TITLE -->|attract| DEMOG["Game anidado en demo (dentro de Title)"]
TITLE --> QUIT
```
> **Matiz importante**: `Instructions` y la **demo** no son secciones del
> `Director`. Viven **dentro de `Title`**, que ejecuta un *attract loop* (ver
> §8). El `Director` solo conoce Logo/Intro/Title/Game/Quit.
---
## 4. Renderizado: de la lógica al píxel
Los sprites son **texturas GPU** dibujadas por `SDL_Renderer` sobre un canvas
virtual; el post-procesado va por un backend SDL3 GPU.
### 4.1. El canvas virtual 256×192
`Screen` (`core/rendering/screen.h`) crea `game_canvas_`, una `SDL_Texture` de
**256×192** (`GAMECANVAS_WIDTH/HEIGHT` en `defaults.hpp:64`) con
`SDL_TEXTUREACCESS_TARGET` (`screen.cpp:106`). Toda la geometría del juego se
deriva de esa resolución y de un `BLOCK` base (áreas de juego en
`defaults.hpp`).
`Screen::start()` (`screen.cpp:166`) fija el render-target a `game_canvas_`;
a partir de ahí, la sección activa dibuja sus sprites sobre él.
### 4.2. Texturas y jerarquía de sprites
- `core/rendering/texture.h``Texture` envuelve un `SDL_Texture*` cargado de
PNG; método `render(...)` con clip/zoom/flip.
- `core/rendering/sprite.h` y derivados:
- `Sprite` — dibuja desde un *spritesheet*.
- `AnimatedSprite` — animación por fotogramas, definida en ficheros **`.ani`**.
- `MovingSprite` — añade posición/velocidad (p.ej. las nubes del fondo).
- `SmartSprite` — sprite autónomo (popups de puntuación, el café que salta al
recibir un golpe).
- Texto: `core/rendering/text.h` + `writer.h` (fuentes bitmap).
- Transiciones: `core/rendering/fade.h`. Notificaciones:
`core/rendering/notifications.*`.
### 4.3. Post-procesado y presentación
El path de presentación (`screen.cpp:185`) decide cómo llega el canvas a la
ventana:
- **Con backend GPU acelerado**: lee los píxeles de `game_canvas_` con
`SDL_RenderReadPixels` a un `pixel_buffer_` (ARGB8888;
`screen.h:162`), los sube al backend (`shader_backend_->uploadPixels(...)`) y
este renderiza con el shader activo a la ventana.
- **Sin backend / desactivado** (fallback): `SDL_RenderTexture` del
`game_canvas_` a la ventana y `SDL_RenderPresent` (`screen.cpp:233`).
El backend vive en `core/rendering/sdl3gpu/` (interfaz abstracta en
`shader_backend.hpp`). Dos shaders: **PostFX** (viñeta, scanlines, chroma,
gamma, máscara, curvatura, *bleeding*, flicker) y **CrtPi** (scanlines continuas
con bloom). Los GLSL de `data/shaders/` se compilan a SPIR-V (`spv/*_spv.h`) vía
`glslc`; en macOS se usan shaders **Metal (MSL)** inline (`sdl3gpu/msl/`). El
build `NO_SHADERS` (Emscripten) fuerza la ruta clásica.
```mermaid
graph TD
OBJ["fondo, globos, jugador, balas, items…"] -->|SDL_RenderTexture| CANVAS["game_canvas_ 256×192 (render target)"]
CANVAS -->|RenderReadPixels → uploadPixels| SHADER["ShaderBackend (PostFX / CrtPi)"]
SHADER --> WIN[Ventana]
CANVAS -.fallback sin GPU.-> WIN
```
### 4.4. Modos de escalado y efectos
La presentación a la ventana respeta `Options::video.presentation_mode`
(`INTEGER_SCALE`, `LETTERBOX`, `STRETCHED`, `OVERSCAN`; `options.hpp:24`). El
`Game` añade efectos como *flash*, *shake* y un *death shake* intenso
(`game.h:100`, `DeathShake`).
---
## 5. Entrada
### 5.1. `Input`
`source/core/input/input.h` — abstracción de teclado y mando bajo un enum de
acciones (`Input::Action`). El jugador se mueve con flechas y dispara
izquierda/centro/derecha; el `Input::Device` selecciona teclado o mando por
jugador (`Game::player_one_control_`, `game.h:379`).
### 5.2. Hotkeys globales y salida
`source/core/input/global_inputs.*` gestiona las teclas de sistema (ventana,
vídeo, post-FX, idioma, FPS overlay…) y la **salida en dos pasos**: la primera
pulsación de ESC arma una confirmación y la segunda activa `wantsQuit()`, que el
`Director` traduce a `SECTION_PROG_QUIT` (`director.cpp`). El cursor del ratón
se autooculta (`core/input/mouse.*`).
Las hotkeys de shaders documentadas: **F4** activa/desactiva post-procesado,
**F5** alterna PostFX↔CrtPi, **F6** siguiente preset (ver `CLAUDE.md`).
### 5.3. Cómo llega la entrada al jugador
Dentro de `Game`, `checkGameInput()``processLiveInput()`
`processPlayerLiveInput(player, i)` consulta `Input` y llama a
`player->setInput(Input::Action)` y a `createBullet(...)` al disparar
(`game.h:228`). En modo demo, esa misma vía la alimenta `processDemoInput()`
con datos grabados (§8).
---
## 6. Lógica del juego: la clase `Game`
`source/game/game.{h,cpp}` es **el hub de gameplay y, con diferencia, la clase
más grande** del proyecto: ~400 líneas solo de declaración. A diferencia de la
Arcade Edition, **no delega en managers**: las formaciones, fases, globos,
balas e ítems se gestionan directamente aquí.
### 6.1. El frame y sus sub-bucles
`Game::iterate()` (`game.cpp`) consume el delta con `DeltaTime::tick()` y
despacha según `section_->subsection`:
```cpp
switch (section_->subsection) {
case SUBSECTION_GAME_PAUSE: iteratePaused(dt); break;
case SUBSECTION_GAME_GAMEOVER: iterateGameOver(dt); break;
case SUBSECTION_GAME_PLAY_1P:
case SUBSECTION_GAME_PLAY_2P: iteratePlaying(dt); break;
}
```
Es decir, **pausa y game-over son sub-estados** del propio `Game` (no secciones
del Director), cada uno con su `update`/`render`. En modo demo, entrar en pausa
o game-over rebota directamente a `Title` (`game.cpp`, `Game::iterate`).
### 6.2. Lo que gestiona `Game`
Todo dentro de la misma clase (`game.h`):
- **Fases (`Stage stage_[10]`)**: cada fase tiene un *pool* de formaciones
enemigas (`EnemyPool`/`EnemyFormation`/`EnemyInit`, structs internos), poder
para completarla y umbrales de amenaza. `initEnemyFormations*` precalcula las
formaciones (lineales, simétricas, hexágonos…).
- **Nivel de amenaza** (`menace_current_`/`menace_threshold_`): si la amenaza
cae bajo el umbral, se despliega otra formación (`updateMenace`,
`evaluateAndSetMenace`).
- **Ítems y power-ups**: disco/gaviota/pacmar (puntos), café (toque extra),
máquina de café (power-up), reloj (**detener el tiempo**,
`enableTimeStopItem`), *power ball*. Probabilidades en `Helper`
(`game.h:128`).
- **Colisiones**: jugador↔globo, jugador↔ítem, bala↔globo (`checkPlayer…`,
`checkBulletBalloonCollision`).
- **Muerte del jugador**: secuencia con *death shake* y fases
(`DeathSequence`/`DeathPhase`, `game.h:113`).
- **Marcador, hi-score, fades, fondo** (nubes con parallax via `MovingSprite`),
menús de pausa y game-over (`Menu`), audio (`Ja::Sound*`/`Ja::Music*`).
### 6.3. Gestión de memoria
Las entidades viven como **vectores de punteros crudos**
(`std::vector<Player*>`, `<Balloon*>`, `<Bullet*>`, `<Item*>`,
`<SmartSprite*>`; `game.h:264`), creados con `new` y liberados con métodos
`freeBalloons()`, `freeBullets()`, `freeItems()`, `deleteAllVectorObjects()`.
Es un estilo más antiguo que el de la Arcade Edition (smart pointers).
---
## 7. Entidades
`source/game/entities/`:
- **`Player`** (`player.h`) — movimiento, disparo (tres direcciones),
animaciones, power-up, invulnerabilidad, vidas/score. Puede usar teclado o
mando.
- **`Balloon`** (`balloon.h`) — enemigo básico que rebota; al explotar puede
generar globos hijos. Tiene varios contadores de estado.
- **`Bullet`** (`bullet.h`) — proyectil con `Kind` (UP/LEFT/RIGHT) y estado de
power-up.
- **`Item`** (`item.h`) — power-ups y objetos de puntos que caen, con `Id` por
tipo.
No hay clase base de entidad común ni managers: el ciclo de vida lo lleva
`Game` directamente sobre los vectores (§6.3). Los efectos visuales tipo
"popup de puntuación" o "café arrojado" se modelan como `SmartSprite`
(`core/rendering/smartsprite.h`).
---
## 8. Modo demo y attract mode
> **El modo demo SÍ existe, y NO es IA**: es **reproducción de input
> pregrabado**, igual concepto que en la Arcade Edition.
### 8.1. Formato
`source/core/system/demo.hpp`: cada fotograma 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 "a 60 Hz de
referencia" (`demo.hpp:9`). Hay tres ficheros: `data/demo/demo{1,2,3}.bin`. El
playback es **time-based**: `index = elapsed_s * 60`.
### 8.2. Reproducción
Cuando un `Game` corre en modo demo, `processDemoInput()` (`game.cpp`) lee el
fotograma actual del set seleccionado y lo inyecta por la **misma vía que un
humano** sobre `players_[0]`:
```cpp
const DemoKeys &keys = dd.at(demo_.index % dd.size());
if (keys.left == 1) players_[0]->setInput(Input::Action::LEFT);
if (keys.fire == 1 && players_[0]->canFire()) {
players_[0]->setInput(Input::Action::FIRE_CENTER);
createBullet(...); players_[0]->setFireCooldown(10);
}
// … (right, no_input, fire_left, fire_right)
```
No hay toma de decisiones: repite las pulsaciones grabadas. Al agotar el
playback (`index >= TOTAL_DEMO_DATA`) vuelve a `Title`.
### 8.3. Attract mode (dentro de `Title`)
El bucle de atracción vive en `source/game/scenes/title.cpp`: el Title arma un
*timeout* (`demo_remaining_s_`) y, al agotarse, lanza un **`Game` anidado en
modo demo** (`runDemoGame()`, `demo_game_`, `demo_game_active_`;
`title.cpp:323`). Title tiquea ese `demo_game_->iterate()` directamente y, al
terminar la demo, encadena las **instrucciones**
(`demo_then_instructions_``runInstructions(Instructions::Mode::AUTO)`,
`title.cpp:334`) antes de volver al título. Así el Title alterna
atracción → demo → instrucciones de forma autónoma.
> Es una diferencia notable con la Arcade Edition, donde la demo es una sección
> `GAME_DEMO` propia del Director. Aquí el `Director` ni se entera: todo el
> attract está encapsulado en `Title`.
---
## 9. Recursos
- **`Asset`** (`core/resources/asset.h`) — índice de ficheros de recurso
(`add`/`get` por nombre).
- **`Resource`** (`core/resources/resource.h`) — carga y caché de los recursos
(texturas, sonidos, música, fuentes, animaciones).
- **Pack**: `resource_pack.*` + `resource_loader.*` + `resource_helper.*`
sirven desde **`resources.pack`**, con *fallback* al filesystem en desarrollo.
- **Formatos**: PNG (spritesheets) + ficheros **`.ani`** (definición de
animaciones); OGG (audio, vía `stb_vorbis`); fuentes bitmap en `data/font/`.
Los shaders GLSL de `data/shaders/` **no** van al pack (se embeben en el
binario como cabeceras SPIR-V).
---
## 10. Audio
`source/core/audio/``Audio` (`audio.hpp`) + `audio_adapter` sobre
**`jail_audio`** (`jail_audio.hpp`), wrapper de audio SDL3 *first-party* (no
librería externa) que usa `stb_vorbis` para OGG y mezcla por canales (API
`JA_*`). `Game` mantiene punteros `Ja::Sound*` para cada efecto (explosión,
disparo, colisión, reloj, etc.) y un `Ja::Music* game_music_`.
---
## 11. Configuración y constantes
- **`Options`** (`source/game/options.hpp`) — opciones persistentes en el
namespace `Options::` (`window`, `video` con `gpu`/`shader`, `audio`,
`loading`, `settings`, `gameplay`, `inputs`), más presets `PostFXPreset` y
`CrtPiPreset`.
> ⚠️ **El `CLAUDE.md` está desactualizado en este punto**: dice que la config
> vive en `config.txt` con "migración a YAML pendiente". El código real
> (`options.hpp:16`) ya **persiste en `config.yaml` vía fkyaml**, con presets
> de shaders en `postfx.yaml`/`crtpi.yaml`. El código manda.
- **`defaults.hpp`** (`source/game/`) — constantes de gameplay y layout: tamaño
de canvas (256×192), `BLOCK`, áreas de juego, colores, y las constantes
`SECTION_PROG_*` / `SUBSECTION_*` del flujo (§3).
- **`utils/defines.hpp`** — macros de build.
### Builds condicionales
Aparecen sobre todo en `Director`/`Screen`: `__EMSCRIPTEN__` (web: no se puede
salir, reinicia al logo; `NO_SHADERS` forzado), `DEBUG`, y la selección de
plataforma para shaders (SPIR-V vs Metal). `make release` empaqueta `.tar.gz` /
`.dmg` / `.zip` según el SO.
---
## 12. Localización
`source/core/locale/lang.*``Lang` carga las cadenas desde `data/lang/`
(es_ES, ba_BA/euskera, en_UK). El idioma se elige en `Options::settings.language`.
---
## 13. Convenciones y patrones recurrentes
- **Cabeceras mixtas `.h` / `.hpp`**: `.h` en lo antiguo, `.hpp` en lo nuevo —
pista fiable de qué módulos se han reescrito.
- **Punteros crudos + `new`/`delete`** en el gameplay (`Game`), frente a smart
pointers en el resto (secciones, `Screen`). Migración a medias.
- **Migración frame-based → time-based**: contadores duplicados
(`x_counter_` + `x_counter_s_`) conviviendo; el reloj es `DeltaTime::tick()`.
- **Flujo por `struct Section` + constantes `SECTION_PROG_*`** (no enums
tipados ni objetos de transición).
- **Sub-estados dentro de la sección** (pausa/game-over como `subsection` de
`Game`), no como secciones del `Director`.
- **Attract mode encapsulado en `Title`** (demo + instrucciones).
- **`Game` monolítico**: la lógica no está repartida en managers; todo cuelga
de la clase `Game` y de structs internos (`Stage`, `EnemyFormation`, …).
- **Comentarios** en español/valenciano; muchos `#include` con comentario
"// for X" (estilo IWYU).
- **El `CLAUDE.md` puede ir por detrás del código** (caso config.txt→YAML): ante
duda, manda el código.
---
## 14. Guía de navegación: "si quieres tocar X, mira Y"
| Quiero… | Empieza por… |
|---|---|
| Entender el arranque | `source/core/system/director.cpp` |
| Cambiar el flujo de pantallas | `struct Section` (`utils/utils.h`) + constantes en `game/defaults.hpp` + `handleSectionTransition` |
| Añadir/editar una pantalla | `source/game/scenes/` (Logo/Intro/Title/Instructions) |
| Gestión del tiempo | `source/core/system/delta_time.*` |
| Cómo se dibuja todo | `Screen::start`/render (`core/rendering/screen.cpp`) |
| Canvas / resolución / áreas | `source/game/defaults.hpp` (256×192, BLOCK) |
| Sprites / animaciones `.ani` | `core/rendering/sprite.h` + `animatedsprite.h` + `texture.h` |
| Shaders / CRT / post-FX | `core/rendering/sdl3gpu/` + `data/shaders/` + `Options` |
| Modos de escalado / efectos | `Screen` + `Options::video.presentation_mode` |
| Controles / mandos | `core/input/input.h` |
| Hotkeys / salida en dos pasos | `core/input/global_inputs.cpp` |
| **Toda la lógica de partida** | `source/game/game.cpp` (`iteratePlaying/Paused/GameOver`) |
| Fases / formaciones / amenaza | `Game::initEnemyFormations*`, `Stage stage_[10]`, `updateMenace` |
| Globos / balas / ítems | `game/entities/{balloon,bullet,item}.*` (gestionados en `Game`) |
| El jugador | `game/entities/player.*` |
| Ítems y power-ups | `Game::dropItem/createItem`, `Helper` (`game.h`) |
| **Modo demo / attract** | `core/system/demo.*`, `Game::processDemoInput`, `scenes/title.cpp` (`runDemoGame`) |
| Cargar un recurso | `core/resources/asset.h` + `resource.h` |
| Audio | `core/audio/audio.hpp` + `jail_audio.hpp` |
| Opciones del usuario | `game/options.hpp` (+ `config.yaml`) |
| Valores por defecto / constantes | `game/defaults.hpp`, `utils/defines.hpp` |
| Idiomas | `core/locale/lang.*` + `data/lang/` |
| Empaquetar datos | `tools/` + `make pack` |
---
## 15. Diferencias frente a la Arcade Edition
`coffee_crisis` es el **predecesor** de `coffee_crisis_arcade_edition`. Ambos
comparten ADN (SDL3, `jail_audio`, demo por input grabado, backend SDL3 GPU con
PostFX/CrtPi, capas core/game), pero el código diverge de forma sistemática.
Resumen de lo observado leyendo ambos repos:
| Dimensión | **Coffee Crisis (este)** | **Arcade Edition** |
|---|---|---|
| Tamaño | ~51 ficheros, ~16k LOC | ~150 ficheros, ~32k LOC |
| Cabeceras | mixto `.h` (antiguo) / `.hpp` | todo `.hpp` |
| Flujo | `struct Section{name,subsection}` + `enum ActiveSection`; **4 secciones** (Logo/Intro/Title/Game) | variable global `Section::name` con **muchas más** (Preload, HiScore, Credits, GameDemo, Instructions…) |
| Arranque | directo | **no bloqueante** con sección `PRELOAD` + `Resource::loadStep(50ms)` |
| Gameplay | **`Game` monolítico**; formaciones/fases como structs internos | **managers** (`BalloonManager`, `BulletManager`, `StageManager` con `IStageInfo`) |
| Memoria de entidades | **punteros crudos** + `new`/`delete` | `shared_ptr`/`unique_ptr` + listas |
| Pausa / game-over | **sub-estados** dentro de `Game` (`subsection`) | FSM de estados de `Game` + managers dedicados |
| Demo / attract | **encapsulado en `Title`** (Game anidado en demo + instrucciones) | sección `GAME_DEMO` propia del Director + attract Title↔Logo/Demo |
| Canvas | **256×192** fijo (`defaults.hpp`) | parametrizable (`param_320x*.txt`) |
| Render | un único `game_canvas_` → readback → shader / fallback | dos render-targets (`canvas_` zona de juego → `game_canvas_`) → shader / fallback |
| Sprites | `Sprite/AnimatedSprite/MovingSprite/SmartSprite` | añade `PathSprite`, `CardSprite` |
| Reinicio en caliente | (no observado a nivel de `relaunch()`) | `Director::relaunch()` vía `execv` |
| Plataformas | Linux/macOS/Windows/Emscripten | + Raspberry Pi, Anbernic |
| Estado del código | **en migración**: frame→time-based, `config.txt`→YAML | más consolidado (YAML, time-based) |
En una frase: la Arcade Edition es esta misma idea **refactorizada y ampliada**
— se troceó el `Game` monolítico en managers, se pasó a smart pointers, se
añadieron secciones y plataformas, y se consolidó la migración a time-based y
YAML que aquí todavía está a medias.
---
*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.*