# CLAUDE.md

This file provides guidance to Claude Code when working with code in this repository.

## Project Overview

This is a pure Rust game project using SDL3 for windowing/input, wgpu for rendering, rapier3d for physics, and a low-res retro aesthetic with dithering. This is a migration from the Godot-based snow_trail project, implementing the same snow deformation system and character controller without engine dependencies.

**Content Creation:** Blender 5.0 is used for terrain modeling and asset export (glTF meshes + EXR heightmaps).

## Code Style

**Code Documentation Guidelines**:
- **NO inline comments unless ABSOLUTELY necessary**
- Code must be self-documenting through clear naming and structure
- Use doc comments (`///`) only for public APIs and complex algorithms
- Avoid obvious comments that restate what the code does
- Let the code speak for itself

**Formatting:**
- All code must follow the project's `rustfmt.toml` configuration
- Always run `cargo fmt` before committing to ensure consistent formatting
- Current rustfmt settings: brace_style = "AlwaysNextLine", control_brace_style = "AlwaysNextLine"
- **NO inline paths** - always add `use` statements at the top of files (e.g., `use std::rc::Rc;` instead of `std::rc::Rc` inline in code)
- **NO inline `use` statements in functions** - all `use` statements must be at the file level (module top), not inside function bodies or impl blocks

## Architecture Decisions

### ECS Architecture

The project uses a **pure ECS (Entity Component System)** architecture:

**Entities:**
- Just IDs (`EntityHandle = u64`)
- Managed by `EntityManager` (spawn/despawn)
- No data themselves - just containers for components

**Components:**
- Pure data structures stored in component storages
- Each storage is a `HashMap<EntityHandle, ComponentData>`
- No `Rc<RefCell<>>` - clean ownership model
- Components: Transform, Mesh, Physics, Movement, Input, PlayerTag, StateMachine

**Systems:**
- Functions that query entities with specific component combinations
- Run each frame in defined order
- Read from and write to component storages
- Examples: `player_input_system`, `state_machine_system`, `physics_sync_system`, `render_system`

**Component Storages (World-owned):**
- `TransformStorage` - Position, rotation, scale
- `MeshStorage` - Mesh data + render pipeline
- `PhysicsStorage` - Rapier3d rigidbody/collider handles
- `MovementStorage` - Movement config + state
- `InputStorage` - Gameplay input commands
- `PlayerTagStorage` - Marker for player entities
- `StateMachineStorage` - Behavior state machines
- All storages owned by single `World` struct for clean ownership

**Key Benefits:**
- No `Rc<RefCell<>>` needed - components are just data
- Clear data flow through systems
- Easy to add/remove components at runtime
- Testable - systems are pure functions
- StateMachine integrates as a component for complex behaviors
- EventBus remains for irregular events and cross-system messaging

### SDL3 vs SDL2
We are using SDL3 (latest stable bindings) rather than SDL2. SDL3 provides:
- Modern GPU API integration
- Better input handling
- Active development and future-proofing

As of December 2025, SDL3 Rust bindings are usable but still maturing:
- `sdl3` crate: v0.16.2 (high-level bindings)
- `sdl3-sys` crate: v0.5.11 (low-level FFI)
- Some features may be incomplete, but core functionality is stable

### wgpu for Rendering

**Using wgpu instead of OpenGL:**
- Modern GPU API abstraction (Vulkan/Metal/DX12/OpenGL backends)
- Better cross-platform support
- WESL shader language (WebGPU Shading Language)
- Type-safe API with explicit resource management
- Low-res framebuffer rendering with 3-bit RGB dithering (retro aesthetic)

**Rendering Architecture:**
- wgpu for 3D mesh rendering with custom shaders
- Low-resolution framebuffer (160×120) upscaled to window size
- Multiple rendering pipelines: standard meshes and terrain
- Separate bind groups for different material types

