dreamwell-runtime 1.0.0

Dreamwell Runtime — cross-platform GPU-accelerated game client
Documentation
// Input — winit event adapter for the Dreamwell runtime.
// Translates winit events into engine VirtualKeys and delegates to FabricInput.

use dreamwell_engine::input::VirtualKey;
use dreamwell_fabric::FabricInput;
use dreamwell_gpu::camera::Camera;
use winit::event::{ElementState, KeyEvent, MouseButton, MouseScrollDelta};
use winit::keyboard::{KeyCode, PhysicalKey};

const SCROLL_DEAD_ZONE: f32 = 0.01;
const CURSOR_DEAD_ZONE: f32 = 0.1;
const PAN_SPEED: f32 = 0.5;
const SCROLL_SENSITIVITY: f32 = 0.5;
const PIXEL_SCROLL_SCALE: f32 = 0.01;

/// Orbit camera sensitivity (radians per pixel of mouse movement).
const ORBIT_YAW_SENSITIVITY: f32 = 0.005;
/// Vertical orbit is slightly less sensitive than horizontal (feels natural).
const ORBIT_PITCH_SENSITIVITY: f32 = 0.003;

/// Runtime action vocabulary — every player input mapped to a control field change.
/// Cross-platform: PC first, maps to Xbox/PS/Switch controllers.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum RuntimeAction {
    /// WASD / Left Stick → camera-relative direction + target speed + phase bias + shape compression.
    MoveIntent,
    /// Mouse / Right Stick → orbit yaw/pitch.
    LookIntent,
    /// Left Shift / L3 → accel cap increase + drag reduction + cohesion spike + forward compression + FOV push.
    BurstStart,
    BurstEnd,
    /// Q / LB / L1 / L → coherence → 1.0 (compact fibonacci particle).
    SetFormCohere,
    /// E / RB / R1 / R → coherence → 0.0 (broad wave fabric).
    SetFormWave,
    /// Left Ctrl / LT / L2 / ZL → increase cohesion + reduce amplitude + strengthen restoring force + tighten collision.
    GatherStart,
    GatherEnd,
    /// Left Mouse / RT / R2 / ZR → emit/wave push (analog strength).
    PrimaryActionStart,
    PrimaryActionEnd,
    /// F / X / Square / Y → POI couple.
    Interact,
    /// Space / A / Cross / B → vertical impulse.
    Jump,
    /// C / Y / Triangle / X → snap camera behind player.
    CameraRecenter,
}

/// Runtime input state. Wraps FabricInput with winit-specific translation.
pub struct InputState {
    pub fabric: FabricInput,
    /// Raw scroll delta for camera application (before dead zone).
    scroll_raw: f32,
    /// Active touch points for mobile input.
    active_touches: std::collections::HashMap<u64, [f32; 2]>,
    /// Accumulated orbit yaw delta from right-click drag (consumed by chase camera).
    pub orbit_dx: f32,
    /// Accumulated orbit pitch delta from right-click drag (consumed by chase camera).
    pub orbit_dy: f32,
    /// Scroll zoom delta (consumed by chase camera distance).
    pub scroll_delta: f32,
}

impl InputState {
    pub fn new() -> Self {
        Self {
            fabric: FabricInput::new(),
            scroll_raw: 0.0,
            active_touches: std::collections::HashMap::new(),
            orbit_dx: 0.0,
            orbit_dy: 0.0,
            scroll_delta: 0.0,
        }
    }

    /// Translate a winit KeyEvent into VirtualKey press/release.
    pub fn handle_keyboard(&mut self, event: &KeyEvent) {
        if let PhysicalKey::Code(code) = event.physical_key {
            if let Some(vk) = winit_to_virtual_key(code) {
                match event.state {
                    ElementState::Pressed => self.fabric.key_down(vk),
                    ElementState::Released => self.fabric.key_up(vk),
                }
            }
        }
    }

    /// Translate a winit mouse button event.
    pub fn handle_mouse_button(&mut self, button: MouseButton, state: ElementState) {
        let vk = match button {
            MouseButton::Left => VirtualKey::MouseLeft,
            MouseButton::Right => VirtualKey::MouseRight,
            MouseButton::Middle => VirtualKey::MouseMiddle,
            MouseButton::Back => VirtualKey::MouseButton4,
            MouseButton::Forward => VirtualKey::MouseButton5,
            _ => return,
        };
        match state {
            ElementState::Pressed => self.fabric.key_down(vk),
            ElementState::Released => self.fabric.key_up(vk),
        }
    }

    /// Feed cursor movement.
    pub fn handle_cursor_move(&mut self, x: f32, y: f32) {
        self.fabric.cursor_moved(x, y);
    }

    /// Feed scroll wheel.
    pub fn handle_scroll(&mut self, delta: &MouseScrollDelta) {
        let y = match delta {
            MouseScrollDelta::LineDelta(_, y) => *y,
            MouseScrollDelta::PixelDelta(pos) => pos.y as f32 * PIXEL_SCROLL_SCALE,
        };
        self.scroll_raw = y;
        self.fabric.scroll(y);
    }

