diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 9462cc3..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1,1095 +0,0 @@ -# CLAUDE.md - -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. - -## Project Overview - -This is **Orni Attack**, an **Asteroids-style game** originally written in **Turbo Pascal 7 for DOS** (1999), now being **migrated to modern C++20 with SDL3**. The game features a spaceship that must avoid and destroy enemies (pentagonal "ORNIs"). This is a **phased migration** preserving the original game feel. - -**Language**: All code, comments, and variable names are in **Catalan/Valencian** (preserved from original). - -**Current Status**: **BETA 3.0** - Modernized architecture with dynamic windows, modular code organization, viewport scaling, and auto-generated project metadata. - -## Build System - -Based on `/home/sergio/gitea/pollo` project structure, now with **CMake as build authority** and **automatic file discovery**. - -### Basic Build Commands - -```bash -make # Compile (delegates to CMake) -make debug # Debug build -make clean # Clean artifacts -./orni # Run -``` - -### Release Packaging - -```bash -make macos_release # macOS .app bundle + .dmg (Apple Silicon) -make linux_release # Linux .tar.gz -make windows_release # Windows .zip (requires MinGW on Windows) -make windows_cross # Cross-compile Windows from Linux/macOS -make rpi_release # Raspberry Pi ARM64 cross-compile -``` - -### Build Files - -- **CMakeLists.txt** - CMake configuration (C++20, SDL3, auto-discovers .cpp files) -- **Makefile** - Wrapper for compilation + complex packaging recipes -- **source/project.h.in** - Template for auto-generated project.h -- **build/project.h** - Auto-generated (by CMake) with project constants -- **release/** - Platform-specific resources (icons, .rc, .plist, frameworks, DLLs) - -### Architecture: Hybrid CMake + Makefile - -**CMake handles**: Compilation (simple, standard, IDE-friendly) -- Auto-discovers all `.cpp` files in `source/core/` and `source/game/` -- Excludes `source/legacy/` automatically -- Generates `build/project.h` from template -- Links SDL3 - -**Makefile handles**: Packaging (complex bash scripts) -- Delegates compilation to CMake (`make` → `cmake --build build`) -- Contains 5 release packaging targets (macOS, Linux, Windows, RPI, Windows-cross) -- Includes: code signing, framework symlinks, DMG creation, cross-compilation - -### Project Metadata System - -**Single source of truth** in `CMakeLists.txt`: - -```cmake -project(orni VERSION 0.3.0) -set(PROJECT_LONG_NAME "Orni Attack") -set(PROJECT_COPYRIGHT "© 2026 JailDesigner") -``` - -**Auto-generated** `build/project.h`: - -```cpp -namespace Project { - constexpr const char* NAME = "orni"; - constexpr const char* LONG_NAME = "Orni Attack"; - constexpr const char* VERSION = "0.3.0"; - constexpr const char* COPYRIGHT = "© 2026 JailDesigner"; - constexpr const char* GIT_HASH = "abc1234"; // From git rev-parse -} -``` - -**Window title** (estático): `© 2026 Orni Attack — JailDesigner` - -### File Discovery - -**Automatic** - no manual maintenance needed: - -```cmake -# CMakeLists.txt automatically finds: -file(GLOB_RECURSE CORE_SOURCES "source/core/*.cpp") -file(GLOB_RECURSE GAME_SOURCES "source/game/*.cpp") -# + source/main.cpp -# - source/legacy/* (excluded) -``` - -**When you create a new file** like `source/game/entities/asteroide.cpp`: -1. Just create it in the appropriate directory -2. Run `make` -3. CMake automatically detects and compiles it - -**No need to edit** Makefile or CMakeLists.txt! - -### Cross-Platform Notes - -- **macOS**: Requires `create-dmg` (auto-installed via Homebrew) -- **Windows**: Compile natively with MinGW or use `make windows_cross` on Linux/macOS -- **Windows cross**: Requires `x86_64-w64-mingw32-g++` toolchain -- **RPI cross**: Requires `aarch64-linux-gnu-g++` toolchain -- **Frameworks**: macOS release includes SDL3.xcframework with symlink recreation - -## Architecture - -### File Structure (BETA 3.0) - -``` -source/ -├── core/ - Reusable engine layer -│ ├── defaults.hpp - Configuration constants (SINGLE SOURCE OF TRUTH) -│ ├── types.hpp - Data structures (IPunt, Punt, Triangle, Poligon) -│ └── rendering/ -│ ├── sdl_manager.hpp/cpp - SDL3 window management + viewport scaling -│ └── primitives.hpp/cpp - Pure geometric functions -├── game/ - Asteroids-specific game logic -│ ├── constants.hpp - Legacy constant aliases -│ └── joc_asteroides.hpp/cpp - Game loop, physics, rendering -├── utils/ - Shared utilities (empty for now) -├── main.cpp - Entry point, F1/F2/F3 window controls -└── legacy/ - └── asteroids.cpp - Original Pascal code (reference only) -``` - -**Key architectural decisions:** -- **core/** contains reusable, game-agnostic code -- **game/** contains Asteroids-specific logic -- All constants centralized in `core/defaults.hpp` -- Backward compatibility via `game/constants.hpp` aliases - -### Core Data Structures - -The game uses **polar coordinates** for all geometric objects (preserved from Pascal original): - -```cpp -struct IPunt { - float r; // Radius - float angle; // Angle in radians -}; - -struct Punt { - int x, y; // Cartesian coordinates -}; - -struct Triangle { // Player's ship (nau_) - IPunt p1, p2, p3; // 3 polar points - Punt centre; // Center position - float angle; // Orientation - float velocitat; // Speed (px/s) -}; - -struct Poligon { // Enemies (orni_) and bullets (bales_) - std::array ipuntx; // Polar points - Punt centre; - float angle; // Movement direction - float velocitat; // Speed (units/frame) - uint8_t n; // Number of sides - float drotacio; // Rotation delta per frame - float rotacio; // Current rotation angle - bool esta; // Is active? -}; -``` - -### Constants (joc_asteroides.hpp) - -```cpp -namespace Constants { - constexpr int MARGE_DALT = 20; // Top margin - constexpr int MARGE_BAIX = 460; // Bottom margin - constexpr int MARGE_ESQ = 20; // Left margin - constexpr int MARGE_DRET = 620; // Right margin - constexpr int MAX_IPUNTS = 30; // Max polygon points - constexpr int MAX_ORNIS = 15; // Max enemies - constexpr int MAX_BALES = 3; // Max bullets - constexpr int VELOCITAT = 2; // Base velocity - constexpr int VELOCITAT_MAX = 6; // Max velocity - constexpr float PI = 3.14159265359f; -} -``` - -## Game Loop (main.cpp) - -**Time-based physics** with real delta_time: - -```cpp -// Lines 21-56 -Uint64 last_time = SDL_GetTicks(); -while (running) { - // Calculate real delta_time - Uint64 current_time = SDL_GetTicks(); - float delta_time = (current_time - last_time) / 1000.0f; // ms → s - last_time = current_time; - - // Cap at 50ms (20 FPS minimum) - if (delta_time > 0.05f) delta_time = 0.05f; - - // Process events - while (SDL_PollEvent(&event)) { - joc.processar_input(event); - // Handle quit/ESC - } - - // Update + render - joc.actualitzar(delta_time); - sdl.neteja(0, 0, 0); - joc.dibuixar(); - sdl.presenta(); -} -``` - -**Critical**: Delta_time is **real** and **variable**, not fixed at 0.016f. All physics must multiply by delta_time. - -## SDL3 API Notes - -SDL3 has breaking changes from SDL2: - -- `SDL_CreateRenderer(window, nullptr)` - no flags parameter -- `event.key.key` instead of `event.key.keysym.sym` -- `SDL_EVENT_KEY_DOWN` instead of `SDL_KEYDOWN` -- `SDL_EVENT_QUIT` instead of `SDL_QUIT` -- `SDL_GetKeyboardState(nullptr)` - state-based input for continuous keys - -## Physics System - -### Ship Movement (joc_asteroides.cpp:67-155) - -**State-based input** (not event-based) for smooth controls: - -```cpp -const bool* keyboard_state = SDL_GetKeyboardState(nullptr); - -if (keyboard_state[SDL_SCANCODE_RIGHT]) - nau_.angle += ROTATION_SPEED * delta_time; -if (keyboard_state[SDL_SCANCODE_LEFT]) - nau_.angle -= ROTATION_SPEED * delta_time; -if (keyboard_state[SDL_SCANCODE_UP]) - nau_.velocitat += ACCELERATION * delta_time; -``` - -**Physics constants** (calibrated for ~20 FPS feel): - -```cpp -constexpr float ROTATION_SPEED = 2.5f; // rad/s (~143°/s) -constexpr float ACCELERATION = 100.0f; // px/s² -constexpr float MAX_VELOCITY = 200.0f; // px/s -constexpr float FRICTION = 6.0f; // px/s² -``` - -**Position calculation** (angle-PI/2 because angle=0 points up, not right): - -```cpp -float dy = (nau_.velocitat * delta_time) * std::sin(nau_.angle - PI/2.0f); -float dx = (nau_.velocitat * delta_time) * std::cos(nau_.angle - PI/2.0f); -nau_.centre.y += round(dy); -nau_.centre.x += round(dx); -``` - -**Visual velocity effect**: Ship triangle grows when moving (joc_asteroides.cpp:162-164): - -```cpp -// Scale 200 px/s → 6 px visual effect (like original) -float velocitat_visual = nau_.velocitat / 33.33f; -rota_tri(nau_, nau_.angle, velocitat_visual, true); -``` - -### Enemy Movement (joc_asteroides.cpp:367-405) - FASE 8 - -Autonomous movement with random direction changes: - -```cpp -void mou_orni(Poligon& orni, float delta_time) { - // 5% probability to change direction - if (rand() < 0.05f * RAND_MAX) - orni.angle = random() * 2*PI; - - // Move (2 px/frame * 20 FPS = 40 px/s) - float velocitat_efectiva = orni.velocitat * 20.0f * delta_time; - float dy = velocitat_efectiva * sin(orni.angle - PI/2.0f); - float dx = velocitat_efectiva * cos(orni.angle - PI/2.0f); - orni.centre.y += round(dy); - orni.centre.x += round(dx); - - // Bounce on walls - if (x < MARGE_ESQ || x > MARGE_DRET) - orni.angle = PI - orni.angle; // Horizontal reflection - if (y < MARGE_DALT || y > MARGE_BAIX) - orni.angle = 2*PI - orni.angle; // Vertical reflection -} -``` - -### Bullet Movement (joc_asteroides.cpp:444-465) - FASE 9 - -Straight-line movement, deactivates when leaving screen: - -```cpp -void mou_bales(Poligon& bala, float delta_time) { - // Fast movement (6 px/frame * 20 FPS = 120 px/s) - float velocitat_efectiva = bala.velocitat * 20.0f * delta_time; - float dy = velocitat_efectiva * sin(bala.angle - PI/2.0f); - float dx = velocitat_efectiva * cos(bala.angle - PI/2.0f); - bala.centre.y += round(dy); - bala.centre.x += round(dx); - - // Deactivate if out of bounds - if (x < MARGE_ESQ || x > MARGE_DRET || - y < MARGE_DALT || y > MARGE_BAIX) - bala.esta = false; -} -``` - -## Rendering System - -### Coordinate Conversion - -**Polar → Cartesian** with rotation (used in `rota_tri` and `rota_pol`): - -```cpp -// For each polar point -int x = round((r + velocitat) * cos(angle_punt + angle_object)) + centre.x; -int y = round((r + velocitat) * sin(angle_punt + angle_object)) + centre.y; -``` - -### Line Drawing (joc_asteroides.cpp:230-298) - -Currently uses **SDL_RenderLine** for efficiency: - -```cpp -bool linea(int x1, int y1, int x2, int y2, bool dibuixar) { - if (dibuixar && renderer_) { - SDL_SetRenderDrawColor(renderer_, 255, 255, 255, 255); // White - SDL_RenderLine(renderer_, x1, y1, x2, y2); - } - return false; // Collision detection TODO (Phase 10) -} -``` - -**Note**: Original Bresenham algorithm preserved in comments for **Phase 10** (pixel-perfect collision detection). - -### Ship Rendering (joc_asteroides.cpp:300-337) - -Triangle with 3 lines: - -```cpp -void rota_tri(const Triangle& tri, float angul, float velocitat, bool dibuixar) { - // Convert 3 polar points to Cartesian - int x1 = round((tri.p1.r + velocitat) * cos(tri.p1.angle + angul)) + tri.centre.x; - int y1 = round((tri.p1.r + velocitat) * sin(tri.p1.angle + angul)) + tri.centre.y; - // ... same for p2, p3 - - // Draw 3 lines - linea(x1, y1, x2, y2, dibuixar); - linea(x1, y1, x3, y3, dibuixar); - linea(x3, y3, x2, y2, dibuixar); -} -``` - -### Polygon Rendering (joc_asteroides.cpp:339-365) - -Enemies and bullets: - -```cpp -void rota_pol(const Poligon& pol, float angul, bool dibuixar) { - // Convert all polar points to Cartesian - std::array xy; - for (int i = 0; i < pol.n; i++) { - xy[i].x = round(pol.ipuntx[i].r * cos(pol.ipuntx[i].angle + angul)) + pol.centre.x; - xy[i].y = round(pol.ipuntx[i].r * sin(pol.ipuntx[i].angle + angul)) + pol.centre.y; - } - - // Draw lines between consecutive points - for (int i = 0; i < pol.n - 1; i++) - linea(xy[i].x, xy[i].y, xy[i+1].x, xy[i+1].y, dibuixar); - - // Close polygon - linea(xy[pol.n-1].x, xy[pol.n-1].y, xy[0].x, xy[0].y, dibuixar); -} -``` - -## Input System - FASE 9 - -### Continuous Input (actualitzar) - -Arrow keys use **state-based** polling: - -```cpp -const bool* keyboard_state = SDL_GetKeyboardState(nullptr); -if (keyboard_state[SDL_SCANCODE_UP]) { /* accelerate */ } -``` - -### Event-Based Input (processar_input) - -SPACE bar for shooting (joc_asteroides.cpp:174-212): - -```cpp -void processar_input(const SDL_Event& event) { - if (event.type == SDL_EVENT_KEY_DOWN) { - if (event.key.key == SDLK_SPACE) { - // Find first inactive bullet - for (auto& bala : bales_) { - if (!bala.esta) { - bala.esta = true; - bala.centre = nau_.centre; // Spawn at ship - bala.angle = nau_.angle; // Fire in ship direction - bala.velocitat = 6.0f; // High speed - break; // Only one bullet at a time - } - } - } - } -} -``` - -## Initialization (joc_asteroides.cpp:15-65) - -### Ship (lines 20-34) - -```cpp -// Triangle with 3 polar points (r=12, angles at 270°, 45°, 135°) -nau_.p1.r = 12.0f; -nau_.p1.angle = 3.0f * PI / 2.0f; // Points up -nau_.p2.r = 12.0f; -nau_.p2.angle = PI / 4.0f; // Back-right -nau_.p3.r = 12.0f; -nau_.p3.angle = 3.0f * PI / 4.0f; // Back-left -nau_.centre = {320, 240}; -nau_.angle = 0.0f; -nau_.velocitat = 0.0f; -``` - -### Enemies (lines 39-54) - FASE 7 - -```cpp -for (int i = 0; i < MAX_ORNIS; i++) { - crear_poligon_regular(orni_[i], 5, 20.0f); // Pentagon, r=20 - orni_[i].centre.x = rand(30, 610); - orni_[i].centre.y = rand(30, 450); - orni_[i].angle = rand(0, 360) * PI/180; - orni_[i].esta = true; -} -``` - -### Bullets (lines 56-64) - FASE 9 - -```cpp -for (int i = 0; i < MAX_BALES; i++) { - crear_poligon_regular(bales_[i], 5, 5.0f); // Small pentagon, r=5 - bales_[i].esta = false; // Initially inactive -} -``` - -## Update Loop (joc_asteroides.cpp:67-155) - -```cpp -void actualitzar(float delta_time) { - // 1. Ship input + physics (lines 68-125) - // - Keyboard state polling - // - Rotation, acceleration, friction - // - Position update with boundary checking - - // 2. Enemy movement (lines 137-147) - FASE 8 - for (auto& enemy : orni_) { - if (enemy.esta) { - mou_orni(enemy, delta_time); - enemy.rotacio += enemy.drotacio; // Visual rotation - } - } - - // 3. Bullet movement (lines 149-154) - FASE 9 - for (auto& bala : bales_) { - if (bala.esta) - mou_bales(bala, delta_time); - } - - // TODO Phase 10: Collision detection -} -``` - -## Draw Loop (joc_asteroides.cpp:157-184) - -```cpp -void dibuixar() { - // 1. Ship (if alive) - if (itocado_ == 0) { - float velocitat_visual = nau_.velocitat / 33.33f; - rota_tri(nau_, nau_.angle, velocitat_visual, true); - } - - // 2. Enemies (FASE 7) - for (const auto& enemy : orni_) { - if (enemy.esta) - rota_pol(enemy, enemy.rotacio, true); - } - - // 3. Bullets (FASE 9) - for (const auto& bala : bales_) { - if (bala.esta) - rota_pol(bala, 0.0f, true); // No visual rotation - } - - // TODO Phase 11: Draw borders -} -``` - -## Title Screen Ship System (BETA 3.0) - -The title screen features two 3D ships floating on the starfield with perspective rendering, entry/exit animations, and subtle floating motion. - -### Architecture Overview - -**Files:** -- `source/game/title/ship_animator.hpp/cpp` - Ship animation state machine -- `source/core/rendering/shape_renderer.hpp/cpp` - 3D rotation + perspective projection -- `source/core/defaults.hpp` - Title::Ships namespace with all constants -- `source/game/escenes/escena_titol.hpp/cpp` - Integration with title scene - -**Design Philosophy:** -- **Static 3D rotation**: Ships have fixed pitch/yaw/roll angles (not recalculated per frame) -- **Simple Z-axis simulation**: Scale changes simulate depth, not full perspective recalculation -- **State machine**: ENTERING → FLOATING → EXITING states -- **Easing functions**: Smooth transitions with ease_out_quad (entry) and ease_in_quad (exit) -- **Sinusoidal floating**: Organic motion using X/Y oscillation with phase offset - -### 3D Rendering System - -#### Rotation3D Struct (shape_renderer.hpp) - -```cpp -struct Rotation3D { - float pitch; // X-axis rotation (nose up/down) - float yaw; // Y-axis rotation (turn left/right) - float roll; // Z-axis rotation (bank left/right) - - Rotation3D() : pitch(0.0f), yaw(0.0f), roll(0.0f) {} - Rotation3D(float p, float y, float r) : pitch(p), yaw(y), roll(r) {} - - bool has_rotation() const { - return pitch != 0.0f || yaw != 0.0f || roll != 0.0f; - } -}; -``` - -#### 3D Transformation Pipeline (shape_renderer.cpp) - -```cpp -static Punt apply_3d_rotation(float x, float y, const Rotation3D& rot) { - float z = 0.0f; // All 2D points start at Z=0 - - // 1. Pitch (X-axis): Rotate around horizontal axis - float cos_pitch = std::cos(rot.pitch); - float sin_pitch = std::sin(rot.pitch); - float y1 = y * cos_pitch - z * sin_pitch; - float z1 = y * sin_pitch + z * cos_pitch; - - // 2. Yaw (Y-axis): Rotate around vertical axis - float cos_yaw = std::cos(rot.yaw); - float sin_yaw = std::sin(rot.yaw); - float x2 = x * cos_yaw + z1 * sin_yaw; - float z2 = -x * sin_yaw + z1 * cos_yaw; - - // 3. Roll (Z-axis): Rotate around depth axis - float cos_roll = std::cos(rot.roll); - float sin_roll = std::sin(rot.roll); - float x3 = x2 * cos_roll - y1 * sin_roll; - float y3 = x2 * sin_roll + y1 * cos_roll; - - // 4. Perspective projection (Z-divide) - constexpr float perspective_factor = 500.0f; - float scale_factor = perspective_factor / (perspective_factor + z2); - - return {x3 * scale_factor, y3 * scale_factor}; -} -``` - -**Rendering order**: 3D rotation → perspective → 2D scale → 2D rotation → translation - -**Backward compatibility**: Optional `rotation_3d` parameter (default nullptr) - existing code unaffected - -### Ship Animation State Machine - -#### States (ship_animator.hpp) - -```cpp -enum class EstatNau { - ENTERING, // Entering from off-screen - FLOATING, // Floating at target position - EXITING // Flying towards vanishing point -}; - -struct NauTitol { - int jugador_id; // 1 or 2 - EstatNau estat; // Current state - float temps_estat; // Time in current state - - Punt posicio_inicial; // Start position - Punt posicio_objectiu; // Target position - Punt posicio_actual; // Current interpolated position - - float escala_inicial; // Start scale - float escala_objectiu; // Target scale - float escala_actual; // Current interpolated scale - - Rotation3D rotacio_3d; // STATIC 3D rotation (never changes) - float fase_oscilacio; // Oscillation phase accumulator - - std::shared_ptr forma; - bool visible; -}; -``` - -#### State Transitions - -**ENTERING** (2.0s): -- Ships appear from beyond screen edges (calculated radially from clock positions) -- Lerp position: off-screen → target (clock 8 / clock 4) -- Lerp scale: 1.0 → 0.6 (perspective effect) -- Easing: `ease_out_quad` (smooth deceleration) -- Transition: → FLOATING when complete - -**FLOATING** (indefinite): -- Sinusoidal oscillation on X/Y axes -- Different frequencies (0.5 Hz / 0.7 Hz) with phase offset (π/2) -- Creates organic circular/elliptical motion -- Scale constant at 0.6 -- Transition: → EXITING when START pressed - -**EXITING** (1.0s): -- Ships fly towards vanishing point (center: 320, 240) -- Lerp position: current → vanishing point -- Lerp scale: current → 0.0 (simulates Z → infinity) -- Easing: `ease_in_quad` (acceleration) -- Edge case: If START pressed during ENTERING, ships fly from mid-animation position -- Marks invisible when complete - -### Configuration (defaults.hpp) - -```cpp -namespace Title { -namespace Ships { - // Clock positions (polar coordinates from center 320, 240) - constexpr float CLOCK_8_ANGLE = 150.0f * Math::PI / 180.0f; // Bottom-left - constexpr float CLOCK_4_ANGLE = 30.0f * Math::PI / 180.0f; // Bottom-right - constexpr float CLOCK_RADIUS = 150.0f; - - // Target positions (pre-calculated) - constexpr float P1_TARGET_X = 190.0f; // Clock 8 - constexpr float P1_TARGET_Y = 315.0f; - constexpr float P2_TARGET_X = 450.0f; // Clock 4 - constexpr float P2_TARGET_Y = 315.0f; - - // 3D rotations (STATIC - tuned for subtle effect) - constexpr float P1_PITCH = 0.1f; // ~6° nose-up - constexpr float P1_YAW = -0.15f; // ~9° turn left - constexpr float P1_ROLL = -0.05f; // ~3° bank left - - constexpr float P2_PITCH = 0.1f; // ~6° nose-up - constexpr float P2_YAW = 0.15f; // ~9° turn right - constexpr float P2_ROLL = 0.05f; // ~3° bank right - - // Scales - constexpr float ENTRY_SCALE_START = 1.0f; - constexpr float FLOATING_SCALE = 0.6f; - - // Animation durations - constexpr float ENTRY_DURATION = 2.0f; - constexpr float EXIT_DURATION = 1.0f; - constexpr float ENTRY_OFFSET = 200.0f; // Distance beyond screen edge - - // Floating oscillation - constexpr float FLOAT_AMPLITUDE_X = 6.0f; - constexpr float FLOAT_AMPLITUDE_Y = 4.0f; - constexpr float FLOAT_FREQUENCY_X = 0.5f; - constexpr float FLOAT_FREQUENCY_Y = 0.7f; - constexpr float FLOAT_PHASE_OFFSET = 1.57f; // π/2 (90°) - - // Vanishing point - constexpr float VANISHING_POINT_X = 320.0f; - constexpr float VANISHING_POINT_Y = 240.0f; -} -} -``` - -### Integration with EscenaTitol - -#### Constructor - -```cpp -// Initialize ships after starfield -ship_animator_ = std::make_unique(sdl_.obte_renderer()); -ship_animator_->inicialitzar(); - -if (estat_actual_ == EstatTitol::MAIN) { - // Jump to MAIN: ships already in position (no entry animation) - ship_animator_->set_visible(true); -} else { - // Normal flow: ships enter during STARFIELD_FADE_IN - ship_animator_->set_visible(true); - ship_animator_->start_entry_animation(); -} -``` - -#### Update Loop - -```cpp -// Update ships in visible states -if (ship_animator_ && - (estat_actual_ == EstatTitol::STARFIELD_FADE_IN || - estat_actual_ == EstatTitol::STARFIELD || - estat_actual_ == EstatTitol::MAIN || - estat_actual_ == EstatTitol::PLAYER_JOIN_PHASE)) { - ship_animator_->actualitzar(delta_time); -} - -// Trigger exit when START pressed -if (checkStartGameButtonPressed()) { - estat_actual_ = EstatTitol::PLAYER_JOIN_PHASE; - ship_animator_->trigger_exit_animation(); // Edge case: handles mid-ENTERING - Audio::get()->fadeOutMusic(MUSIC_FADE); -} -``` - -#### Draw Loop - -```cpp -// Draw order: starfield → ships → logo → text -if (starfield_) starfield_->dibuixar(); - -if (ship_animator_ && - (estat_actual_ == EstatTitol::STARFIELD_FADE_IN || - estat_actual_ == EstatTitol::STARFIELD || - estat_actual_ == EstatTitol::MAIN || - estat_actual_ == EstatTitol::PLAYER_JOIN_PHASE)) { - ship_animator_->dibuixar(); -} - -// Logo and text drawn after ships (foreground) -``` - -### Timing & Visibility - -**Timeline:** -1. **STARFIELD_FADE_IN** (3.0s): Ships enter from off-screen -2. **STARFIELD** (4.0s): Ships floating -3. **MAIN** (indefinite): Ships floating + logo + text visible -4. **PLAYER_JOIN_PHASE** (2.5s): Ships exit (1.0s) + text blink -5. **BLACK_SCREEN** (2.0s): Ships already invisible (exit completed at 1.0s) - -**Automatic visibility management:** -- Ships marked `visible = false` when exit animation completes (actualitzar_exiting) -- No manual hiding needed - state machine handles it - -### Tuning Notes - -**If ships look distorted:** -- Reduce rotation angles (P1_PITCH, P1_YAW, P1_ROLL, P2_*) -- Current values (0.1, 0.15, 0.05) are tuned for subtle 3D effect -- Angles in radians: 0.1 rad ≈ 6°, 0.15 rad ≈ 9° - -**If ships are too large/small:** -- Adjust FLOATING_SCALE (currently 0.6) -- Adjust ENTRY_SCALE_START (currently 1.0) - -**If floating motion is too jerky/smooth:** -- Adjust FLOAT_AMPLITUDE_X/Y (currently 6.0/4.0 pixels) -- Adjust FLOAT_FREQUENCY_X/Y (currently 0.5/0.7 Hz) - -**If entry/exit animations are too fast/slow:** -- Adjust ENTRY_DURATION (currently 2.0s) -- Adjust EXIT_DURATION (currently 1.0s) - -### Implementation Phases (Completed) - -✅ **Phase 1**: 3D infrastructure (Rotation3D, render_shape extension) -✅ **Phase 2**: Foundation (ship_animator files, constants) -✅ **Phase 3**: Configuration & loading (shape loading, initialization) -✅ **Phase 4**: Floating animation (sinusoidal oscillation) -✅ **Phase 5**: Entry animation (off-screen → position with easing) -✅ **Phase 6**: Exit animation (position → vanishing point) -✅ **Phase 7**: EscenaTitol integration (constructor, update, draw) -✅ **Phase 8**: Polish & tuning (angles, scales, edge cases) -✅ **Phase 9**: Documentation (CLAUDE.md, code comments) - -## Migration Progress - -### ✅ Phase 0: Project Setup -- CMakeLists.txt + Makefile (based on pollo) -- Stub files created - -### ✅ Phase 1: SDL Manager -- SDLManager class (sdl_manager.hpp/cpp) -- Window + renderer initialization -- Fixed SDL3 API differences - -### ✅ Phase 2: Data Structures -- IPunt, Punt, Triangle, Poligon defined -- Constants namespace with constexpr - -### ✅ Phase 3: Geometry Functions -- modul(), diferencia(), distancia(), angle_punt() -- crear_poligon_regular() - -### ✅ Phase 4: Line Drawing -- linea() with SDL_RenderLine -- Bresenham preserved in comments for Phase 10 - -### ✅ Phase 5: Ship Rendering -- rota_tri() polar→Cartesian conversion -- Ship initialization - -### ✅ Phase 6: Ship Movement -- **Critical fix**: Event-based → State-based input (SDL_GetKeyboardState) -- **Critical fix**: Fixed delta_time (0.016f) → Real delta_time calculation -- **Critical fix**: Visual velocity scaling (200 px/s → 6 px visual) -- Time-based physics (all values in px/s) -- Rotation, acceleration, friction, boundary checking - -### ✅ Phase 7: Enemy Rendering -- rota_pol() for polygons -- 15 random pentagons initialized -- Visual rotation (enemy.rotacio) - -### ✅ Phase 8: Enemy AI & Movement -- **mou_orni()** (joc_asteroides.cpp:367-405) - - 5% random direction change - - Polar movement (40 px/s) - - Boundary bounce (angle reflection) -- Integrated in actualitzar() (lines 137-147) - -### ✅ Phase 9: Bullet System -- **Bullet initialization** (joc_asteroides.cpp:56-64) - - 3 bullets, initially inactive - - Small pentagons (r=5) -- **Shooting with SPACE** (joc_asteroides.cpp:174-212) - - Spawns at ship position - - Fires in ship direction - - Only one bullet at a time -- **mou_bales()** (joc_asteroides.cpp:444-465) - - Fast rectlinear movement (120 px/s) - - Deactivates when out of bounds -- **Drawing** (joc_asteroides.cpp:175-181) - - No visual rotation - -### 🔲 Phase 10: Collision Detection & Death (NEXT) -- **Collision detection**: - - Restore Bresenham pixel-perfect algorithm - - Detect ship-enemy collision → tocado() - - Detect bullet-enemy collision → destroy enemy -- **Death sequence** (tocado): - - Explosion animation (itocado_ counter) - - Ship shrinking - - Debris particles (chatarra_cosmica) - - Respawn after delay -- **Important**: Original Pascal used bit-packed framebuffer (llig() function) - - Need to adapt to SDL3 rendering pipeline - - Options: render to texture, software buffer, or geometric collision - -### 🔲 Phase 11: Polish & Refinements -- Draw borders (marges) -- Text rendering with SDL_ttf (TODO for later) -- Sound effects (optional) -- Score system (optional) - -### 🔲 Phase 12: Cross-Platform Testing -- Test on Linux, macOS, Windows -- Create release builds -- Package with resources - -## Known Issues & Tuning Needed - -1. **Ship physics constants**: User mentioned "sigue sin ir fino" - may need adjustment: - - `ROTATION_SPEED` (currently 2.5 rad/s) - - `ACCELERATION` (currently 100.0 px/s²) - - `MAX_VELOCITY` (currently 200.0 px/s) - - `FRICTION` (currently 6.0 px/s²) - -2. **Enemy movement**: May need speed/bounce angle tuning - - `VELOCITAT_SCALE` (currently 20.0) - - Reflection angles (PI - angle, 2*PI - angle) - -3. **Bullet speed**: May need adjustment - - `velocitat = 6.0f` (120 px/s) - -## Important Pascal References (Original Code) - -The original Pascal game is in `source/ASTEROID.PAS` (if available). Key procedures: - -- `teclapuls` - Keyboard handler (converted to SDL_GetKeyboardState) -- `mou_nau` - Ship movement (now actualitzar ship section) -- `mou_orni` - Enemy movement (joc_asteroides.cpp:367-405) -- `mou_bales` - Bullet movement (joc_asteroides.cpp:444-465) -- `rota_tri` - Ship rendering (joc_asteroides.cpp:300-337) -- `rota_pol` - Polygon rendering (joc_asteroides.cpp:339-365) -- `linea` - Bresenham line (joc_asteroides.cpp:230-298) -- `tocado` - Death sequence (TODO Phase 10) - -## Controls - -- **Arrow Keys** (UP/DOWN/LEFT/RIGHT): Ship movement (continuous) -- **SPACE**: Shoot (event-based) -- **ESC**: Quit - -## Debug Output - -Ship debug info printed every second (joc_asteroides.cpp:115-125): - -```cpp -static float time_accumulator = 0.0f; -time_accumulator += delta_time; -if (time_accumulator >= 1.0f) { - std::cout << "Nau: pos(" << nau_.centre.x << "," << nau_.centre.y - << ") vel=" << (int)nau_.velocitat << " px/s" - << " angle=" << (int)(nau_.angle * 180/PI) << "°" - << " dt=" << (int)(delta_time * 1000) << "ms" << std::endl; - time_accumulator -= 1.0f; -} -``` - -## Next Session Priorities - -1. **Phase 10: Collision Detection** - - Most complex phase - - Need to decide: geometric vs pixel-perfect collision - - Implement tocado() death sequence - - Bullet-enemy collision - -2. **Phase 11: Polish** - - Draw borders - - Consider text rendering (score, lives) - -3. **Phase 12: Release** - - Cross-platform testing - - Final physics tuning - -## IMPORTANT: Modernization Architecture (BETA 3.0) - -Starting from BETA 3.0, the project has evolved into a professional modular architecture: - -### Structural Changes - -**Before (BETA 2.2):** -``` -source/ -├── main.cpp -├── sdl_manager.hpp/cpp -├── joc_asteroides.hpp/cpp -└── asteroids.cpp (Pascal) -``` - -**Now (BETA 3.0):** -``` -source/ -├── core/ - Reusable engine -│ ├── defaults.hpp - SINGLE SOURCE OF TRUTH for constants -│ ├── types.hpp - Data structures -│ └── rendering/ -│ ├── sdl_manager - Dynamic windows + viewport -│ └── primitives - Pure geometric functions -├── game/ - Asteroids-specific logic -│ ├── constants.hpp - Aliases for backward compatibility -│ └── joc_asteroides - Game loop -├── utils/ - Shared utilities -├── main.cpp - Entry point -└── legacy/ - └── asteroids.cpp - Original Pascal code (reference) -``` - -### Dynamic Window System - -**Controls:** -- **F1**: Decrease window (-100px width/height) -- **F2**: Increase window (+100px width/height) -- **F3**: Toggle fullscreen -- **ESC**: Exit (unchanged) - -**Behavior:** -- Window starts at 640x480 centered on screen -- Each resize keeps window centered on itself -- Minimum size: 320x240 -- Maximum size: Calculated from display resolution (limit -100px) - -**Viewport Scaling (SDL3):** -- Game ALWAYS renders in logical coordinates 640x480 -- SDL3 automatically scales to any physical window size -- Aspect ratio preserved (4:3 with letterboxing) -- Vectors look SHARPER in larger windows (higher resolution) -- Game physics UNCHANGED (still px/s relative to 640x480 logical) - -**Implementation:** -```cpp -SDL_SetRenderLogicalPresentation( - renderer_, - 640, 480, // Fixed logical size - SDL_LOGICAL_PRESENTATION_LETTERBOX // Maintain aspect ratio -); -``` - -### Configuration System - -**core/defaults.hpp** - Only place to change constants: - -```cpp -namespace Defaults { - namespace Window { - constexpr int WIDTH = 640; - constexpr int HEIGHT = 480; - constexpr int SIZE_INCREMENT = 100; // F1/F2 - } - - namespace Game { - constexpr int WIDTH = 640; // Logical - constexpr int HEIGHT = 480; - constexpr int MARGIN_LEFT = 20; // MARGE_ESQ - constexpr int MARGIN_RIGHT = 620; // MARGE_DRET - // ... - } - - namespace Physics { - constexpr float ROTATION_SPEED = 2.5f; - constexpr float ACCELERATION = 100.0f; - // ... - } -} -``` - -**game/constants.hpp** - Backward compatibility: -```cpp -using Defaults::Game::MARGIN_LEFT; // For legacy code using MARGE_ESQ -// ... -``` - -### Important Reminders - -**1. Logical vs Physical Coordinates** -- ALL game code uses logical coordinates (640x480) -- NO need to adjust physics or collision calculations -- SDL3 handles conversion automatically - -**2. Rendering** -- `linea()`, `rota_tri()`, `rota_pol()` still use direct coords -- NO manual transformation, SDL does it internally - -**3. Configuration** -- NEVER use magic numbers in new code -- Always reference `Defaults::*` -- For game values, create aliases in `game/constants.hpp` if needed - -**4. Future OpenGL** -- Current system allows migrating to OpenGL without changing game code -- Would only require changing SDLManager and rendering function implementations -- Postponed until needing >50 enemies or complex effects - -### Compilation - -**No changes:** -```bash -make clean && make -./asteroids -``` - -**Files modified by CMake:** -- Updated to include subdirectories core/, game/ -- Include path: `${CMAKE_SOURCE_DIR}/source` (for relative includes) - -### Migration for Future Sessions - -If you find code using magic numbers: -1. Add constant in `core/defaults.hpp` in the appropriate namespace -2. If it's a frequently used game value, create alias in `game/constants.hpp` -3. Replace the number with the constant -4. Compile and verify - -Example: -```cpp -// Before (bad): -if (enemy.centre.x < 20 || enemy.centre.x > 620) { ... } - -// After (good): -if (enemy.centre.x < MARGIN_LEFT || enemy.centre.x > MARGIN_RIGHT) { ... } -``` - -## Tips for Future Claude Code Sessions - -- **Always read this file first** before making changes -- **Preserve Valencian naming**: nau, orni, bales, centre, velocitat, etc. -- **Time-based physics**: All movement must multiply by delta_time -- **Polar coordinates**: Core to the game, don't change to Cartesian -- **Test compilation** after each change: `make clean && make` -- **Visual velocity scaling**: Remember to scale velocitat before passing to rota_tri -- **Angle convention**: angle=0 points UP (not right), hence `angle - PI/2` in calculations -- **One bullet at a time**: Original game limitation, preserve it -- **Simple code style**: Avoid over-engineering, keep "small DOS program" feel -- **Use defaults.hpp**: Never hardcode constants, always use Defaults namespace diff --git a/CODE_REVIEW.md b/CODE_REVIEW.md deleted file mode 100644 index bfa2995..0000000 --- a/CODE_REVIEW.md +++ /dev/null @@ -1,346 +0,0 @@ -# Auditoría arquitectónica — Orni Attack - -> Pasada de revisión de **diseño y arquitectura** (no de estilo) sobre la rama `main` tras cerrar el ciclo de lint (cppcheck/clang-tidy a 0 hits). -> Generada el 2026-05-20 por el subagente `Plan` y verificada parcialmente. -> Las referencias `file:line` son relativas a la raíz del repo. - -## Cómo usar este documento - -- **Prioridad** indica urgencia subjetiva (alta/media/baja). -- **Tipo** distingue: - - **[estructural]** = bug latente, deuda técnica medible, código muerto - - **[opinable]** = cambio de estilo/diseño que el usuario puede rechazar legítimamente -- **Esfuerzo**: small (1 commit < 30 min) / medium (1-3 commits) / large (varias sesiones). - -Antes de actuar sobre un hallazgo, **re-verificar** que sigue siendo válido (el código habrá cambiado). - ---- - -## Hallazgos por área - -### A. Escenas - -#### 1. `Scene` no declara `init()`, pero `GameScene` lo expone — [PRIORIDAD: alta, estructural] -- **Dónde**: `source/core/system/scene.hpp:31-40`, `source/game/scenes/game_scene.hpp:47`, `source/game/scenes/game_scene.cpp:64` (`init()` se llama desde el ctor). -- **Problema**: La interfaz `Scene` declara `handleEvent/update/draw/isFinished` como obligatorios. `GameScene::init()` es un método público adicional, pero el Director nunca lo llama — `GameScene` lo invoca desde su propio ctor. El comentario del header dice "init() — llamado por Director tras crear la escena", pero eso no ocurre. `TitleScene` y `LogoScene` no tienen ese método: hacen todo en el ctor. Inconsistencia de lifecycle. -- **Propuesta**: (a) eliminar `init()` y mover su contenido al ctor (lo más simple), o (b) añadirlo a la interfaz `Scene` y llamarlo desde el Director tras `buildScene`. Lo coherente es (a). -- **Esfuerzo**: small - -#### 2. No existe clase base `MenuScene` para el patrón `timer + state-machine + skip-button` — [PRIORIDAD: media, opinable] -- **Dónde**: `source/game/scenes/title_scene.cpp:368-388`, `source/game/scenes/logo_scene.cpp:219-280`, partes de `game_scene.cpp` (INIT_HUD/LEVEL_START). -- **Problema**: Las tres escenas implementan la misma idea: un enum `State`, un timer acumulado, un `switch` por estado, una transición "skip". `checkSkipButtonPressed()` aparece en `TitleScene` y `LogoScene` con la misma signatura estática. -- **Propuesta**: Mixin pequeño `TimedStateScene` con `enterState(EnumT)`, `time_in_state()`, helpers de "esperar N segundos y avanzar". Al menos consolidar `checkSkipButtonPressed()` y la pareja `temps_estat_actual_ + cambio de estado` en un helper. -- **Esfuerzo**: medium - -#### 3. `TitleScene::initTitle()` duplica dos veces el cálculo de bounding box + posicionado — [PRIORIDAD: alta, estructural] -- **Dónde**: `source/game/scenes/title_scene.cpp:104-254` (función de 150 líneas). -- **Problema**: Carga "ORNI" (108-166) y "ATTACK!" (178-241) con el mismo código copiado. `inicialitzarJailgames()` (256-325) hace lo mismo por tercera vez. Tres copias del cómputo de bounding box. -- **Propuesta**: Extraer `loadLetterRow(filenames, scale, y_position, espai) -> std::vector`. -- **Esfuerzo**: small - -#### 4. `PLAYER_JOIN_PHASE` en TitleScene mezcla animación + audio + late join — [PRIORIDAD: media, opinable] -- **Dónde**: `source/game/scenes/title_scene.cpp:451-483`. -- **Problema**: `updatePlayerJoinPhaseState` hace varias cosas no relacionadas. La lógica de "late join" reaprovecha `checkStartGameButtonPressed()` (que *muta* `match_config_` — efecto secundario no advertido por la firma). -- **Propuesta**: Renombrar `checkStartGameButtonPressed()` a `tryActivatePlayersOnStart()`. Separar la lógica de "late join" en un helper. -- **Esfuerzo**: small - -#### 5. `SceneContext::actual` es estado global redundante — [PRIORIDAD: baja, opinable] -- **Dónde**: `source/core/system/scene_context.hpp:81`, escrita en `director.cpp:267,275` y `global_events.cpp:29,58`. -- **Problema**: `inline SceneContext::SceneType actual` como variable global, sincronizada manualmente. No la consulta nadie excepto el propio Director (que lee `context.nextScene()`, no `actual`). -- **Propuesta**: Verificación profunda. Si nadie la consulta, eliminarla. -- **Esfuerzo**: small - -### B. Sistemas - -#### 6. `StageManager::update(dt, pause_spawn)` tiene `pause_spawn` muerto — [PRIORIDAD: alta, estructural] -- **Dónde**: `source/game/stage_system/stage_manager.hpp:28`, `source/game/stage_system/stage_manager.cpp:128-135`, `game_scene.cpp:359-374,423-424`. -- **Problema**: El parámetro `pause_spawn` existe en la API pero ningún caller lo pasa con sentido — la pausa real ocurre por otra ruta (`spawn_controller_.update(dt, enemies, PAUSE_SPAWN)`). El comentario en `stage_manager.cpp:131-134` ya admite que no se usa. -- **Propuesta**: Quitar el parámetro. -- **Esfuerzo**: small - -#### 7. `SpawnController` hardcoda `std::array` en su firma — [PRIORIDAD: media, estructural] -- **Dónde**: `source/game/stage_system/spawn_controller.hpp:33,37,39`; `.cpp:44,89,96`. -- **Problema**: Literal `15` en lugar de `Defaults::Entities::MAX_ORNIS`. Acoplamiento silencioso por número. -- **Propuesta**: Usar `std::array&` o un alias `using EnemyArray = ...;`. -- **Esfuerzo**: small - -#### 8. `Systems::Collision::Context` y `Systems::ContinueScreen::Context` son "god structs" — [PRIORIDAD: alta, estructural] -- **Dónde**: `source/game/systems/collision_system.hpp:31-43`, `source/game/systems/continue_system.hpp:25-38`. -- **Problema**: Cada `Context` agrupa entre 9 y 12 referencias mutables a estado de `GameScene` + un `std::function` callback. Punto único donde cualquier cambio de campo en GameScene rompe el módulo. Los sistemas pueden mutar todo (no hay distinción "lo que leo" vs "lo que modifico"). La separación `GameScene` ↔ `Systems::*` es nominal. -- **Propuesta**: Que `detectAll` devuelva una struct "outcome" (qué balas se desactivaron, qué enemigos murieron, qué jugador fue hit) y que GameScene aplique el resultado. Si es demasiado, al menos partir el Context en `In` (lectura) e `Out` (escritura). -- **Esfuerzo**: large - -#### 9. `CollisionSystem` y `PhysicsWorld` se solapan — [PRIORIDAD: media, opinable] -- **Dónde**: `source/game/systems/collision_system.cpp` (todo), `source/core/physics/physics_world.cpp:118-179`. -- **Problema**: `PhysicsWorld::resolveBodyCollisions` resuelve colisiones cuerpo-cuerpo con impulsos elásticos; `CollisionSystem::detectBulletEnemy` itera otra vez los mismos pares con un *amplifier* distinto y aplica daño. Dos bucles O(n²) sobre el mismo conjunto. El comentario en `game_scene.cpp:159` explica la sutileza pero es frágil. -- **Propuesta**: Verificación profunda. ¿Deberían las balas vivir fuera del world directamente? -- **Esfuerzo**: medium - -#### 10. `init_hud_animator` es solo 3 funciones libres en un namespace — [PRIORIDAD: baja, opinable] -- **Dónde**: `source/game/systems/init_hud_animator.hpp`, `.cpp` (99 líneas). -- **Problema**: A diferencia de `CollisionSystem`/`ContinueSystem`, no tiene `Context`; son 3 funciones puras. La nomenclatura sugiere algo más grande. -- **Propuesta**: Mover a `source/core/rendering/` o renombrar `systems/` a `gameplay/`. -- **Esfuerzo**: small - -### C. Entidades - -#### 11. `Ship` expone tres métodos equivalentes: `isAlive()`, `isHit()`, `isActive()` — [PRIORIDAD: alta, estructural] -- **Dónde**: `source/game/entities/ship.hpp:27,38,39`. -- **Problema**: `isActive() = !is_hit_`, `isAlive() = !is_hit_`, `isHit() = is_hit_`. Tres formas de preguntar lo mismo, usadas en sitios distintos. -- **Propuesta**: Quedarse con `isActive()` (override de Entity) y reescribir los call-sites. -- **Esfuerzo**: small - -#### 12. `Entity::renderer_` raw pointer; ctor por defecto deja la entidad inservible — [PRIORIDAD: media, estructural] -- **Dónde**: `source/core/entities/entity.hpp:56,67`, `source/game/entities/ship.hpp:15-16` (`Ship() : Entity(nullptr) {}`), idem en `enemy.hpp:38-39`, `bullet.hpp:15-16`. -- **Problema**: El ctor por defecto existe solo para soportar `std::array`, pero deja `renderer_` nullptr. Si alguien dibuja antes de la reasignación, segfault. `renderer_` no tiene in-class initializer. -- **Propuesta**: (a) Inicializador en miembro `Rendering::Renderer* renderer_{nullptr};`. (b) Guard en `draw()` para `renderer_ == nullptr`. (c) Considerar `std::array, 2>`. -- **Esfuerzo**: small (a)+(b); medium (c) - -#### 13. Duplicación de estado: `center_/angle_` en Entity vs `body_.position/body_.angle` en RigidBody — [PRIORIDAD: media, estructural] -- **Dónde**: `source/core/entities/entity.hpp:58-59,64`; `source/game/entities/ship.cpp:131-135` (postUpdate sincroniza), `enemy.cpp:214-219`, `bullet.cpp:121-125`. -- **Problema**: Cada entidad mantiene `center_/angle_` como mirror del body. Cualquier código que lea `getCenter()` durante `update()` (antes del `postUpdate`) verá el valor del frame anterior. El patrón está documentado pero es frágil. -- **Propuesta**: Hacer que `getCenter()` devuelva `body_.position` directamente (eliminando `center_`). -- **Esfuerzo**: medium - -#### 14. `Entity::renderer_` `protected:` con acceso directo + getter por referencia mutable a `body_` — [PRIORIDAD: media, estructural] -- **Dónde**: `source/core/entities/entity.hpp:51-52` (`getBody() -> RigidBody&`), `:54-64`. -- **Problema**: Encapsulación abierta + getters que devuelven referencias no-const al `body_`. El contrato de quién muta `body_.velocity`/`position` no está claro. -- **Propuesta**: Documentar el contrato. Hacer `body_` privado con API estricta (`registerInWorld(world)`). -- **Esfuerzo**: medium - -#### 15. `Enemy` tiene `drotacio_/rotacio_` (rotación visual) Y `body_.angle/angular_velocity` — [PRIORIDAD: baja, estructural] -- **Dónde**: `source/game/entities/enemy.hpp:92-93`, `.cpp:210-212`. -- **Problema**: El body de un enemy nunca rota (angular_velocity siempre 0), pero la visual sí. Dos representaciones de orientación viviendo en la misma entidad. -- **Propuesta**: Eliminar `body_.angle/angular_velocity` para enemies (no se usa). -- **Esfuerzo**: small - -### D. Renderizado y recursos - -#### 16. `Rotation3D` (struct + parámetro en `renderShape`) es código muerto — [PRIORIDAD: media, estructural] -- **Dónde**: `source/core/rendering/shape_renderer.hpp:18-35,52`, `.cpp:13-78`. -- **Problema**: La función `renderShape` acepta `const Rotation3D* rotation_3d = nullptr`. **Todos** los callers pasan `nullptr` (`ship.cpp:161`, `enemy.cpp:232`, `bullet.cpp:137`). 28 líneas de código muerto. -- **Propuesta**: Eliminar `Rotation3D`, el parámetro y el branch. -- **Esfuerzo**: small - -#### 17. `Resource::Helper` es una fachada delgada sobre `Resource::Loader` — [PRIORIDAD: media, estructural] -- **Dónde**: `source/core/resources/resource_helper.hpp`, `.cpp` (80 líneas); `resource_loader.hpp` (54 líneas). -- **Problema**: `Helper::loadFile()` es `Loader::get().loadResource(normalizePath(...))`. La única "lógica real" de Helper es `normalizePath()`. Duplicación de API. -- **Propuesta**: Mover `normalizePath` a `Resource::Loader` y eliminar `Helper`. -- **Esfuerzo**: small - -#### 18. `ShapeLoader::resolvePath` parece código muerto — [PRIORIDAD: baja, opinable] -- **Dónde**: `source/core/graphics/shape_loader.hpp:36`, `.cpp:70-83`. -- **Problema**: `resolvePath` está declarada como helper privado, pero no se llama desde ningún sitio. -- **Propuesta**: Eliminar `resolvePath` y `BASE_PATH`. -- **Esfuerzo**: small - -#### 19. `ShapeLoader::cache` sin política de invalidación — [PRIORIDAD: baja, estructural] -- **Dónde**: `source/core/graphics/shape_loader.cpp:13,15-60`. -- **Problema**: Caché estática `unordered_map>`. `clearCache()` existe pero nadie la llama. Sin límite de tamaño. -- **Propuesta**: Documentar en el header que la caché vive toda la vida del proceso. -- **Esfuerzo**: small - -#### 20. `GpuFrameRenderer::device()` y `isInsideFrame()` son getters públicos sin callers — [PRIORIDAD: media, estructural] -- **Dónde**: `source/core/rendering/gpu/gpu_frame_renderer.hpp:96,97`. Similar `SDLManager::getScaleFactor()` (línea 42). -- **Problema**: API pública sin clientes. -- **Propuesta**: Eliminar o documentar como API reservada. -- **Esfuerzo**: small - -### E. Configuración - -#### 21. `Options::physics`, `Options::audio`, `Options::gameplay` se cargan desde YAML pero nunca se consultan en runtime — [PRIORIDAD: alta, estructural] -- **Dónde**: - - `Options::physics` se rellena en `options.cpp:194-201`, parse 286-291, write 597-603. Único call-site lector: `director.cpp:109` (informativo). Ship/Enemy/Bullet leen `Defaults::Physics::*` directamente. - - `Options::audio` documentado como "deliberadamente desacoplado" (`audio.hpp:22-25`); `director.cpp:235-244` construye `Audio::Config` desde `Defaults::Audio`. - - `Options::gameplay` (max_enemies, max_bullets): las arrays son compile-time (`std::array`). -- **Problema**: El usuario edita su `config.yaml`, cambia `physics.rotation_speed: 6.0`, reinicia el juego — el juego sigue usando el default. Prometemos configurabilidad que no entregamos. -- **Propuesta**: O bien (a) conectar Options→runtime, o (b) eliminar los campos muertos. Híbrido: borrar `audio`/`gameplay`, conectar `physics` (apoya Fase 10). -- **Esfuerzo**: small (eliminar) o medium (conectar correctamente). - -#### 22. `defaults.hpp` mezcla constantes de cinco dominios distintos en un solo archivo de 530 líneas — [PRIORIDAD: media, estructural] -- **Dónde**: `source/core/defaults.hpp` (todo). -- **Problema**: Contiene Window, Game, Zones, Entities, Palette, Ship, Physics, Math, Brightness, Rendering, Audio, Music, Sound, Controls, Enemies (con sub-namespaces), Title, FloatingScore. **22 archivos** lo incluyen. Editar una constante de Audio fuerza recompilación de todo. Dos `namespace Game` separados (líneas 22-25 y 132-210). -- **Propuesta**: Partir en `defaults/window.hpp`, `defaults/game.hpp`, `defaults/zones.hpp`, etc. Con `defaults.hpp` como umbrella. -- **Esfuerzo**: medium - -#### 23. `Defaults::Window::WIDTH = 1280` y `Defaults::Game::WIDTH = 1280` son duplicados sin relación enforced — [PRIORIDAD: media, estructural] -- **Dónde**: `source/core/defaults.hpp:10` vs `:23`. -- **Problema**: Dos `WIDTH` distintos pero con el mismo valor. Nadie obliga a que coincidan. Si alguien quisiera arrancar a 1366×768 cambiando `Window::WIDTH`, el cálculo `zoom_factor = (float)width / Window::WIDTH` (`sdl_manager.cpp:101`) usa `Window::WIDTH`, no `Game::WIDTH` — bug latente. -- **Propuesta**: Decidir la fuente de verdad. Si es `Game::{WIDTH,HEIGHT}`, derivar `Window::WIDTH = Game::WIDTH`. -- **Esfuerzo**: small - -#### 24. `game/constants.hpp` contiene aliases legacy mayoritariamente muertos — [PRIORIDAD: baja, estructural] -- **Dónde**: `source/game/constants.hpp:10-23`. -- **Problema**: `Constants::MARGIN_*`, `MAX_ORNIS`, `MAX_BALES`, `VELOCITAT`, `VELOCITAT_MAX` — no se usan. Solo se usan los helpers (PI, isInPlayArea, getSafePlayAreaBounds, getPlayAreaCenter). -- **Propuesta**: Eliminar las constantes muertas. -- **Esfuerzo**: small - -#### 25. `Defaults::Physics::{ENEMY_SPEED, BULLET_SPEED, VELOCITY_SCALE}` legacy de Pascal son código muerto — [PRIORIDAD: media, estructural] -- **Dónde**: `source/core/defaults.hpp:218-220`. -- **Problema**: Los valores están en unidades/frame, comentados como "actuales del juego", pero la migración a SDL3 los reemplazó (`bullet.cpp:22` define su propio `BULLET_SPEED = 140.0F`; enemy speeds vienen de `Defaults::Enemies::{Pentagon,Cuadrado,Molinillo}::VELOCITAT`). -- **Propuesta**: Eliminar. -- **Esfuerzo**: small - -### F. Naming y consistencia de idioma - -#### 26. Variables del estado de juego mezclan catalán y castellano — [PRIORIDAD: baja, opinable] -- **Dónde**: `source/game/scenes/game_scene.hpp:67-77`, `source/game/scenes/title_scene.hpp:65-84`. -- **Problema**: Conviven `hit_timer_per_player_` (inglés), `lives_per_player_` (inglés), `match_config_.jugador1_actiu` (catalán). En TitleScene: `estat_actual_` (catalán) junto a `factor_lerp_`. No hay convención clara dentro de la misma clase. -- **Propuesta**: Si el usuario quiere preservar el sabor, reglar: identificadores expuestos en headers públicos en catalán, detalles internos en inglés. -- **Esfuerzo**: medium - -#### 27. Dos namespaces `Constants` no relacionados — [PRIORIDAD: baja, opinable] -- **Dónde**: `source/game/constants.hpp:8` (`namespace Constants`) y `source/game/stage_system/stage_config.hpp:82` (`namespace StageSystem::Constants`). -- **Problema**: Mismo nombre, scopes distintos. Lecturalmente confuso. -- **Propuesta**: Renombrar el del stage system a `StageSystem::Messages`. -- **Esfuerzo**: small - -### G. Headers y dependencias - -#### 28. `core/` depende de `game/` — violación de capas — [PRIORIDAD: alta, estructural] -- **Dónde**: - - `source/core/system/director.cpp:25-28` incluye `game/scenes/*.hpp` y `game/options.hpp` - - `source/core/rendering/sdl_manager.cpp:16` incluye `game/options.hpp` - - `source/core/system/debug_overlay.cpp:8` incluye `game/options.hpp` - - `source/core/input/input.cpp:11` incluye `game/options.hpp` -- **Problema**: La estructura sugiere un layered design, pero `core/` lee y muta `game::Options`. Si se intenta convertir `core/` en librería estática reusable, no compila. -- **Propuesta**: Mover `Options` a `source/core/config/`, o invertir la dependencia con `IConfigProvider*`. Relacionado con #21. -- **Esfuerzo**: medium - -#### 29. `continue_system.hpp` forward-declara `enum class GameOverState` definida a nivel global en `game_scene.hpp` — [PRIORIDAD: media, estructural] -- **Dónde**: `source/game/scenes/game_scene.hpp:28-32` (declaración en namespace global, NO dentro de la clase), `source/game/systems/continue_system.hpp:18-20`. -- **Problema**: Si la subyacente cambia de `uint8_t` a `int`, las dos declaraciones divergen silenciosamente. -- **Propuesta**: Mover `GameOverState` a `source/game/systems/game_over_state.hpp` (header de tipos puros). -- **Esfuerzo**: small - -#### 30. `core/defaults.hpp` se incluye en 22 archivos arrastrando ~530 líneas — [PRIORIDAD: media, estructural] -- **Dónde**: Ver #22. -- **Problema**: Tiempo de compilación incremental innecesariamente alto. -- **Propuesta**: Ver #22. -- **Esfuerzo**: medium - -### H. Bugs latentes / code smells - -#### 31. `hit_timer_per_player_` usa el valor mágico `999.0F` como sentinela "jugador permanentemente inactivo" — [PRIORIDAD: alta, estructural] -- **Dónde**: `source/game/scenes/game_scene.cpp:140,229,230,239,312,332`. -- **Problema**: El timer combina tres estados en un float: `== 0.0F` = vivo, `> 0 && < 3.0` = secuencia de muerte, `== 999.0F` = sin vidas. Si en algún momento se decrementa accidentalmente, el sentinela se pierde. -- **Propuesta**: Introducir un enum `PlayerStatus { ALIVE, DYING, OUT }` separado del timer; el timer solo cuenta `DYING`. -- **Esfuerzo**: medium - -#### 32. `std::rand()` en todo el gameplay; `std::mt19937` solo en LogoScene; reseed solo en GameScene::init — [PRIORIDAD: media, estructural] -- **Dónde**: `std::srand` en `game_scene.cpp:80`; `std::rand` en `spawn_controller.cpp:131`, `enemy.cpp:133-447`, `stage_manager.cpp:84`, `debris_manager.cpp:121-352`. `std::mt19937` en `logo_scene.cpp:171-173`. -- **Problema**: Mezcla de PRNG. `std::rand()` tiene calidad mala y es global. `srand` solo se llama una vez por partida; el logo y el título usan la seed por defecto. Repetibilidad accidental. -- **Propuesta**: Centralizar un `Random` (single mt19937 con seeds explícitas). Al menos `srand` en main/Director para que se siembre antes. -- **Esfuerzo**: small (centralizar srand) o medium (reemplazar todos). - -#### 33. `LineRenderer` mantiene dos globales mutables: `g_current_line_color` y `g_current_line_thickness` — [PRIORIDAD: media, estructural] -- **Dónde**: `source/core/rendering/line_renderer.cpp:11,15`. -- **Problema**: Estado mutable global en la TU. Si alguna escena cambia color y se olvida de restaurarlo, contamina las siguientes. Misma problemática con `g_current_scale_factor` en `coordinate_transform.cpp:9`. -- **Propuesta**: Moverlos a miembros del `GpuFrameRenderer`. El color global puede vivir como uniform de pipeline. -- **Esfuerzo**: medium - -#### 34. `GameScene::init()` reasigna `enemies_[i] = Enemy(...)` duplicando el trabajo del constructor — [PRIORIDAD: alta, estructural] -- **Dónde**: `source/game/scenes/game_scene.cpp:56-59` (ctor) vs `:149-153` (init). -- **Problema**: En el ctor se hace `enemy = Enemy(sdl.getRenderer())`. Inmediatamente después se llama `init()` (línea 64), que reasigna otra vez. Trabajo perdido. Inconsistencia con bullets (que solo se inicializan una vez). -- **Propuesta**: Eliminar la inicialización en el ctor o en init. Decidir cuál es la fuente única. -- **Esfuerzo**: small - -#### 35. `runCollisionDetections()` reconstruye un `Context` enorme cada frame — [PRIORIDAD: media, estructural] -- **Dónde**: `source/game/scenes/game_scene.cpp:470-484`, similar `stepContinueScreen` (`:252-282`). -- **Problema**: Cada frame en PLAYING se construye una struct con 9 referencias + un `std::function` (que aloca en heap por la captura `[this]`). 60 allocs/segundo del `std::function`. -- **Propuesta**: Cachear el Context como miembro o pasar el callback como puntero a función miembro. -- **Esfuerzo**: small - -#### 36. `SpawnController::generateSpawnEvents` precomputa toda la cola pero `update()` solo procesa secuencialmente — [PRIORIDAD: media, estructural] -- **Dónde**: `source/game/stage_system/spawn_controller.cpp:54-82`. -- **Problema**: El flag `event.spawnejat` parece sobrante (el index ya marca progreso). -- **Propuesta**: Verificación profunda. Simplificable. -- **Esfuerzo**: small - -#### 37. `Director::run()` es estático pero usa estado de instancia via singletons — [PRIORIDAD: baja, estructural] -- **Dónde**: `source/core/system/director.hpp:21-25`, `.cpp:41-48,150`. -- **Problema**: `executable_path_` se asigna en ctor pero nadie lo lee. `system_folder_` se usa en ctor pero no después. El patrón "Director con state" es decorativo. -- **Propuesta**: O hacer `run()` no-estática, o eliminar los miembros y mover `run()` a una función libre `App::main()`. -- **Esfuerzo**: small - -### Otros - -#### 38. `MatchConfig::jugador{1,2}_actiu` mutación desde dos sistemas independientes sin notificar — [PRIORIDAD: media, estructural] -- **Dónde**: `source/game/scenes/game_scene.cpp:912-916` (joinPlayer), `source/game/systems/continue_system.cpp:29-33` (revivePlayer). -- **Problema**: `MatchConfig` viaja como referencia mutable por los `Context` y se modifica desde múltiples sistemas. Rompe la intuición de que `MatchConfig` es "la configuración decidida en TitleScene". -- **Propuesta**: Renombrar el campo a `player_active_runtime` o mover el flag a otro lado (ej. dentro de `lives_per_player_`: si lives > 0 → activo). -- **Esfuerzo**: medium - -#### 39. Inconsistencia: `LogoScene::~LogoScene()` llama `stopAllSounds()`, `TitleScene::~TitleScene()` llama `stopMusic()`, `GameScene::~GameScene()` es default — [PRIORIDAD: baja, opinable] -- **Dónde**: `source/game/scenes/logo_scene.cpp:62-66`, `title_scene.cpp:99-102`, `game_scene.hpp:38`. -- **Problema**: Cada escena gestiona limpieza de audio a su manera. Si una olvida un sonido en loop, fuga. -- **Propuesta**: Decidir si el Director debe llamar `Audio::stopAllSounds()` y `stopMusic()` antes de destruir cada escena. -- **Esfuerzo**: small - -#### 40. `Bullet::BULLET_SPEED = 140.0F` en anonymous namespace, no en `defaults.hpp` — [PRIORIDAD: baja, opinable] -- **Dónde**: `source/game/entities/bullet.cpp:22-23`. -- **Problema**: Constante de gameplay escondida en una TU privada. Si alguien quiere tunear, mira en defaults.hpp y no la encuentra (ahí hay otro `BULLET_SPEED` diferente y muerto, ver #25). -- **Propuesta**: Mover a `Defaults::Entities::BULLET_SPEED_PXS`; eliminar el legacy de Pascal. -- **Esfuerzo**: small - ---- - -## Resumen ejecutivo - -### Top picks "bug latente / deuda clara" - -| # | Hallazgo | Esfuerzo | -|---|---|---| -| **21** | `Options::physics/audio/gameplay` se cargan del YAML pero nadie las consulta | small-medium | -| **28** | `core/` depende de `game/` (4 ficheros incluyen `game/options.hpp`) | medium | -| **11** | `Ship::isAlive() == isActive() == !isHit()` (tres alias del mismo bool) | small | -| **34** | `GameScene` construye `enemies_` dos veces (en ctor y en init) | small | -| **31** | `hit_timer_per_player_ = 999.0F` como sentinela | medium | -| **16** | `Rotation3D` es código muerto (28 líneas que nunca se ejecutan) | small | -| **1** | `GameScene::init()` no encaja en el lifecycle de `Scene` | small | - -### Top picks "limpieza con impacto" - -| # | Hallazgo | Esfuerzo | -|---|---|---| -| **22** | `defaults.hpp` (530 líneas, 22 includes) dividido en subarchivos | medium | -| **13** | `Entity::center_/angle_` mirror de `body_.position/angle` | medium | -| **25** | Constantes legacy del Pascal muertas en `Defaults::Physics` | small | -| **18** | `ShapeLoader::resolvePath` + `BASE_PATH` sin callers | small | - -### Hallazgos que requieren verificación más profunda antes de actuar -- **#5** (`SceneManager::actual`) -- **#9** (separación CollisionSystem/PhysicsWorld) -- **#20** (getters dead public) -- **#36** (`event.spawnejat`) - -### Hallazgos puramente opinables que el usuario podría rechazar legítimamente -- **#2, #4, #10, #14, #15, #19, #26, #27, #37, #39, #40** - -### Hallazgos estructurales claros (bug latente o deuda técnica medible) -- **#1, #6, #11, #12, #16, #21, #28, #31, #34** - ---- - -## Ya identificados y resueltos en la rama `chore/lint` (no incluidos arriba) -- Patrón draw-by-state de `GameScene` (refactorizado: complejidad 59 → 3) -- Complejidad de `TitleScene::update` (68 → 4) -- Duplicado `LetraLogo` Title/Logo -- Double `isActive()` en Bullet (redeclaración tras rename) -- Refactor de `DebrisManager::explode` (39 → 3) -- `Options::loadXXXConfigFromYaml` boilerplate (con templates `readField`) -- `PhysicsWorld::resolveBodyCollisions` (35 → 5) - -## Plan de ataque sugerido - -1. **#21** (en curso): borrar `Options::audio` y `Options::gameplay`, conectar `Options::physics` a runtime -2. **#16, #18, #24, #25** (dead code, commits limpios y rápidos) -3. **#34** (eliminar doble inicialización de `enemies_`) -4. **#11** (consolidar `isActive/isAlive/isHit`) -5. **#1** (limpiar lifecycle de `Scene`) -6. **#22 + #30** (partir `defaults.hpp` en subarchivos) -7. **#28** (mover `Options` a `core/`) -8. **#31** (enum `PlayerStatus` reemplazando 999.0F) -9. **#13** (eliminar mirror `center_/angle_`) - -A partir de ahí, decidir si seguir con el resto o pasar a otras prioridades (SDL_CALLBACKS, tuning de físicas). diff --git a/MIGRATION_PLAN.md b/MIGRATION_PLAN.md deleted file mode 100644 index 2f92025..0000000 --- a/MIGRATION_PLAN.md +++ /dev/null @@ -1,140 +0,0 @@ -# Plan de migración Orni Attack → SDL3 GPU + física vectorial - -Documento maestro de la rama `rewrite/physics-gpu`. Resumen del progreso y -qué queda. Si pierdes el contexto de la sesión, **léeme primero**. - -## Visión - -Modernizar la base técnica del juego antes de tunear jugabilidad: -1. Limpiar legacy (polares, mezcla catalán/inglés, header guards). -2. Adoptar formato 16:9 (1280×720). -3. Importar subsistemas robustos de AEEA (audio, *input deferido*). -4. Implementar física vectorial 2D con rigid bodies + colisiones elásticas. -5. **Migrar renderizado a SDL3 GPU sin fallback** ([memoria: project-no-sdl-fallback](./.claude/projects/-home-sergio-gitea-orni-attack/memory/project_no_sdl_fallback.md)). -6. Postprocesado y color. - -El tuning de feeling se hace al final, post-GPU. - -## Estado actual - -Rama de trabajo: **`rewrite/physics-gpu`** (pusheada a Gitea). -Tag de seguridad: **`beta-3.0`** (snapshot de `main` antes de empezar). - -| Fase | Estado | Commits | -|---|---|---| -| 0 — Limpieza de código muerto (primitives, polígonos, Bresenham, polares) | ✅ | `6cf990b` | -| 1a — `Punt → Vec2` con operadores modernos | ✅ | `cd38101` | -| 1b — Renames de entidades + métodos virtuales a camelBack | ✅ | `ae5cc1c` | -| 1c — Renames de escenas | ✅ | `5871d29` | -| 1d — Renames effects/stage_system/locales | ✅ | `7ee359b` | -| 1e — Pragma once + locales restantes + comentarios castellano | ✅ | `bf83f16` | -| (fix) — clave YAML `quadrat → cuadrado` post-sweep | ✅ | `56533ca` | -| 2 — Cambio a 1280×720 (16:9) | ✅ | `a4f6a55` | -| 3 — Import del subsistema de Audio desde AEEA | ✅ | `ed98ef6` | -| **4** — Input parcial AEEA | ⏭️ **saltado** (decisión usuario) | — | -| 5 — Infraestructura física (`RigidBody`, `PhysicsWorld`) | ✅ | `0fd9360` | -| 6a+b — `body_` en Entity + `world_` en GameScene | ✅ | `0574077` | -| 6c — Migrar Ship | ✅ | `2fe22ff` | -| 6d — Migrar Enemy | ✅ | `27242f5` | -| 6e — Migrar Bullet | ✅ | `9993b2d` | -| 7a — Infra GPU (shaders + wrappers, runtime dormido) | ✅ | `ba6fd00` | -| 7b+c — Swap atómico a SDL3 GPU + line_renderer al pipeline | ✅ | `fa7da4c` | -| 7d — Validación visual del usuario en hardware Vulkan | ✅ | — | -| 9a — Extraer `Systems::Collision` | ✅ | `896a899` | -| 9b — Extraer `Systems::ContinueScreen` | ✅ | `816bc02` | -| 9c — Extraer `Systems::InitHud` | ✅ | `a4942fc` | -| 9d — Descomponer `GameScene::update` (339→18 LOC) | ✅ | `808abb2` | -| **8 — Postprocesado + paleta de colores por entidad** | 🔲 **siguiente** | — | -| 10 — Tuning final de masa/restitución/damping | 🔲 | — | -| Mac/Metal — shaders MSL en build | 🔲 | — | -| 8 — Postprocesado, color, paleta por tipo | 🔲 | — | -| 9 — Refactor de GameScene (2.877 LOC → módulos) | 🔲 | — | -| 10 — Tuning final de masa/restitución/damping | 🔲 | — | - -## Lo que queda inmediato (Fase 8 — postprocesado + paleta de colores) - -Fase 7 y Fase 9 cerradas. Validación visual SDL3 GPU OK (`Backend GPU: vulkan`). -`GameScene.cpp` 1429 → 1015 LOC; `update()` 339 → 18 LOC, complexity baja a -< 10 por sub-paso. Tres sistemas extraídos a `source/game/systems/`: -- `Systems::Collision` (~210 LOC propios) -- `Systems::ContinueScreen` (~160 LOC propios) -- `Systems::InitHud` (~155 LOC propios) - -**Fase 8 — siguientes pasos**: -1. Color por entidad (paleta semántica): naves blancas, balas verdes, - pentagons azules, quadrats rojos, molinillos magenta, debris hereda - color del padre. Hoy todo se pinta con el color global del oscilador - (`g_current_line_color`). Hay que pasar el color al `pushLine` desde - las entidades (cada entity expone un `getColor()`). -2. Postprocesado básico: - - Bloom/glow: render-to-texture al swapchain real con fragment shader - que aplique kernel separable de blur sobre los píxeles iluminados. - Requiere un pipeline extra y un par de texturas off-screen. - - Opcional: scanlines o leve aberración cromática para feel CRT. - -**Fase 10** (tras 8): tuning de mass/restitution/damping con feedback -visual de los colores. - -**Mac/Metal**: cuando vayamos a release de macOS, añadir compilación -de los shaders también a MSL (con `spirv-cross` o `glslangValidator`). - -## Memoria del proyecto - -Las preferencias y decisiones persistentes están en: -`~/.claude/projects/-home-sergio-gitea-orni-attack/memory/MEMORY.md` y los -archivos enlazados desde ahí. Léelos al empezar sesión nueva. - -Resumen: -- **No fallback a SDL_Renderer** en Fase 7 — solo GPU o falla. -- **Naming**: `.clang-tidy` exige `CamelCase` tipos, `camelBack` métodos, - `lower_case_` miembros privados, `UPPER_CASE` constantes. -- **Costuras del título tras 16:9** son tuning artístico, no bug, post-Fase 10. -- **Push automático** tras cada commit en `rewrite/physics-gpu`. -- **Smoke test xvfb** (`timeout 5 xvfb-run -a ./build/orni 2>&1 | head -40`) - tras builds importantes para validar arranque. - -## Convenciones técnicas - -- **Lenguaje código**: inglés. **Lenguaje comentarios**: castellano. **Strings - de UI**: valenciano (preservados). -- **Coordenadas**: cartesianas (`Vec2`). Polares prohibidas (legacy). -- **Resolución lógica**: 1280×720. Constantes en `core/defaults.hpp`. -- **Audio**: import de AEEA. API: `Audio::get()->playSound/playMusic` con - crossfade y efectos. -- **Física**: `Physics::RigidBody` en `core/physics/rigid_body.hpp`, - `Physics::PhysicsWorld` en `core/physics/physics_world.hpp/.cpp`. - Entity tiene `body_` como member. Flujo tri-fase por frame en - `GameScene::update`: - 1. `physics_world_.update(dt)` — integrar y resolver - 2. `entity.postUpdate(dt)` — sincronizar mirror (center_, angle_) - 3. Lógica del juego (input, AI, etc.) — aplica fuerzas/cambia velocity - -## Cómo reanudar tras compactación - -1. Lee este archivo. -2. Lee `MEMORY.md` (memorias persistentes). -3. `git log --oneline -20` para ver últimos commits. -4. `git status` para ver si hay trabajo en curso sin commitear. -5. Si la fase actual estaba a medias: continúa donde se quedó (este archivo - indica en qué fase estamos). -6. Tras cada commit, push automático: `git push origin rewrite/physics-gpu`. - -## Configuración física actual (Fase 6e) - -| Entidad | mass | radius (world) | restitution | linear_damping | angular_damping | -|---|---|---|---|---|---| -| Ship | 10.0 | 12 | 0.6 | 1.5 | 0.0 | -| Enemy Pentagon | 5.0 | 20 | 1.0 | 0.0 | 0.0 | -| Enemy Quadrat | 8.0 | 20 | 1.0 | 0.0 | 0.0 | -| Enemy Molinillo | 4.0 | 20 | 1.0 | 0.0 | 0.0 | -| Bullet | 0.5 | **0** (cinemática) | 0 | 0 | 0 | -| Wall (PLAYAREA bounds) | ∞ (estático) | — | — | — | — | - -Nota: Bullet usa `radius=0` en el body físico para que no participe en las -colisiones del world (ni body-body ni bounds). El radio de gameplay -(`Defaults::Entities::BULLET_RADIUS = 3`) lo expone vía `getCollisionRadius()` -y lo consumen `check_collision` y la detección de salida del PLAYAREA en -`Bullet::update`. - -Las paredes son implícitas: `physics_world_.setBounds(PLAYAREA)` en -`GameScene::init`. No son `RigidBody` separados, sino un AABB que rebota. diff --git a/todo.txt b/todo.txt deleted file mode 100644 index 91da79b..0000000 --- a/todo.txt +++ /dev/null @@ -1,15 +0,0 @@ -ORNI TODO: -- Crear escena titulo: muestra el logo, el copyright, el texto de insert coin o press start y quizas un starfield de fondo -- Revisar contador de frames: hace cosas raras, hacer que se actualice cada segundo -- Añadir clase audio -> añadir ruidos a logo, title y game -- Añadir resource.pack -- Crear estados en GAME: GET_READY -> GAME -> GAME_OVER -> CONTINUE - -> STAGE_COMPLETED -> GET_READY (next stage) -- Añadir nuevos enemigos: cuadrado, ... otras formas (revisar Geometry Wars). Darles fiferente personalidad -- La bala ha de salir de la punta de la nave, con forma cuadrada, va rotando -- Pensar si todas las entidades deberian tener dos radios de colision: uno pequeño para colision con bordes o jugador, uno mas grande para colision con balas -- Revisar que los debris salgan realmente en direccion contraria al centro de la figura -- Crear progresion: fichero con enemigos para ese nivel. cuando se acaba la lista de niveles -> loop con dificultad aumentada. Pensar si jefe final. Juego no tiene fin -- Menu de servicio? numero de vidas, numero de continues o free play, controles de audio, controles de video, colores, activar parpadeo o cambiar frecuencia -- Cuando se destruye un enemigo, salen dos mas pequeños -- El escalado de pantalla hace cosas raras al salir de full screen: permite ventanas no-4:3