### Future: Debug UI
- Debug UI system not yet implemented
- Will be used for real-time parameter tweaking (replacing Godot's exported properties)
- Current debugging relies on println! and recompilation

## Physics Integration

Using rapier3d for 3D physics:
- Character controller implemented manually (no built-in CharacterBody equivalent)
- Ground detection via raycasting with QueryPipeline
- Manual rigidbody velocity application
- State machine for movement states (Idle, Walking, Jumping, Falling)

## Input Handling

**Two-Layer Input Pipeline:**

**Layer 1: Raw Input (`utility/input.rs` - `InputState`):**
- Global singleton for SDL event handling
- Tracks raw hardware state (W/A/S/D pressed, mouse delta, etc.)
- Handles SDL events via `handle_event()` method
- Manages global state (mouse capture, quit request, noclip mode)
- Lives in main event loop

**Layer 2: Gameplay Commands (`components/input.rs` - `InputComponent`):**
- Per-entity ECS component
- Stores processed gameplay commands (move_direction, jump_pressed)
- Filled by `player_input_system()` which reads `InputState`
- Used by movement systems to control entities
- Decouples input source from entity control

**Input Flow:**
```
SDL Events → InputState → player_input_system() → InputComponent → movement_system()
```

**Current Input Layout:**
- `W/A/S/D`: Movement (converted to Vec3 direction in InputComponent)
- `Space`: Jump (sets jump_pressed in InputComponent)
- `Shift`: Speed boost (for noclip camera)
- `I`: Toggle mouse capture (lock/unlock cursor)
- `Escape`: Quit game
- `N`: Toggle noclip mode
- Mouse motion: Camera look (yaw/pitch)

## Rendering Pipeline

**wgpu Rendering System:**
- Low-res framebuffer (160×120) renders to texture
- Final blit pass upscales framebuffer to window using nearest-neighbor sampling
- Depth buffer for 3D rendering with proper occlusion

**Terrain Rendering:**
- glTF mesh exported from Blender 5.0 with baked height values in vertices
- No runtime displacement in shader - vertices rendered directly
- Separate terrain pipeline for terrain-specific rendering
- Terrain mesh has heights pre-baked during export for optimal performance

**Terrain Physics:**
- EXR heightmap files loaded via `exr` crate (single-channel R32Float format)
- Heightmap loaded directly into rapier3d heightfield collider
- No runtime sampling or computation - instant loading
- Both glTF and EXR exported from same Blender terrain, guaranteed to match

**Lighting Model:**
- Directional light (like Godot's DirectionalLight3D)
- Diffuse + ambient lighting (basic Phong model, no specular)
- Light direction is uniform across entire scene
- No attenuation or distance falloff
- Dithering applied after lighting calculations

## Migration from Godot

This project ports the snow_trail Godot project (located at `~/shared/projects/snow_trail`) to pure Rust:

**What carries over:**
- Snow deformation compute shader logic (GLSL can be reused with minor adjustments)
- Character controller state machine architecture
- Movement physics parameters
- Camera follow behavior

**What changes:**
- No `Base<Node3D>` pattern → **Pure ECS with EntityHandle + Components**
- No Godot scene tree → **Entity-Component-System architecture**
- No exported properties → Components with data (debug UI planned for future)
- rapier3d RigidBodyHandle in PhysicsComponent instead of Gd<RigidBody3D>
- Manual ground detection instead of CharacterBody3D.is_on_floor()
- **Component storages** (TransformStorage, MeshStorage, etc.) instead of Godot nodes
- **Systems** (player_input_system, state_machine_system, etc.) instead of _process()
- **No `Rc<RefCell<>>`** - components are just data in hashmaps
- Event bus implemented from scratch (complementary to systems)
- State machine implemented from scratch (integrates as ECS component)

## Build Commands

```bash
cargo build
cargo build --release
cargo check
cargo test
cargo run
cargo fmt
```

## Shader Files

WESL shaders are stored in the `src/shaders/` directory:
- `src/shaders/standard.wesl` - Standard mesh rendering with directional lighting
- `src/shaders/terrain.wesl` - Terrain rendering with shadow mapping (no displacement)
- `src/shaders/blit.wgsl` - Fullscreen blit for upscaling low-res framebuffer

Shaders are loaded at runtime via `std::fs::read_to_string()`, allowing hot-reloading by restarting the application.

## Module Structure

**Core:**
- `main.rs` - SDL3 event loop, game loop orchestration, system execution order
- `entity.rs` - EntityManager for entity lifecycle (spawn/despawn/query)
- `world.rs` - World struct that owns all component storages and EntityManager

**ECS Components (`components/`):**
- `input.rs` - InputComponent (gameplay commands)
- `mesh.rs` - MeshComponent (mesh + pipeline)
- `movement.rs` - MovementComponent (movement config/state)
- `physics.rs` - PhysicsComponent (rigidbody/collider handles)
- `player_tag.rs` - PlayerTag marker component
- `state_machine.rs` - (empty, StateMachine defined in state.rs)
- Note: Component *storages* are defined in `world.rs`, not in component files

**ECS Systems (`systems/`):**
- `input.rs` - player_input_system (InputState → InputComponent)
- `state_machine.rs` - state_machine_system (updates all state machines)
- `physics_sync.rs` - physics_sync_system (physics → transforms)
- `render.rs` - render_system (queries entities, generates DrawCalls)

**Rendering:**
- `render.rs` - wgpu renderer, pipelines, bind groups, DrawCall execution
- `shader.rs` - Standard mesh shader (WESL) with diffuse+ambient lighting
- `terrain.rs` - Terrain entity spawning, glTF loading, EXR heightmap → physics collider
- `postprocess.rs` - Low-res framebuffer and blit shader for upscaling
- `mesh.rs` - Vertex/Mesh structs, plane/cube mesh generation, glTF loading
- `draw.rs` - DrawManager (legacy, kept for compatibility)

**Game Logic:**
- `player.rs` - Player entity spawning function
- `camera.rs` - 3D camera with rotation and follow behavior
- `movement.rs` - Movement configuration and state structs
- `state.rs` - Generic StateMachine implementation
- `physics.rs` - PhysicsManager singleton (rapier3d world)

**Utilities:**
- `utility/input.rs` - InputState (raw SDL input handling)
- `utility/time.rs` - Time singleton (game time tracking)
- `utility/transform.rs` - Transform struct (position/rotation/scale data type)

**Debug:**
- `debug/noclip.rs` - Noclip camera controller for development

**Other:**
- `event.rs` - Type-safe event bus (complementary to ECS for irregular events)
- `picking.rs` - Ray casting for mouse picking (unused currently)

## Dependencies Rationale

- **sdl3**: Windowing, input events, and platform integration
- **wgpu**: Modern GPU API abstraction for rendering (Vulkan/Metal/DX12 backends)
- **pollster**: Simple blocking executor for async wgpu initialization
- **rapier3d**: Fast physics engine with good Rust integration
- **glam**: Fast vector/matrix math library (vec3, mat4, quaternions)
- **nalgebra**: Linear algebra for rapier3d integration (Isometry3 conversions)
- **bytemuck**: Safe byte casting for GPU buffer uploads (Pod/Zeroable for vertex data)
- **anyhow**: Ergonomic error handling
- **gltf**: Loading 3D models in glTF format
- **exr**: Loading EXR heightmap files (single-channel float data for physics colliders)
- **kurbo**: Bezier curve evaluation for movement acceleration curves

## Technical Notes

### EXR Heightmap Loading (Physics Only)
When loading EXR files with the `exr` crate for physics colliders:
- Must import traits: `use exr::prelude::{ReadChannels, ReadLayers};`
- Use builder pattern: `.no_deep_data().largest_resolution_level().all_channels().all_layers().all_attributes().from_file(path)`
- Extract float data: `channel.sample_data.values_as_f32().collect()`
- Convert to nalgebra DMatrix for rapier3d heightfield collider
- No GPU texture creation - physics data only

### Blender Export Workflow
**Using Blender 5.0** for terrain creation and export:
- Export terrain as **glTF** with baked height values in mesh vertices
- Export same terrain as **EXR** heightmap (single-channel R32Float)
- Both files represent the same terrain data, guaranteeing visual/physics sync
- glTF used for rendering (vertices rendered directly, no shader displacement)
- EXR used for physics (loaded into rapier3d heightfield collider)

### Multiple Render Pipelines
- `Pipeline` enum determines which pipeline to use per DrawCall
- Different pipelines can have different shaders, bind group layouts, uniforms
- Terrain pipeline: shadow-mapped rendering with line hatching shading
- Standard pipeline: basic mesh rendering with diffuse lighting
- Each pipeline writes to its own uniform buffer before rendering

### ECS Component Storages

**Pattern:**
All component storages are owned by the `World` struct:

```rust
pub struct World {
    pub entities: EntityManager,
    pub transforms: TransformStorage,
    pub meshes: MeshStorage,
    pub physics: PhysicsStorage,
    pub movements: MovementStorage,
    pub inputs: InputStorage,
    pub player_tags: PlayerTagStorage,
    pub state_machines: StateMachineStorage,
}

pub struct TransformStorage {
    pub components: HashMap<EntityHandle, Transform>,
}

impl TransformStorage {
    pub fn insert(&mut self, entity: EntityHandle, component: Transform) { }
    pub fn get(&self, entity: EntityHandle) -> Option<&Transform> { }
    pub fn with_mut<F, R>(&mut self, entity: EntityHandle, f: F) -> Option<R> { }
    pub fn remove(&mut self, entity: EntityHandle) { }
    pub fn all(&self) -> Vec<EntityHandle> { }
}
```

**Key Features:**
- No `Rc<RefCell<>>` needed - clean ownership model
- World owns all component data - explicit ownership
- Instance methods instead of static methods
- Systems receive `&mut World` - clear data dependencies
- Easy to test - can create multiple worlds
- Safe lookups return `Option`

**Example Usage:**
```rust
// Create world and entity
let mut world = World::new();
let entity = world.spawn();

// Insert components via world
world.transforms.insert(entity, Transform::IDENTITY);
world.meshes.insert(entity, MeshComponent { ... });

// Query and update via world
world.transforms.with_mut(entity, |transform| {
    transform.position += velocity * delta;
});

// Systems receive world
pub fn my_system(world: &mut World) {
    for entity in world.player_tags.all() {
        if let Some(input) = world.inputs.get(entity) {
            // Process input...
        }
    }
}

// Cleanup
world.despawn(entity);  // Removes from all storages
```

### State Machine as ECS Component

StateMachine integrates into ECS as a component for complex entity behaviors:

**State Machine Pattern:**
- StateMachine owns all states via `HashMap<TypeId, Box<dyn State>>`
- TypeId-based state identification
- Transition conditions are simple closures (can capture entity ID)
- State callbacks receive `&mut World` and can access any component
- Updated by `state_machine_system()` each frame using safe remove/insert pattern

**Integration Example:**
```rust
// Create state machine for entity
let mut sm = StateMachine::new(Box::new(IdleState { entity }));
sm.add_state(WalkingState { entity });

// Transitions can capture entity for checking
sm.add_transition::<IdleState, WalkingState>(move || {
    // Note: transitions run before update, so they don't access world
    false  // Placeholder - implement proper transition logic
});

// Insert into world
world.state_machines.insert(entity, sm);

// States receive world when updated
impl State for IdleState {
    fn on_state_update(&mut self, world: &mut World, delta: f32) {
        // States can access any component via world
        if let Some(input) = world.inputs.get(self.entity) {
            // React to input...
        }
    }
}
```

**State Machine System (Safe Pattern):**
```rust
pub fn state_machine_system(world: &mut World, delta: f32) {
    let entities: Vec<_> = world.state_machines.all();

    for entity in entities {
        // Temporarily remove state machine to avoid borrow conflicts
        if let Some(mut state_machine) = world.state_machines.components.remove(&entity) {
            state_machine.update(world, delta);  // States can now safely access world
            world.state_machines.components.insert(entity, state_machine);
        }
    }
}
```

### Event System (event.rs)

**Complementary to ECS:**
- Events handle irregular, one-time occurrences
- Systems handle regular per-frame updates
- Events enable cross-system messaging without tight coupling

**Event Bus Features:**
- No `Clone` requirement on events (fire-and-forget)
- `FnMut` handlers allow stateful callbacks
- Global `add_listener()` and `emit()` functions
- Handlers can access ECS components directly via storages

**ECS Integration:**
```rust
#[derive(Debug)]
struct FootstepEvent { position: Vec3, force: f32 }
impl Event for FootstepEvent {}

// System emits event
pub fn foot_contact_system(world: &World) {
    for player in world.player_tags.all() {
        if is_on_ground(player) {
            let pos = world.transforms.get(player).unwrap().position;
            emit(&FootstepEvent { position: pos, force: 10.0 });
        }
    }
}

// Event handler (global listener, not part of World)
add_listener(|event: &FootstepEvent| {
    snow_terrain::deform_at_position(event.position, event.force);
});
```

**When to use:**
- ✅ One-time events (collision, death, pickup)
- ✅ Cross-system communication (audio, VFX triggers)
- ✅ Spawning/despawning entities
- ❌ Regular updates (use systems instead)

### ECS Systems

**System Execution Order (main.rs game loop):**
```rust
let mut world = World::new();

'running: loop {
    // 1. SDL Events → InputState
    for event in event_pump.poll_iter() {
        input_state.handle_event(&event);
    }

    // 2. InputState → InputComponent (player_input_system)
    player_input_system(&mut world, &input_state);

    // 3. Update state machines (state_machine_system)
    state_machine_system(&mut world, delta);

    // 4. Simulate physics (PhysicsManager)
    PhysicsManager::physics_step();

    // 5. Sync physics → transforms (physics_sync_system)
    physics_sync_system(&mut world);

    // 6. Render (render_system)
    let draw_calls = render_system(&world);
    render::render(&camera, &draw_calls, time);

    // 7. Cleanup
    input_state.clear_just_pressed();
}
```

**System Patterns:**

**Query Pattern:**
```rust
pub fn my_system(world: &mut World) {
    let entities = world.my_storage.all();  // All entities with this component

    for entity in entities {
        world.my_storage.with_mut(entity, |component| {
            // Process component
        });
    }
}
```

**Multi-Component Query:**
```rust
pub fn movement_system(world: &mut World) {
    for entity in world.player_tags.all() {
        if let Some(input) = world.inputs.get(entity) {
            if let Some(movement) = world.movements.get(entity) {
                world.transforms.with_mut(entity, |transform| {
                    // Update position based on input + movement
                });
            }
        }
    }
}
```

### Movement System (movement.rs)
Configuration and state for character movement physics:

**Horizontal Movement:**
- `HorizontalMovementConfig`: Parameters for ground movement (acceleration, damping, speed limits)
- `HorizontalMovementState`: Runtime state (input direction, flooring status, surface normal)
- Uses Bezier curves (kurbo::CubicBez) for smooth acceleration ramps
- Separate damping for walking vs idle states

**Vertical Movement:**
- `VerticalMovementConfig`: Jump parameters (height, duration, air control)
- `VerticalMovementState`: Jump execution tracking (progress, peak detection, abort state)
- Bezier curves for jump height progression over time
- Peak detection allows early jump termination with smooth falloff

**Key Features:**
- Physics parameters tuned to match Godot prototype
- Curve-based interpolation for responsive feel
- State tracking for ground detection and jump execution
- Configurable air control and momentum limits
- Integration with Time singleton for execution timing

**Usage Pattern:**
```rust
let config = HorizontalMovementConfig::new();
let mut state = HorizontalMovementState::new();

state.move_input = Vec3::new(input.x, 0.0, input.z);
state.forward_direction = camera.forward();
state.is_floored = ground_check.is_grounded;

// Apply movement physics using config + state
```

### Time System (utility/time.rs)
Global game time tracking using OnceLock singleton:

**Implementation:**
```rust
static GAME_START: OnceLock<Instant> = OnceLock::new();

pub struct Time;
impl Time {
    pub fn init() { GAME_START.get_or_init(Instant::now); }
    pub fn get_time_elapsed() -> f32 { /* ... */ }
}
```

**Key Features:**
- Thread-safe singleton using std::sync::OnceLock
- Single initialization point (call Time::init() at startup)
- Returns elapsed time as f32 seconds
- Used for animation, jump timing, and time-based effects
- Zero-cost after initialization (static lookup)

**Usage:**
```rust
Time::init(); // In main() before game loop

let time = Time::get_time_elapsed(); // Anywhere in code
```

