Files
snow_trail/QWEN.md
2026-02-08 14:06:35 +01:00

352 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Snow Trail SDL Project
## Model Usage Guide
**When interacting with this codebase, I follow a step-by-step, concise approach:**
1. **Start with exploration**: Read files to understand context before making changes
2. **Build incrementally**: Make small, targeted changes and verify them
3. **Test after changes**: Run `cargo check` and relevant tests
4. **Keep explanations brief**: Code should speak for itself; comments only for complex logic
5. **Follow existing patterns**: Mimic the style, structure, and conventions in the codebase
**For task management**, I use the todo list to track multi-step work:
- Mark tasks as `in_progress` when starting
- Mark tasks as `completed` immediately after finishing
- Add new tasks when scope expands
- Never batch multiple completions
## Project Overview
This is a pure Rust game engine implementation (not a game yet) that serves as a migration from a Godot-based project. It's a 3D game using SDL3 for windowing/input, wgpu for rendering, rapier3d for physics, and features a low-res retro aesthetic with dithering.
The project implements an ECS (Entity Component System) architecture without engine dependencies, providing core systems for rendering, physics, input handling, and entity management.
### Key Technologies
- **SDL3**: Windowing and input handling (latest stable bindings)
- **wgpu**: Modern GPU API abstraction for rendering (Vulkan/Metal/DX12/OpenGL backends)
- **rapier3d**: Fast 3D physics engine
- **glam**: Fast vector/matrix math library
- **gltf**: Loading 3D models in glTF format
- **exr**: Loading EXR heightmap files for physics colliders
- **kurbo**: Bezier curve evaluation for movement acceleration curves
### Architecture: Pure ECS
- **Entities**: Just IDs (`EntityHandle = u64`), managed by `EntityManager`
- **Components**: Pure data structures stored in component storages (HashMap)
- **Systems**: Functions that query entities with specific component combinations
- **No Rc<RefCell<>>** - Clean ownership model with components as data in hashmaps
- **Component Storages** owned by single `World` struct
## Building and Running
### Build Commands
```bash
cargo build
cargo build --release
cargo check
cargo test
cargo run
cargo fmt
```
### Shader Compilation
The project uses a custom shader compilation system via the `wesl` crate:
- WGSL/WESL shaders are compiled at build time via `build.rs`
- Shaders are loaded at runtime via `std::fs::read_to_string()`, allowing hot-reloading by restarting the application
- Build artifact: `standard` shader package
### Runtime Behavior
- Window resolution: 800×600 (resizable)
- Rendering resolution: Low-res framebuffer (160×120) upscaled to window
- Target FPS: 60 FPS with fixed timestep physics (1/60s)
- Default mode: Noclip camera active
- Toggle modes: Press 'N' to toggle noclip/follow modes
## Development Conventions
### Code Style (from CLAUDE.md)
- **NO inline comments unless ABSOLUTELY necessary** - Code must be self-documenting
- **Doc comments (`///`)** only for public APIs and complex algorithms
- **All `use` statements must be at the file level** (module top), not inside function bodies
- **NO inline paths** - always add `use` statements at the top of files
- **Formatting**: `brace_style = "AlwaysNextLine"`, `control_brace_style = "AlwaysNextLine"`
### File Structure
```
src/
├── main.rs - SDL3 event loop, game loop orchestration, system execution order
├── entity.rs - EntityManager for entity lifecycle (spawn/despawn/query)
├── world.rs - World struct owning all component storages
├── camera.rs - 3D camera with rotation and follow behavior
├── physics.rs - PhysicsManager singleton (rapier3d world)
├── player.rs - Player entity spawning function
├── terrain.rs - Terrain entity spawning, glTF loading, EXR heightmap loading
├── render.rs - wgpu renderer, pipelines, bind groups, DrawCall execution
├── postprocess.rs - Low-res framebuffer and blit shader for upscaling
├── mesh.rs - Vertex/Mesh structs, plane/cube mesh generation, glTF loading
├── shader.rs - Standard mesh shader (WGSL) with diffuse+ambient lighting
├── state.rs - Generic StateMachine implementation
├── event.rs - Type-safe event bus (complementary to ECS)
├── picking.rs - Ray casting for mouse picking (unused currently)
├── heightmap.rs - EXR heightmap loading utilities
├── draw.rs - DrawManager (legacy, kept for compatibility)
├── texture_loader.rs - Texture loading utilities
└── systems/ - ECS systems (input, state_machine, physics_sync, render, camera)
├── input.rs
├── state_machine.rs
├── physics_sync.rs
├── render.rs
├── camera_follow.rs
├── camera_input.rs
└── camera_noclip.rs
├── components/ - ECS component definitions
├── input.rs
├── mesh.rs
├── movement.rs
├── physics.rs
├── player_tag.rs
├── jump.rs
├── camera.rs
└── camera_follow.rs
├── utility/ - Utility modules
├── input.rs - InputState (raw SDL input handling)
├── time.rs - Time singleton (game time tracking)
└── transform.rs - Transform struct (position/rotation/scale data type)
├── debug/ - Debug utilities
├── noclip.rs - Noclip camera controller
└── render_collider_debug.rs
└── shaders/ - WGSL/WESL shader files
├── shared.wesl - Shared shader utilities
├── standard.wesl - Standard mesh rendering with directional lighting
├── terrain.wesl - Terrain rendering with shadow mapping
└── blit.wgsl - Fullscreen blit for upscaling low-res framebuffer
```
### System Execution Order (main.rs game loop)
1. **SDL Events → InputState**: Poll events, handle raw input
2. **InputState → InputComponent**: `player_input_system()` converts raw input to gameplay commands
3. **State Machine Update**: `state_machine_physics_system()` and `state_machine_system()`
4. **Physics Simulation**: Fixed timestep physics step
5. **Physics → Transforms**: `physics_sync_system()` syncs physics bodies to transforms
6. **Rendering**: `render_system()` generates DrawCalls, renderer executes pipeline
7. **Cleanup**: Clear just-pressed states
### ECS Component Storages
All storages are owned by the `World` struct:
- `TransformStorage` - Position, rotation, scale
- `MeshStorage` - Mesh data + render pipeline
- `PhysicsStorage` - Rapier3d rigidbody/collider handles
- `MovementStorage` - Movement config + state
- `JumpStorage` - Jump mechanics state
- `InputStorage` - Gameplay input commands
- `PlayerTagStorage` - Marker for player entities
- `StateMachineStorage` - Behavior state machines
- `CameraStorage` - Camera components
- `CameraFollowStorage` - Camera follow behavior
### Input Handling (Two-Layer 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
**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`
**Input Flow**: SDL Events → InputState → InputComponent → Movement Systems
**Current Controls**:
- `W/A/S/D`: Movement
- `Space`: Jump
- `Shift`: Speed boost (noclip mode)
- `I`: Toggle mouse capture
- `Escape`: Quit game
- `N`: Toggle noclip/follow mode
- Mouse motion: Camera look (yaw/pitch)
### Rendering Pipeline
- **Low-res framebuffer** (160×120) renders to texture
- **Bayer 8×8 dithering** reduces colors to 3-bit RGB (8 colors)
- **Final blit pass** upscales framebuffer to window using nearest-neighbor sampling
- **Depth buffer** for 3D rendering with proper occlusion
- **Multiple render pipelines**: Standard mesh and terrain pipelines
- **Directional lighting**: Diffuse + ambient (basic Phong model)
### Terrain System
- **glTF mesh** exported from Blender 5.0 with baked height values in vertices
- **EXR heightmap** loaded for physics colliders (single-channel R32Float format)
- **Heightfield collider** created directly from EXR data
- **No runtime displacement** - vertices rendered directly
- **Separate terrain pipeline** for terrain-specific rendering
### Build System
- **`build.rs`**: Custom build script using `wesl` crate
- **Shader compilation**: `package::standard``standard` artifact
- **No external dependencies** needed for shader compilation at build time
### Dependencies Rationale
- **sdl3**: Modern SDL3 bindings for future-proofing
- **wgpu**: Modern GPU API with cross-platform support
- **rapier3d**: Fast physics engine with good Rust integration
- **gltf**: Standard 3D model format for asset pipeline
- **exr**: High-dynamic-range heightmap loading for physics
- **kurbo**: Bezier curves for smooth movement acceleration
- **bytemuck**: Safe byte casting for GPU buffer uploads
## Current Implementation Status
### Implemented Features
✅ Full ECS architecture (entities, components, systems)
✅ SDL3 windowing and input handling
✅ wgpu rendering with low-res framebuffer
✅ Multiple render pipelines (standard mesh + terrain)
✅ Bayer dithering for retro aesthetic
✅ glTF mesh loading
✅ EXR heightmap loading for physics
✅ rapier3d physics integration
✅ State machine system (generic implementation)
✅ Event bus (complementary to ECS)
✅ Camera system (free look + follow modes)
✅ Noclip mode for development
✅ Two-layer input pipeline
### In Progress
⚠️ Movement system (apply InputComponent → physics velocities)
⚠️ Ground detection and collision response
⚠️ Player state machine configuration (transitions not yet set up)
⚠️ Camera follow behavior (partial implementation)
⚠️ Snow deformation compute shaders
⚠️ Debug UI system
### Known Limitations
- Player entity spawns but is inactive (noclip camera used for testing)
- Movement physics not yet connected to input
- Ground detection not implemented
- State machine transitions not configured
- Camera follow needs refinement
## Content Creation Workflow
### Blender 5.0 (blender/)
- **Terrain modeling**: `terrain.blend`
- **Player character**: `player_mesh.blend`
- **Export formats**:
- glTF: `meshes/` for rendering (baked heights in vertices)
- EXR: `textures/` single-channel float heightmap for physics
### GIMP (gimp/)
- **Dither patterns**: `dither_patterns.xcf` (Bayer matrix patterns)
### Export Process
1. Model terrain in Blender 5.0
2. Export as glTF with baked height values
3. Export same terrain as EXR heightmap
4. Both files represent same data (visual/physics sync guaranteed)
## Future Development
### Next Steps (from CLAUDE.md)
1. Implement `movement_system` to apply `InputComponent` to physics velocities
2. Configure player state machine transitions (idle → walking → jumping → falling)
3. Implement ground detection (raycasting with QueryPipeline)
4. Add camera follow system (tracks player entity)
5. Integrate snow deformation compute shaders
6. Implement debug UI system for parameter tweaking
### Testing Strategy
- Systems are pure functions (easy to test)
- Create multiple `World` instances for isolation
- Query patterns are predictable
- State machine transitions are testable
## Technical Notes
### EXR Heightmap Loading (Physics Only)
```rust
use exr::prelude::{ReadChannels, ReadLayers};
let builder = exr::Image::new("heightmap.exr")
.no_deep_data()
.largest_resolution_level()
.all_channels()
.all_layers()
.all_attributes();
```
### Component Storage Pattern
```rust
pub struct TransformStorage {
pub components: HashMap<EntityHandle, Transform>,
}
impl TransformStorage {
pub fn with_mut<F, R>(&mut self, entity: EntityHandle, f: F) -> Option<R>
where
F: FnOnce(&mut Transform) -> R,
{
self.components.get_mut(&entity).map(f)
}
}
```
### State Machine Integration
- TypeId-based state identification
- Transitions as closures (can capture entity ID)
- State callbacks receive `&mut World` for component access
- Safe pattern: Remove → Update → Insert to avoid borrow conflicts
### Event System (Complementary to ECS)
- Handles irregular, one-time occurrences
- Cross-system messaging without tight coupling
- Global `add_listener()` and `emit()` functions
- Example: FootstepEvent for snow deformation
### Shader Hot-Reloading
- Shaders loaded at runtime via `std::fs::read_to_string()`
- Restart application to reload shaders
- No recompilation needed
## Quick Reference
### Running the Project
```bash
cd /home/jonas/projects/snow_trail_sdl
cargo run
```
### Building for Release
```bash
cargo build --release
```
### Formatting Code
```bash
cargo fmt
```
### Toggling Modes at Runtime
- Press **N** to toggle between noclip and follow modes
- Press **I** to toggle mouse capture
- Press **Escape** to quit
### Working with Shaders
- Edit `.wesl` files in `src/shaders/`
- Changes take effect on application restart
- Build script compiles `package::standard``standard` artifact
### Adding New Components
1. Define component struct (pure data, no Rc<RefCell>)
2. Add storage to `world.rs` (HashMap<EntityHandle, Component>)
3. Add storage to `World` struct
4. Update `World::despawn()` to clean up component
5. Create systems that query and modify the component
### Adding New Systems
1. Add function in `systems/` directory
2. Import at top of `main.rs`
3. Add to system execution order in game loop
4. Systems receive `&mut World` (or `&World` for read-only)