    /// Apply input state to camera. `dt` is frame delta time in seconds
    /// for frame-rate independent movement.
    pub fn apply_to_camera(&mut self, camera: &mut Camera, dt: f32) {
        let frame = self.fabric.build_frame();
        let dt_pan = PAN_SPEED * dt * 60.0; // Normalize to 60fps baseline

        // Scroll zoom
        if self.scroll_raw.abs() > SCROLL_DEAD_ZONE {
            camera.zoom(-self.scroll_raw * SCROLL_SENSITIVITY);
            self.scroll_raw = 0.0;
        }

        // Right-click drag: rotate
        if frame.has_action(dreamwell_engine::input::InputAction::CameraRotate) {
            let [dx, dy] = frame.cursor_delta;
            if dx.abs() > CURSOR_DEAD_ZONE || dy.abs() > CURSOR_DEAD_ZONE {
                camera.rotate(dx, dy);
            }
        }

        // Middle-click drag: pan
        if frame.has_action(dreamwell_engine::input::InputAction::CameraPan) {
            let [dx, dy] = frame.cursor_delta;
            if dx.abs() > CURSOR_DEAD_ZONE || dy.abs() > CURSOR_DEAD_ZONE {
                camera.pan(-dx, dy);
            }
        }

        // WASD: pan (from movement vector), dt-scaled for frame-rate independence.
        let [mx, my] = frame.movement;
        if my > 0.0 {
            camera.pan(0.0, dt_pan);
        }
        if my < 0.0 {
            camera.pan(0.0, -dt_pan);
        }
        if mx < 0.0 {
            camera.pan(dt_pan, 0.0);
        }
        if mx > 0.0 {
            camera.pan(-dt_pan, 0.0);
        }
    }

    /// Apply only mouse/scroll input for chase camera (no WASD pan).
    /// Used when simulation is active and WASD drives the player kernel.
    /// Accumulates orbit_dx, orbit_dy, and scroll_delta for the chase camera in app.rs.
    pub fn apply_mouse_to_camera(&mut self, _camera: &mut Camera, _dt: f32) {
        // Scroll zoom → accumulate delta for chase camera distance
        if self.scroll_raw.abs() > SCROLL_DEAD_ZONE {
            self.scroll_delta += -self.scroll_raw * SCROLL_SENSITIVITY;
            self.scroll_raw = 0.0;
        }

        // Right-click drag → accumulate orbit yaw + pitch delta
        let frame = self.fabric.build_frame();
        if frame.has_action(dreamwell_engine::input::InputAction::CameraRotate) {
            let [dx, dy] = frame.cursor_delta;
            if dx.abs() > CURSOR_DEAD_ZONE {
                self.orbit_dx += dx * ORBIT_YAW_SENSITIVITY;
            }
            if dy.abs() > CURSOR_DEAD_ZONE {
                self.orbit_dy += dy * ORBIT_PITCH_SENSITIVITY;
            }
        }
    }

    /// Poll gamepad input and apply to FabricInput.
    /// Only available when the `gamepad` feature is enabled.
    #[cfg(feature = "gamepad")]
    pub fn handle_gamepad(&mut self, gilrs: &mut gilrs::Gilrs, camera: &mut Camera) {
        while let Some(event) = gilrs.next_event() {
            // Process but don't act on events — we read state below.
            let _ = event;
        }
        // Read first active gamepad.
        if let Some((_id, gamepad)) = gilrs.gamepads().next() {
            let lx = gamepad.value(gilrs::Axis::LeftStickX);
            let ly = gamepad.value(gilrs::Axis::LeftStickY);
            let rx = gamepad.value(gilrs::Axis::RightStickX);
            let ry = gamepad.value(gilrs::Axis::RightStickY);

            const DEAD_ZONE: f32 = 0.15;

            // Left stick: camera pan.
            if lx.abs() > DEAD_ZONE || ly.abs() > DEAD_ZONE {
                camera.pan(-lx * PAN_SPEED, ly * PAN_SPEED);
            }

            // Right stick: camera rotate.
            if rx.abs() > DEAD_ZONE || ry.abs() > DEAD_ZONE {
                camera.rotate(rx * 2.0, ry * 2.0);
            }

            // Triggers: zoom.
            let lt = gamepad.value(gilrs::Axis::LeftZ);
            let rt = gamepad.value(gilrs::Axis::RightZ);
            let zoom_delta = rt - lt;
            if zoom_delta.abs() > DEAD_ZONE {
                camera.zoom(zoom_delta * SCROLL_SENSITIVITY);
            }
        }
    }

    /// Handle touch input (mobile/tablet).
    /// Single-finger drag: pan. Two-finger pinch: zoom. Two-finger rotate: orbit.
    pub fn handle_touch(&mut self, phase: TouchPhase, id: u64, x: f32, y: f32) {
        match phase {
            TouchPhase::Started => {
                self.active_touches.insert(id, [x, y]);
            }
            TouchPhase::Moved => {
                if let Some(prev) = self.active_touches.get_mut(&id) {
                    let dx = x - prev[0];
                    let dy = y - prev[1];
                    *prev = [x, y];

                    if self.active_touches.len() == 1 {
                        // Single finger: pan.
                        self.fabric.cursor_moved(dx, dy);
                    }
                }
            }
            TouchPhase::Ended | TouchPhase::Cancelled => {
                self.active_touches.remove(&id);
            }
        }
    }

