# 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 "© 1999 Visente i Sergi, 2025 Port") ``` **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 = "© 1999 Visente i Sergi, 2025 Port"; constexpr const char* GIT_HASH = "abc1234"; // From git rev-parse } ``` **Window title** (dynamic): `Orni Attack v0.3.0 (© 1999 Visente i Sergi, 2025 Port)` ### 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