@@ -0,0 +1,542 @@
# 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 1– 2 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.*