    /// End-of-frame cleanup.
    pub fn end_frame(&mut self) {
        self.fabric.end_frame();
        self.scroll_raw = 0.0;
        self.orbit_dx = 0.0;
        self.orbit_dy = 0.0;
        self.scroll_delta = 0.0;
    }
}

/// Touch event phase (matches winit::event::TouchPhase semantics).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TouchPhase {
    Started,
    Moved,
    Ended,
    Cancelled,
}

impl Default for InputState {
    fn default() -> Self {
        Self::new()
    }
}

/// Translate winit KeyCode to engine VirtualKey.
fn winit_to_virtual_key(code: KeyCode) -> Option<VirtualKey> {
    Some(match code {
        KeyCode::KeyA => VirtualKey::A,
        KeyCode::KeyB => VirtualKey::B,
        KeyCode::KeyC => VirtualKey::C,
        KeyCode::KeyD => VirtualKey::D,
        KeyCode::KeyE => VirtualKey::E,
        KeyCode::KeyF => VirtualKey::F,
        KeyCode::KeyG => VirtualKey::G,
        KeyCode::KeyH => VirtualKey::H,
        KeyCode::KeyI => VirtualKey::I,
        KeyCode::KeyJ => VirtualKey::J,
        KeyCode::KeyK => VirtualKey::K,
        KeyCode::KeyL => VirtualKey::L,
        KeyCode::KeyM => VirtualKey::M,
        KeyCode::KeyN => VirtualKey::N,
        KeyCode::KeyO => VirtualKey::O,
        KeyCode::KeyP => VirtualKey::P,
        KeyCode::KeyQ => VirtualKey::Q,
        KeyCode::KeyR => VirtualKey::R,
        KeyCode::KeyS => VirtualKey::S,
        KeyCode::KeyT => VirtualKey::T,
        KeyCode::KeyU => VirtualKey::U,
        KeyCode::KeyV => VirtualKey::V,
        KeyCode::KeyW => VirtualKey::W,
        KeyCode::KeyX => VirtualKey::X,
        KeyCode::KeyY => VirtualKey::Y,
        KeyCode::KeyZ => VirtualKey::Z,
        KeyCode::Digit0 => VirtualKey::Num0,
        KeyCode::Digit1 => VirtualKey::Num1,
        KeyCode::Digit2 => VirtualKey::Num2,
        KeyCode::Digit3 => VirtualKey::Num3,
        KeyCode::Digit4 => VirtualKey::Num4,
        KeyCode::Digit5 => VirtualKey::Num5,
        KeyCode::Digit6 => VirtualKey::Num6,
        KeyCode::Digit7 => VirtualKey::Num7,
        KeyCode::Digit8 => VirtualKey::Num8,
        KeyCode::Digit9 => VirtualKey::Num9,
        KeyCode::ArrowUp => VirtualKey::ArrowUp,
        KeyCode::ArrowDown => VirtualKey::ArrowDown,
        KeyCode::ArrowLeft => VirtualKey::ArrowLeft,
        KeyCode::ArrowRight => VirtualKey::ArrowRight,
        KeyCode::ShiftLeft => VirtualKey::ShiftLeft,
        KeyCode::ShiftRight => VirtualKey::ShiftRight,
        KeyCode::ControlLeft => VirtualKey::CtrlLeft,
        KeyCode::ControlRight => VirtualKey::CtrlRight,
        KeyCode::AltLeft => VirtualKey::AltLeft,
        KeyCode::AltRight => VirtualKey::AltRight,
        KeyCode::Space => VirtualKey::Space,
        KeyCode::Enter => VirtualKey::Enter,
        KeyCode::Escape => VirtualKey::Escape,
        KeyCode::Tab => VirtualKey::Tab,
        KeyCode::Backspace => VirtualKey::Backspace,
        KeyCode::Delete => VirtualKey::Delete,
        KeyCode::Home => VirtualKey::Home,
        KeyCode::End => VirtualKey::End,
        KeyCode::PageUp => VirtualKey::PageUp,
        KeyCode::PageDown => VirtualKey::PageDown,
        KeyCode::F1 => VirtualKey::F1,
        KeyCode::F2 => VirtualKey::F2,
        KeyCode::F3 => VirtualKey::F3,
        KeyCode::F4 => VirtualKey::F4,
        KeyCode::F5 => VirtualKey::F5,
        KeyCode::F6 => VirtualKey::F6,
        KeyCode::F7 => VirtualKey::F7,
        KeyCode::F8 => VirtualKey::F8,
        KeyCode::F9 => VirtualKey::F9,
        KeyCode::F10 => VirtualKey::F10,
        KeyCode::F11 => VirtualKey::F11,
        KeyCode::F12 => VirtualKey::F12,
        _ => return None,
    })
}