merge docs/arquitectura: guia d'arquitectura del projecte
This commit is contained in:
@@ -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.*
|
||||
Reference in New Issue
Block a user