Files
jaildoctors-dilemma/docs/ARQUITECTURA.md
T

554 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 **JailDoctor's Dilemma**
> 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 el código
> contradice a la documentación previa (`CLAUDE.md`), lo señalo: **manda el
> código**.
>
> **JailDoctor's Dilemma** es un *puzzle-platformer* 2D retro en C++20 + SDL3:
> 60+ habitaciones interconectadas, ítems coleccionables, enemigos y logros.
> Resolución de juego **256×192**. 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. [Escenas y flujo de la aplicación](#3-escenas-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 escena `Game`](#6-lógica-del-juego-la-escena-game)
7. [Habitaciones y colisión](#7-habitaciones-y-colisión)
8. [Entidades](#8-entidades)
9. [Logros, estadísticas y marcador](#9-logros-estadísticas-y-marcador)
10. [Editor de mapas (Debug)](#10-editor-de-mapas-debug)
11. [Consola y notificaciones](#11-consola-y-notificaciones)
12. [Modo demo](#12-modo-demo)
13. [Recursos](#13-recursos)
14. [Audio, localización y configuración](#14-audio-localización-y-configuración)
15. [Convenciones y patrones recurrentes](#15-convenciones-y-patrones-recurrentes)
16. [Guía de navegación: "si quieres tocar X, mira Y"](#16-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` (`director`, `debug`,
`global_events`), `rendering` (+ `sprite`, `sdl3gpu`), `input`, `resources`,
`audio`, `locale`.
- **`source/game/`** — el juego concreto: `scenes/`, `gameplay/`, `entities/`,
`editor/`, `ui/`, `options.*`, `scene_manager.hpp`, `defaults.hpp`.
- **`source/utils/`** — `delta_timer`, `easing_functions`, `utils`, `defines`.
- **`source/external/`** — vendorizado: `fkyaml`, `stb_image`, `stb_vorbis`.
Es el proyecto más grande de su familia: **138 ficheros C++, ~54.000 líneas**.
**Ideas-fuerza que conviene interiorizar:**
1. **Render paletizado por CPU**: `Surface` de 8 bits indexados + paleta, igual
filosofía que un motor retro clásico; la GPU solo escala y aplica post-FX
(§4).
2. **Flujo por `SceneManager::current`** (variable global) + un único
`active_scene_` que el `Director` conmuta (§3).
3. **El mundo son habitaciones** de 256×128 px en tiles de 8 px, con colisión
por superficies (suelos, paredes, rampas, cintas) y transición entre salas
contiguas (§7).
4. Trae **editor de mapas** y **consola de comandos** integrados (solo Debug)
(§10, §11), un **sistema de logros** persistente (§9), y un **modo demo**
que es un *tour de habitaciones* (§12).
```mermaid
graph TD
SDL[SDL3 callbacks · main.cpp] --> DIR[Director]
DIR -->|SceneManager::current| SW{switchToActiveScene}
SW --> SCN["BootLoader / Logo / Title / Game / Demo / Ending…"]
SCN --> GAME["Game (Mode GAME/DEMO)"]
GAME --> ROOM[Room + colisión] & RL[room_loader]
GAME --> PLAYER[Player] & EM[enemy_manager] & IM[item_manager]
GAME --> CHV[Cheevos] & STT[Stats] & SCB[Scoreboard]
GAME -->|blit paletizado| SURF["Surface (8-bit indexed)"]
SURF -->|toARGBBuffer / copyToTexture| SCREEN[Screen]
SCREEN --> GPU["ShaderBackend PostFX/CrtPi"] --> WIN[Ventana]
SCREEN -.fallback.-> WIN
RES["Resource::Cache / List"] -.-> GAME & SCN
EDIT["MapEditor (Debug)"] -.-> GAME
CON[Console] -.-> GAME
```
---
## 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.
```cpp
SDL_AppInit new Director();
SDL_AppIterate Director::iterate(); // un frame
SDL_AppEvent Director::handleEvent(event);
SDL_AppQuit delete Director;
```
> ⚠️ **Discrepancia con el `CLAUDE.md`**: este describe un `Director::run()` con
> un bucle `while (SceneManager::current != QUIT)`. **El código actual no es
> así**: usa la API de callbacks de SDL3. El `Director` real
> (`core/system/director.hpp`) expone `iterate()` y `handleEvent()`, no `run()`.
### 2.2. El `Director`
`source/core/system/director.{hpp,cpp}`. Mantiene **un solo
`std::unique_ptr<Scene> active_scene_`** y un enum `current_scene_`. No guarda un
puntero por escena (a diferencia de los proyectos hermanos): construye la escena
bajo demanda en `switchToActiveScene()` (`director.cpp`).
El constructor inicializa los subsistemas en orden: `Resource::List` (registro
de assets desde `config/assets.yaml`), `Options`, `Audio`, `Screen`, `Input`,
`Resource::Cache` (con `beginLoad()`), y arranca en la escena `BOOT_LOADER`.
### 2.3. Arranque NO bloqueante
`Resource::Cache` no se carga de golpe. El constructor deja la escena en
`BOOT_LOADER` (una barra de progreso) y cada frame `Director::iterate()` llama a
`Resource::Cache::get()->loadStep(50 /*ms*/)` (`director.cpp`): carga assets
hasta agotar un presupuesto de 50 ms por frame, manteniendo ventana y eventos
vivos. Cuando termina, `finishBoot()` inicializa lo que depende de los recursos
(`Notifier`, `RenderInfo`, `Console`, `Cheevos`, `Locale`, en Debug `Debug` y
`MapEditor`) y fija la escena destino (`LOGO` en release; en Debug, la que diga
`debug.yaml` vía `Debug::getInitialScene()`).
### 2.4. Gestión del tiempo
**Time-based**: la escena `Game` posee un `DeltaTimer delta_timer_`
(`utils/delta_timer.hpp`) y toda la física/animación consume `delta_time` en
segundos. Las constantes de tiempo se documentan como "N frames a 66.67 fps →
segundos" (p.ej. `BLACK_SCREEN_DURATION = 0.30F`, `game.hpp:46`).
---
## 3. Escenas y flujo de la aplicación
### 3.1. La base `Scene` y el `SceneManager`
`source/game/scenes/scene.hpp` es minimalista:
```cpp
class Scene {
public:
virtual void iterate() = 0; // un frame (update + render)
virtual void handleEvent(const SDL_Event&) = 0; // un evento
};
```
`source/game/scene_manager.hpp` define el flujo con **variables globales
`inline`** en el namespace `SceneManager`:
```cpp
enum class Scene { BOOT_LOADER, LOGO, LOADING_SCREEN, TITLE, CREDITS,
GAME, DEMO, GAME_OVER, ENDING, ENDING2, RESTART_CURRENT, QUIT };
inline Scene current = Scene::BOOT_LOADER;
inline Options options = Options::LOGO_TO_LOADING_SCREEN;
inline Scene scene_before_restart = Scene::LOGO;
```
Cualquier escena solicita una transición asignando `SceneManager::current`.
### 3.2. La conmutación
`Director::switchToActiveScene()` (`director.cpp`):
- `RESTART_CURRENT` es especial: restaura `scene_before_restart` (relanza la
escena que estaba activa).
- `active_scene_.reset()` destruye la anterior (su destructor puede parar la
música, etc.).
- Un `switch` construye la concreta: `BootLoader`, `Logo`, `LoadingScreen`,
`Title`, `Credits`, `Game(Mode::DEMO)`, `Game(Mode::GAME)`, `GameOver`,
`Ending`, `Ending2`.
Nótese que **DEMO y GAME son la misma clase `Game`**, parametrizada por
`Game::Mode` (§12).
```mermaid
graph LR
BOOT[BOOT_LOADER] --> LOGO --> LOADING[LOADING_SCREEN] --> TITLE
TITLE -->|jugar| GAME --> ENDING --> ENDING2 --> CREDITS
TITLE -->|attract| DEMO --> TITLE
GAME --> GAME_OVER --> TITLE
TITLE --> QUIT
```
---
## 4. Renderizado: de la lógica al píxel
El render es **paletizado por CPU**: se dibuja sobre superficies de 8 bits
indexados y solo al final se sube a la GPU.
### 4.1. `Surface`: 8 bits indexados + paleta
`source/core/rendering/surface.hpp`. Una `Surface` guarda los píxeles como
**índices `Uint8`** (`SurfaceData`) más una **`Palette` de 256 colores ARGB** y
una **`SubPalette`** (remapeo de índices, identidad por defecto vía `std::iota`).
Operaciones clave:
- `render(...)` / `renderWithColorReplace(src, dst)` — blit con color
transparente y reemplazo de índice (para recolorear sprites/glifos).
- `renderWithVerticalFade(...)` — disolución por hash 2D (cantos).
- `fadePalette()` / `fadeSubPalette()` — fundidos manipulando la paleta.
- `copyToTexture(...)` y **`toARGBBuffer(buffer)`** — vuelcan la surface a una
`SDL_Texture` o a un buffer ARGB externo.
Sobre `Surface` se construyen los sprites (`core/rendering/sprite/`):
`Sprite``AnimatedSprite` (frames `.yaml`) → `MovingSprite` (posición/velocidad)
y `DissolveSprite` (transición). Texto: `text.*`. Efectos: `pixel_reveal.*`.
Paletas: `palette_manager.*` (el juego permite **cambiar de paleta en caliente**;
ver §5).
### 4.2. `Screen` y la composición
`source/core/rendering/screen.{hpp,cpp}`. Hay dos superficies/texturas:
- **`game_surface_` / `game_texture_`** — el canvas de juego 256×192
(`SDL_TEXTUREACCESS_STREAMING`, ARGB8888; `screen.cpp:125`).
- **`border_surface_` / `border_texture_`** — el borde/overscan alrededor del
canvas.
El path de presentación (`Screen::render`, `screen.cpp:197`):
- **Con backend GPU acelerado**: vuelca las superficies a buffers ARGB
(`toARGBBuffer`) y los sube al `shader_backend_` (`uploadPixels`), que renderiza
con el shader activo.
- **Sin backend** (fallback): `copyToTexture` + `SDL_RenderTexture` de
`game_texture_` y `border_texture_` a la ventana.
El backend vive en `core/rendering/sdl3gpu/` (interfaz `shader_backend.hpp`). Dos
shaders: **PostFX** y **CrtPi** (scanlines, curvatura, máscara, etc.), GLSL en
`data/shaders/` compilados a SPIR-V (`spv/*_spv.h`), o Metal (MSL) en macOS
(`sdl3gpu/msl/`). En Emscripten (`NO_SHADERS`) se fuerza la ruta clásica.
```mermaid
graph TD
OBJ["room, player, enemies, items, HUD…"] -->|blit índices| GS["game_surface_ (256×192, 8-bit)"]
BORDER["borde / overscan"] --> BS[border_surface_]
GS -->|toARGBBuffer / copyToTexture| SCREEN[Screen]
BS --> SCREEN
SCREEN -->|uploadPixels| SHADER["ShaderBackend (PostFX / CrtPi)"]
SHADER --> WIN[Ventana]
SCREEN -.fallback SDL_Renderer.-> WIN
```
---
## 5. Entrada
### 5.1. `Input`
`source/core/input/input.{hpp,cpp}` + `input_types.*` — abstracción de teclado y
mando bajo un enum `InputAction`. Las vinculaciones se aplican desde `Options`
(`Input::applyKeyboardBindingsFromOptions()` /
`applyGamepadBindingsFromOptions()`, `director.cpp`). `mouse.*` gestiona el ratón
(usado sobre todo por el editor).
### 5.2. Hotkeys globales
`source/core/input/global_inputs.{hpp,cpp}` traduce eventos a acciones de sistema
(`global_inputs.cpp`):
- **Ventana/vídeo**: fullscreen, zoom ±, integer scale, vsync, info.
- **Shaders**: toggle; con **Ctrl** → siguiente shader, con **Shift**
siguiente preset.
- **Paletas**: siguiente / anterior (`NEXT_PALETTE`/`PREVIOUS_PALETTE`), y orden
de paleta — una seña de identidad de este juego (paleta intercambiable).
- **Borde** (overscan) toggle, **consola** toggle, **EXIT**.
- En `GAME`, EXIT vuelve a `TITLE`; el `QUIT` global sale del programa.
Cuando la **consola está activa**, `EXIT`/`ACCEPT` se redirigen a ella en vez de
a la escena (`global_inputs.cpp:231`).
---
## 6. Lógica del juego: la escena `Game`
`source/game/scenes/game.{hpp,cpp}` es la escena de gameplay. Hereda de `Scene` y
coordina habitación, jugador, enemigos, ítems, marcador, estadísticas y logros.
### 6.1. FSM de la escena
`Game::State` (`game.hpp:28`): `PLAYING → BLACK_SCREEN → GAME_OVER →
FADE_TO_ENDING → POST_FADE_ENDING`. Cada estado tiene su `updateX`/`renderX`;
`transitionToState()` cambia de estado y resetea los timers. El modo
(`Game::Mode::GAME` o `DEMO`) condiciona el comportamiento.
### 6.2. El frame
`Game::iterate()` calcula el delta con el `DeltaTimer`, llama a `update()`
(input + lógica + colisiones + cambio de sala) y a `render()`. El render del
estado `PLAYING` va directo a las superficies; los fades de fin de juego usan un
`game_backbuffer_surface_`.
### 6.3. Qué gestiona
- **Habitación activa** (`std::shared_ptr<Room> room_`) y cambio de sala al tocar
un borde (`changeRoom`, `checkPlayerIsOnBorder`; §7).
- **Jugador** (`Player`), con muerte (`killPlayer`, `BLACK_SCREEN`), y la "Jail"
que restaura vidas con el tiempo (`checkRestoringJail`, `JAIL_RESTORE_INTERVAL`).
- **Colisiones**: jugador↔enemigos (`checkPlayerAndEnemies`) y jugador↔ítems
(`checkPlayerAndItems`).
- **Progresión**: `RoomTracker` (salas visitadas), `Stats`, `Scoreboard`, fin de
juego (`checkEndGame`) y secuencias de **ending** (fades a `ENDING`/`ENDING2`).
- **Logros**: `checkSomeCheevos`, `checkEndGameCheevos` (§9).
---
## 7. Habitaciones y colisión
`source/game/gameplay/room.{hpp,cpp}` modela cada sala. Geometría: tiles de
**8 px**, mapa de **32×16** tiles (256×128 px). Los tipos de tile
(`Room::Tile`) son `EMPTY, WALL, PASSABLE, SLOPE_L, SLOPE_R, KILL, ANIMATED`
(`room.hpp:32`).
### 7.1. Datos de sala
`Room::Data` (`room.hpp:42`) se carga de YAML (vía `RoomLoader`,
`Room::loadYAML`). Contiene número/nombre, colores (fondo, borde, ítems),
**salas contiguas** (`upper_room`, `lower_room`, `left_room`, `right_room`
navegación tipo *metroidvania*), tileset, el `tile_map` embebido, y las listas
de enemigos e ítems.
### 7.2. Colisión por superficies
La colisión no es AABB simple contra tiles, sino consultas de **superficies**:
`checkRightSurfaces`, `checkLeftSurfaces`, `checkTopSurfaces`,
`checkBottomSurfaces`, `checkAutoSurfaces` (cintas), más rampas
(`checkLeftSlopes`/`checkRightSlopes`, `getSlopeHeight`, `getSlopeAtPoint`) y
cintas transportadoras (`checkConveyorBelts`, `conveyor_belt_direction_`). El
jugador aporta puntos de colisión finos (8 `collider_points_` + `under_left_foot_`
/ `under_right_foot_`; `player.hpp:147`). Los tiles `KILL` matan al jugador.
Subobjetos de `Room`: `CollisionMap` (datos de colisión), `TilemapRenderer`
(dibujo del tilemap), `EnemyManager` e `ItemManager` (ciclo de vida de enemigos
e ítems de la sala). `RoomTracker` (`gameplay/room_tracker.*`) registra las salas
visitadas.
---
## 8. Entidades
`source/game/entities/`:
- **`Player`** (`player.hpp`) — física *time-based* con `JUMP_VELOCITY = -80`,
`GRAVITY_FORCE = 155.6` px/s² (`player.hpp:42`). FSM de estados (IDLE/WALKING/
JUMPING/…), colisión por 8 puntos + "pies", controladores de sonido de salto y
caída (`JumpSoundController`/`FallSoundController`), y un `SpawnData` para
reaparecer (también usado al cambiar de sala conservando velocidad).
- **`Enemy`** (`enemy.hpp`) — enemigos con datos (`Enemy::Data`), colisión AABB,
gestionados por `EnemyManager` (`gameplay/enemy_manager.*`).
- **`Item`** (`item.hpp`) — coleccionables (`Item::Data`), gestionados por
`ItemManager` (`gameplay/item_manager.*`) y rastreados por `ItemTracker`.
No hay una clase base de entidad común con polimorfismo profundo: cada tipo tiene
su `update`/`render`/colisión y su *manager* dedicado dentro de la `Room`.
---
## 9. Logros, estadísticas y marcador
- **`Cheevos`** (`source/game/gameplay/cheevos.{hpp,cpp}`) — singleton del sistema
de logros. `unlock(id)`, `setUnobtainable(id)`, `getTotalUnlockedAchievements()`;
estado **persistido en `cheevos.bin`** (`loadFromFile`/`saveToFile`). La escena
`Game` llama a `checkSomeCheevos`/`checkEndGameCheevos`, y el `Notifier` muestra
el logro en pantalla (§11).
- **`Stats`** (`gameplay/stats.*`) — diccionario de estadísticas de partida
(`initStats`).
- **`Scoreboard`** (`gameplay/scoreboard.*`) — datos y dibujo del marcador
(`Scoreboard::Data` se comparte por `shared_ptr` con la sala y el editor).
- **`ItemTracker`** / **`RoomTracker`** — progreso de ítems recogidos y salas
visitadas.
---
## 10. Editor de mapas (Debug)
`source/game/editor/`**solo se compila en `_DEBUG`** (todo el header de
`MapEditor` está bajo `#ifdef _DEBUG`, `map_editor.hpp:3`). Es un editor de
habitaciones *in-game* completo, integrado con la escena `Game` y con la consola.
### 10.1. `MapEditor` (singleton)
`map_editor.hpp`. Se entra con `enter(room, player, room_path, scoreboard_data)`
sobre la sala viva. Funcionalidades:
- **Pintado de tiles** con *brush* (`brush_tile_`, `ERASER_BRUSH`, `painting_`),
preview bajo el cursor y rejilla opcional (`renderGrid`, `settings_.grid`).
- **Drag & drop** de jugador, enemigos (posición inicial y *bounds* de patrulla)
e ítems (`DragTarget`, `DragState`, `handleMouseDown/Up`, `updateDrag`), con
*snap* a rejilla.
- **Edición de propiedades** de enemigos, ítems y de la sala
(`setEnemyProperty`, `setItemProperty`, `setRoomProperty`, colores, color de
fondo…), invocables tanto por teclas como por **comandos de consola**.
- **Gestión de salas**: crear (`createNewRoom(direction)`), borrar (`deleteRoom`),
con conexión a las salas contiguas.
- **Persistencia**: `autosave()` + `room_saver.*` escribe el YAML de la sala;
`revert()` restaura desde el backup del nodo YAML (`yaml_backup_`).
### 10.2. Subcomponentes del editor
- **`TilePicker`** (`tile_picker.*`) — selector visual de tiles del tileset
(`openTilePicker`).
- **`MiniMap`** (`mini_map.*`) — minimapa de salas con conexiones, colores
configurables (`setMiniMapBg`/`setMiniMapConn`).
- **`EditorStatusBar`** (`editor_statusbar.*`) — barra de estado con info de
edición (`updateStatusBarInfo`).
- **`RoomSaver`** (`room_saver.*`) — serialización de la sala a YAML preservando
campos no editados.
El editor guarda/restaura estado del juego al entrar/salir (invencibilidad,
overlay de info) para no contaminar la partida.
---
## 11. Consola y notificaciones
### 11.1. `Console`
`source/game/ui/console.{hpp,cpp}` — consola de comandos *in-game* (singleton),
con estética de terminal verde sobre `Surface` propia. Características
(`console.hpp`):
- Panel animado (`Status` HIDDEN/RISING/ACTIVE/VANISHING), efecto *typewriter*,
cursor parpadeante.
- **Historial** navegable (flechas), **autocompletado por TAB** (`tab_matches_`),
*word-wrap* por ancho en píxeles.
- **`CommandRegistry`** (`console_commands.{hpp,cpp}`): metadatos (desde YAML) +
*handlers* C++. Los comandos cubren depuración del juego y **pilotan el editor
de mapas** (`setEnemyProperty`, `addItem`, `setRoomProperty`, etc.).
- **Scopes** (`setScope`/`getScope`): filtran qué comandos y autocompletados
están disponibles según el contexto (p.ej. dentro del editor).
- `on_toggle` notifica a la escena cuando se abre/cierra (para pausar input de
juego).
### 11.2. `Notifier`
`source/game/ui/notifier.{hpp,cpp}` — cola de notificaciones en pantalla (logros
desbloqueados, cambios de opción…). Se inicializa en `finishBoot()` y el `Screen`
las pinta como overlay (`renderNotifications`). El overlay de FPS/driver es
`core/rendering/render_info.*` (toggle por hotkey).
---
## 12. Modo demo
> **El modo demo de este juego NO es reproducción de input grabado.** Es un
> **tour automático de habitaciones** (escaparate de niveles).
La escena `Game` construida con `Game::Mode::DEMO` recorre una **lista curada de
salas** y va cambiando cada `DEMO_ROOM_DURATION = 6.0F` segundos
(`game.hpp:48`):
```cpp
// game.cpp — demoInit()
demo_ = DemoData(0.0F, 0, {"04.yaml","54.yaml","20.yaml","09.yaml",
"05.yaml","11.yaml","31.yaml","44.yaml"});
// demoCheckRoomChange(): acumula delta_time y, al llegar a 6s,
// avanza demo_.room_index y changeRoom(...). Al agotar la lista, vuelve.
```
No hay ficheros `.bin` ni `DemoKeys`: la demo simplemente pasea por las
habitaciones para la pantalla de atracción. La salida de la demo devuelve a la
escena de título.
---
## 13. Recursos
- **`Resource::List`** (`core/resources/resource_list.*`) — registro de rutas de
asset cargado de **`config/assets.yaml`**, con consulta `get(filename)` O(1).
- **`Resource::Cache`** (`core/resources/resource_cache.*`) — caché de surfaces,
música, sonidos y datos de animación (`getSurface`, `getMusic`,
`getAnimationData`). Carga **incremental** vía `beginLoad()` + `loadStep(ms)`
(§2.3), con una FSM interna de etapas (`LoadStage`).
- **Pack y fallback**: `resource_pack.*` + `resource_loader.*` + `resource_helper.*`
sirven desde **`resources.pack`** (release) o el filesystem (desarrollo).
- **Formatos**: GIF (gráficos + paletas, `core/rendering/gif.*`); `.yaml` para
animaciones y para las salas (`data/room/`); `.pal` para paletas
(`data/palette/`); OGG/WAV para audio; GLSL para shaders.
---
## 14. Audio, localización y configuración
- **Audio**: `core/audio/audio.*` (singleton, música y SFX) + `audio_adapter.*`
sobre **`jail_audio`** (`jail_audio.hpp`), wrapper SDL3 *first-party* con
`stb_vorbis` para OGG.
- **Localización**: `core/locale/locale.*` carga las cadenas de `data/locale/`.
En release el locale vive dentro del pack (`Locale::initFromContent`);
`Options::language` selecciona el idioma.
- **Configuración**: `source/game/options.{hpp,cpp}` mantiene las opciones
(ventana, vídeo+shaders, audio, idioma, controles, presets PostFX/CrtPi) y las
persiste; `source/game/defaults.hpp` reúne las constantes de gameplay y layout
(canvas 256×192, tamaños de tile, colores de paleta). En Debug, `debug.yaml`
(`core/system/debug.*`) fija la escena inicial.
- **Builds condicionales**: `_DEBUG` (editor, consola, overlay), `RELEASE_BUILD`,
`__EMSCRIPTEN__` (locale/paths especiales, `NO_SHADERS`), y la selección de
shaders por plataforma (SPIR-V vs Metal).
---
## 15. Convenciones y patrones recurrentes
- **Singletons con `init()`/`destroy()`/`get()`**: `Screen`, `Input`, `Audio`,
`Resource::Cache`, `Resource::List`, `Cheevos`, `Console`, `Notifier`,
`RenderInfo`, `MapEditor`, `Debug`. Se crean/destruyen en orden explícito desde
el `Director` (no por destructores estáticos).
- **Render paletizado por CPU** (`Surface` de 8 bits + `Palette`/`SubPalette`),
con recoloreado por reemplazo de índice y paletas intercambiables en caliente.
- **Flujo por variable global** (`SceneManager::current`) + un único
`active_scene_`.
- **Time-based**: todo consume `delta_time` (`DeltaTimer`); las constantes citan
su equivalencia en frames a 66.67 fps.
- **`#ifdef _DEBUG`** envuelve editor, consola de propiedades, overlays y atajos
de depuración — ausentes en release.
- **Comentarios** en español/valenciano; muchos `#include` con comentario
"// Para X" (estilo IWYU).
- **El `CLAUDE.md` puede ir por detrás del código** (caso `Director::run()` vs
callbacks). Ante duda, **manda el código**.
---
## 16. Guía de navegación: "si quieres tocar X, mira Y"
| Quiero… | Empieza por… |
|---|---|
| Entender el arranque | `core/system/director.cpp` (ctor, `iterate`, `finishBoot`) |
| Cambiar el flujo de pantallas | `game/scene_manager.hpp` + `Director::switchToActiveScene` |
| Añadir/editar una pantalla | `game/scenes/` (hereda de `Scene`) + un `case` en `switchToActiveScene` |
| La barra de carga / arranque | `Resource::Cache::beginLoad/loadStep` + `scenes/boot_loader.*` |
| Cómo se dibuja todo | `core/rendering/surface.*` + `Screen::render` (`screen.cpp`) |
| Sprites / animaciones | `core/rendering/sprite/` + `data/**/*.yaml` (animaciones) |
| Paletas / recoloreado | `core/rendering/palette_manager.*` + `Surface` |
| Shaders / CRT | `core/rendering/sdl3gpu/` + `data/shaders/` |
| Controles / hotkeys | `core/input/input.*` + `global_inputs.cpp` |
| **Lógica de partida** | `game/scenes/game.cpp` (`updatePlaying`, FSM `State`) |
| Habitaciones / colisión | `game/gameplay/room.*` (`check*Surfaces`, slopes, cintas) |
| Cargar una sala | `game/gameplay/room_loader.*` + `data/room/*.yaml` |
| El jugador (física) | `game/entities/player.*` (`JUMP_VELOCITY`, `GRAVITY_FORCE`, colisión por puntos) |
| Enemigos / ítems | `game/entities/{enemy,item}.*` + `gameplay/{enemy,item}_manager.*` |
| Logros | `game/gameplay/cheevos.*` (+ `cheevos.bin`) |
| Marcador / estadísticas | `game/gameplay/{scoreboard,stats,item_tracker,room_tracker}.*` |
| **Editor de mapas** | `game/editor/map_editor.*` (+ `tile_picker`, `mini_map`, `room_saver`) |
| **Consola / comandos** | `game/ui/console.*` + `console_commands.*` |
| Notificaciones / FPS | `game/ui/notifier.*` + `core/rendering/render_info.*` |
| Modo demo (tour) | `Game::demoInit/demoCheckRoomChange` (`game.cpp`) |
| Cargar un recurso | `core/resources/resource_cache.*` + `resource_list.*` + `config/assets.yaml` |
| Audio | `core/audio/audio.*` + `jail_audio.hpp` |
| Idiomas | `core/locale/locale.*` + `data/locale/` |
| Opciones / constantes | `game/options.*`, `game/defaults.hpp` |
| Escena inicial en Debug | `core/system/debug.*` + `debug.yaml` |
---
*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.*