1use winit::application::ApplicationHandler;
4use winit::event::WindowEvent;
5use winit::event_loop::ActiveEventLoop;
6use winit::window::WindowId;
7
8use dreamwell_engine::game_object::PrimitiveKind;
9use dreamwell_engine::TopologyLayer;
10use dreamwell_fabric::packets::SyncPacket;
11
12use crate::authority::{AuthorityClient, AuthorityEvent, LocalAuthority};
13use crate::game_state::GameState;
14use crate::input::InputState;
15use crate::play::SimulationService;
16use crate::renderer::SceneRenderer;
17use crate::scene::Scene;
18use crate::sync::stage::StagingBuffer;
19use crate::time::FrameTimer;
20use crate::window::WindowState;
21use crate::{AuthorityMode, RuntimeConfig};
22use dreamwell_fabric::decoder::{CausalEngineDecoder, DecoderInput};
23
24const MIN_PITCH: f32 = -0.3;
34const MAX_PITCH: f32 = 1.40;
36const MIN_CAMERA_DIST: f32 = 0.5;
38const MAX_CAMERA_DIST: f32 = 200.0;
40const DEFAULT_CAMERA_DIST: f32 = 3.0;
42const CAMERA_POSITION_LERP_RATE: f32 = 16.0;
44const CAMERA_LOOKAT_LERP_RATE: f32 = 20.0;
46const ZOOM_LERP_RATE: f32 = 14.0;
48const SHOULDER_OFFSET: f32 = 0.3;
50const LOOKAT_HEIGHT: f32 = 0.9;
52const SPRINT_PULL_BACK: f32 = 1.0;
54
55pub struct CameraProfile {
58 pub boom_length: f32,
59 pub yaw_lag: f32,
60 pub fov: f32,
61 pub shoulder_offset: f32,
62 pub lookat_height: f32,
63}
64
65impl CameraProfile {
66 pub fn cohere() -> Self {
68 Self {
69 boom_length: 5.0,
70 yaw_lag: 0.15,
71 fov: 60.0,
72 shoulder_offset: 0.4,
73 lookat_height: 1.2,
74 }
75 }
76
77 pub fn wave() -> Self {
79 Self {
80 boom_length: 7.0,
81 yaw_lag: 0.25,
82 fov: 65.0,
83 shoulder_offset: 0.6,
84 lookat_height: 1.0,
85 }
86 }
87
88 pub fn lerp(a: &Self, b: &Self, t: f32) -> Self {
90 let t = t.clamp(0.0, 1.0);
91 Self {
92 boom_length: a.boom_length + (b.boom_length - a.boom_length) * t,
93 yaw_lag: a.yaw_lag + (b.yaw_lag - a.yaw_lag) * t,
94 fov: a.fov + (b.fov - a.fov) * t,
95 shoulder_offset: a.shoulder_offset + (b.shoulder_offset - a.shoulder_offset) * t,
96 lookat_height: a.lookat_height + (b.lookat_height - a.lookat_height) * t,
97 }
98 }
99}
100
101#[derive(Debug)]
103pub enum RuntimeError {
104 Load(String),
106 Init(String),
108}
109
110impl std::fmt::Display for RuntimeError {
111 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
112 match self {
113 Self::Load(msg) => write!(f, "load error: {msg}"),
114 Self::Init(msg) => write!(f, "init error: {msg}"),
115 }
116 }
117}
118
119impl std::error::Error for RuntimeError {}
120
121pub struct RuntimeApp {
123 config: RuntimeConfig,
124 window_state: Option<WindowState>,
125 renderer: Option<SceneRenderer>,
126 scene: Scene,
127 game_state: GameState,
128 input: InputState,
129 timer: FrameTimer,
130 authority: Box<dyn AuthorityClient>,
132 staging: StagingBuffer,
134 sync_packet: SyncPacket,
136 mounted_pack: Option<String>,
138 authority_events: Vec<AuthorityEvent>,
141 simulation: Option<SimulationService>,
144 decoder: Option<CausalEngineDecoder>,
146 camera_orbit_yaw: f32,
148 camera_orbit_pitch: f32,
150 camera_distance: f32,
152 camera_target_distance: f32,
154 #[cfg(feature = "gamepad")]
156 gilrs: gilrs::Gilrs,
157 #[cfg(feature = "audio")]
161 audio: dreamwell_audio::AudioSystem<dreamwell_audio::OddioBackend>,
162}
163
164fn create_authority(mode: AuthorityMode) -> Box<dyn AuthorityClient> {
165 match mode {
166 AuthorityMode::Local => Box::new(LocalAuthority::new()),
167 #[cfg(feature = "multiplayer")]
168 AuthorityMode::Remote => Box::new(crate::authority::RemoteAuthority::new()),
169 #[cfg(not(feature = "multiplayer"))]
170 AuthorityMode::Remote => {
171 log::warn!("Remote authority requested but multiplayer feature not enabled, falling back to local");
172 Box::new(LocalAuthority::new())
173 }
174 }
175}
176
177impl RuntimeApp {
178 pub fn new(config: RuntimeConfig) -> Result<Self, RuntimeError> {
182 let authority = create_authority(config.authority_mode);
183 Ok(Self {
184 config,
185 window_state: None,
186 renderer: None,
187 scene: Scene::default(),
188 game_state: GameState::default(),
189 input: InputState::default(),
190 timer: FrameTimer::new(),
191 authority,
192 staging: StagingBuffer::new(),
193 sync_packet: SyncPacket::new(),
194 mounted_pack: None,
195 authority_events: Vec::with_capacity(64),
198 simulation: None,
199 decoder: None,
200 camera_orbit_yaw: 0.0,
201 camera_orbit_pitch: 0.35, camera_distance: 6.0,
203 camera_target_distance: 6.0,
204 #[cfg(feature = "gamepad")]
205 gilrs: gilrs::Gilrs::new().unwrap_or_else(|e| {
206 log::warn!("gamepad_init_failed:{e} — gamepad input disabled");
207 e.into()
210 }),
211 #[cfg(feature = "audio")]
212 audio: {
213 match dreamwell_audio::OddioBackend::new() {
214 Ok(backend) => dreamwell_audio::AudioSystem::new(backend),
215 Err(e) => {
216 log::warn!("audio_init_failed:{e} — falling back to headless");
217 dreamwell_audio::AudioSystem::new(dreamwell_audio::OddioBackend::headless())
218 }
219 }
220 },
221 })
222 }
223
224 pub fn mount_pack(&mut self, pack_json: &str) -> Result<(), String> {
230 if pack_json.is_empty() {
232 return Err("mount_pack:empty pack JSON".into());
233 }
234 self.mounted_pack = Some(pack_json.to_string());
236 log::info!("Pack mounted ({} bytes)", pack_json.len());
237 Ok(())
238 }
239
240 pub fn load_scene(&mut self, scene_name: &str) -> Result<(), String> {
247 if scene_name.is_empty() {
248 return Err("load_scene:empty scene name".into());
249 }
250 self.scene = Scene::default();
252 self.game_state = GameState::default();
253
254 if let Some(ref pack_json) = self.mounted_pack {
255 match dreamwell_engine::waymark::loader::PackLoader::load_pack_config(pack_json) {
256 Ok(pack) => {
257 let loaded = dreamwell_engine::waymark::loader::load_pack_to_scene(&pack);
258 self.scene.game_objects = loaded.objects;
259 log::info!(
260 "Loaded scene '{}' from pack: {} objects",
261 scene_name,
262 self.scene.game_objects.len()
263 );
264 return Ok(());
265 }
266 Err(e) => {
267 log::warn!("Pack parse failed, falling back to demo: {}", e);
268 }
269 }
270 }
271
272 self.seed_demo_scene();
273 log::info!("Scene '{}' loaded (demo fallback)", scene_name);
274 Ok(())
275 }
276
277 pub fn set_authority_mode(&mut self, mode: AuthorityMode) {
284 if mode == self.config.authority_mode {
285 return;
286 }
287 log::info!(
288 "Switching authority mode: {:?} -> {:?}",
289 self.config.authority_mode,
290 mode
291 );
292 self.authority = create_authority(mode);
293 self.staging = StagingBuffer::new();
294 self.config.authority_mode = mode;
295 }
296
297 fn seed_demo_scene(&mut self) {
301 if let Some(ref lock_path) = self.config.lock_file {
303 if let Ok(json) = std::fs::read_to_string(lock_path) {
304 if let Ok(lock) = dreamwell_sdk::tapestry::TapestryLock::from_json(&json) {
305 log::info!("Loading scene from tapestry.lock: {} objects", lock.object_count);
306 self.load_scene_from_lock(&lock);
307 return;
308 }
309 }
310 log::warn!("Failed to load lock file '{}', falling back to demo", lock_path);
311 }
312
313 let tmpl = dreamwell_sdk::templates::default_3d_scene();
316
317 self.game_state.active_layer = TopologyLayer::Point;
320
321 let scene = &mut self.scene.game_objects;
322 for obj in &tmpl.objects {
323 let kind = match obj.name.as_str() {
324 "Ground" => Some(PrimitiveKind::Plane),
325 _ => None,
326 };
327 let id = if let Some(k) = kind {
328 scene.spawn_primitive(obj.name.clone(), k)
329 } else {
330 scene.spawn(obj.name.clone())
331 };
332 if let Ok(id) = id {
333 if let Some(go) = scene.find_mut(id) {
334 go.transform.position = obj.position;
335 go.transform.scale = obj.scale;
336 go.property_tags = obj.tags.clone();
337 if let Some(ref mat) = obj.material {
339 go.property_tags.push(format!("material={mat}"));
340 }
341 }
342 }
343 }
344
345 log::info!(
350 "Demo scene seeded from template '{}': {} objects, {} lights",
351 tmpl.name,
352 scene.len(),
353 tmpl.lights.len(),
354 );
355
356 let spawn_pos = tmpl
358 .objects
359 .iter()
360 .find(|o| o.tags.iter().any(|t| t == "isInputReceiver"))
361 .map(|o| o.position)
362 .unwrap_or([0.0, 0.5, 0.0]);
363
364 let mut go_scene = dreamwell_engine::game_object::GameObjectScene::new(tmpl.name.clone());
365 for obj in &tmpl.objects {
366 let kind = match obj.name.as_str() {
367 "Ground" => Some(PrimitiveKind::Plane),
368 _ => None,
369 };
370 let id = if let Some(k) = kind {
371 go_scene.spawn_primitive(obj.name.clone(), k)
372 } else {
373 go_scene.spawn(obj.name.clone())
374 };
375 if let Ok(id) = id {
376 if let Some(go) = go_scene.find_mut(id) {
377 go.transform.position = obj.position;
378 go.transform.scale = obj.scale;
379 go.property_tags = obj.tags.clone();
380 }
381 }
382 }
383
384 let particle_count = dreamwell_metaphors::DEFAULT_PARTICLE_COUNT;
385 let mut sim = SimulationService::new(&tmpl.name, crate::play::SimulationMode::Published);
386 if let Err(e) = sim.initialize(go_scene, spawn_pos, particle_count) {
387 log::error!("Simulation init failed: {e}");
388 } else {
390 self.simulation = Some(sim);
391 self.decoder = Some(dreamwell_fabric::decoder::CausalEngineDecoder::new());
392 }
393 self.game_state.player_position = glam::Vec3::from_array(spawn_pos);
394
395 self.camera_orbit_pitch = 0.30;
398 self.camera_distance = DEFAULT_CAMERA_DIST;
399 self.camera_target_distance = DEFAULT_CAMERA_DIST;
400 let pitch = self.camera_orbit_pitch;
401 let yaw = self.camera_orbit_yaw;
402 let horiz = pitch.cos() * self.camera_distance;
403 let height = pitch.sin() * self.camera_distance;
404 let cam_offset = glam::Vec3::new(
405 -yaw.cos() * horiz + yaw.sin() * SHOULDER_OFFSET,
406 height,
407 -yaw.sin() * horiz - yaw.cos() * SHOULDER_OFFSET,
408 );
409 let player = glam::Vec3::from_array(spawn_pos);
410 self.scene.camera.position = player + cam_offset;
411 self.scene.camera.center = glam::Vec3::new(spawn_pos[0], spawn_pos[1] + LOOKAT_HEIGHT, spawn_pos[2]);
412 self.scene.camera.update_view_matrix();
413 }
414
415 fn load_scene_from_lock(&mut self, lock: &dreamwell_sdk::tapestry::TapestryLock) {
419 use dreamwell_engine::loom;
420
421 let mut scene = dreamwell_engine::game_object::GameObjectScene::new(lock.scene_name.clone());
423 for obj in &lock.objects {
424 let primitive = Self::infer_primitive_kind(&obj.tags, &obj.name);
426 let id = if let Some(kind) = primitive {
427 scene.spawn_primitive(obj.name.clone(), kind)
428 } else {
429 scene.spawn(obj.name.clone())
430 };
431 if let Ok(id) = id {
432 if let Some(go) = scene.find_mut(id) {
433 go.transform.position = obj.position;
434 go.transform.rotation = obj.rotation;
435 go.transform.scale = obj.scale;
436 go.visible = obj.visible;
437 go.parent_id = obj.parent_id;
438 go.property_tags = obj.tags.clone();
439 for tag in &obj.tags {
441 if let Some(rest) = tag.strip_prefix("isTopologyLayer") {
442 if let Ok(layer) = rest.parse::<u8>() {
443 go.topology_layer = layer.min(9);
444 }
445 }
446 }
447 }
448 }
449 }
450
451 Self::hydrate_tags_from_template(&mut scene);
453
454 let woven = match loom::weave(scene, loom::RenderConfig::default()) {
456 Ok(w) => w,
457 Err(errs) => {
458 log::error!("Loom validation failed: {:?}", errs);
459 return;
460 }
461 };
462
463 let spawn_pos = woven
465 .scene
466 .objects
467 .iter()
468 .find(|o| o.property_tags.iter().any(|t| t == "isInputReceiver"))
469 .map(|o| o.transform.position)
470 .unwrap_or([0.0, 0.5, 0.0]);
471
472 let mut sim = SimulationService::new(&woven.scene.name, crate::play::SimulationMode::Published);
474
475 sim.decoherence_fields = Self::collect_decoherence_fields(&woven);
477
478 self.game_state.active_layer = TopologyLayer::Point;
480
481 let particle_count = dreamwell_metaphors::DEFAULT_PARTICLE_COUNT;
484 if let Err(e) = sim.initialize(woven.scene, spawn_pos, particle_count) {
485 log::error!("Simulation init failed: {e}");
486 return;
487 }
488 self.game_state.player_position = glam::Vec3::from_array(spawn_pos);
489
490 self.scene.game_objects = sim.scene.clone();
492
493 let init_yaw = sim
495 .encoder
496 .as_ref()
497 .map(|e| e.kernel().facing_yaw)
498 .or_else(|| sim.kernel.as_ref().map(|k| k.facing_yaw));
499 if let Some(yaw) = init_yaw {
500 self.camera_orbit_yaw = yaw;
501 }
502 self.camera_orbit_pitch = 0.35; self.camera_distance = DEFAULT_CAMERA_DIST;
504 self.camera_target_distance = DEFAULT_CAMERA_DIST;
505
506 let yaw = self.camera_orbit_yaw;
508 let pitch = self.camera_orbit_pitch;
509 let horiz = pitch.cos() * self.camera_distance;
510 let height = pitch.sin() * self.camera_distance;
511 let cam_offset = glam::Vec3::new(
512 -yaw.cos() * horiz + yaw.sin() * SHOULDER_OFFSET,
513 height,
514 -yaw.sin() * horiz - yaw.cos() * SHOULDER_OFFSET,
515 );
516 self.scene.camera.position = glam::Vec3::from_array(spawn_pos) + cam_offset;
517 self.scene.camera.center = glam::Vec3::new(spawn_pos[0], spawn_pos[1] + LOOKAT_HEIGHT, spawn_pos[2]);
518 self.scene.camera.update_view_matrix();
519
520 let mesh_count = self
522 .scene
523 .game_objects
524 .objects
525 .iter()
526 .filter(|o| matches!(o.mesh, dreamwell_engine::game_object::MeshBinding::Primitive { .. }))
527 .count();
528 let visible_mesh_count = self
529 .scene
530 .game_objects
531 .objects
532 .iter()
533 .filter(|o| matches!(o.mesh, dreamwell_engine::game_object::MeshBinding::Primitive { .. }) && o.visible)
534 .count();
535 for obj in &self.scene.game_objects.objects {
536 log::debug!(
537 " obj '{}': mesh={:?}, visible={}, pos=[{:.1},{:.1},{:.1}]",
538 obj.name,
539 obj.mesh,
540 obj.visible,
541 obj.transform.position[0],
542 obj.transform.position[1],
543 obj.transform.position[2],
544 );
545 }
546 log::info!(
547 "Tapestry scene loaded: {} objects ({} meshes, {} visible), spawn at [{:.1}, {:.1}, {:.1}], layer 9 (Point)",
548 self.scene.game_objects.len(),
549 mesh_count, visible_mesh_count,
550 spawn_pos[0], spawn_pos[1], spawn_pos[2],
551 );
552
553 self.simulation = Some(sim);
554 self.decoder = Some(CausalEngineDecoder::new());
555 }
556
557 fn convert_interference_kernels(
560 kernels: &dreamwell_quantum::InterferenceKernels,
561 particle_count: u32,
562 ) -> Vec<dreamwell_gpu::dreamlet_catalog::GpuParticleKernel> {
563 let n = (particle_count as usize).min(kernels.kernels.len());
564 let mut gpu_kernels = Vec::with_capacity(n);
565 for p in 0..n {
566 let mut flat = [0.0f32; 60];
567 for pair in 0..10 {
568 for axis in 0..3 {
569 let c = kernels.kernels[p][pair][axis];
570 flat[pair * 6 + axis * 2] = c.re;
571 flat[pair * 6 + axis * 2 + 1] = c.im;
572 }
573 }
574 gpu_kernels.push(dreamwell_gpu::dreamlet_catalog::GpuParticleKernel {
575 kernels: flat,
576 _pad: [0.0; 4],
577 });
578 }
579 gpu_kernels
580 }
581
582 fn collect_decoherence_fields(
584 woven: &dreamwell_engine::loom::WovenScene,
585 ) -> Vec<dreamwell_quantum::DecoherenceField> {
586 use dreamwell_engine::input::parse_decoherence_tag;
587 use dreamwell_engine::input::wave::{DecoherenceTagValue, WaveForm};
588
589 fn to_quantum_form(f: WaveForm) -> dreamwell_quantum::WaveForm {
590 match f {
591 WaveForm::Particle => dreamwell_quantum::WaveForm::Particle,
592 WaveForm::Humanoid => dreamwell_quantum::WaveForm::Humanoid,
593 WaveForm::Vehicle => dreamwell_quantum::WaveForm::Vehicle,
594 WaveForm::Fluid => dreamwell_quantum::WaveForm::Fluid,
595 }
596 }
597
598 let mut fields = Vec::new();
599 for obj in &woven.scene.objects {
600 let is_zone = obj.property_tags.iter().any(|t| t == "isDecoherenceZone");
601 if !is_zone {
602 continue;
603 }
604
605 let mut target_form = WaveForm::Humanoid;
606 let mut gamma = 2.0f32;
607 let mut clip = String::new();
608 for tag in &obj.property_tags {
609 if let Some(val) = parse_decoherence_tag(tag) {
610 match val {
611 DecoherenceTagValue::Form(form, animation) => {
612 target_form = form;
613 clip = animation;
614 }
615 DecoherenceTagValue::Gamma(rate) => {
616 gamma = rate;
617 }
618 }
619 }
620 }
621
622 let radius = obj
623 .property_tags
624 .iter()
625 .find_map(|t| t.strip_prefix("decohere.radius=").and_then(|v| v.parse::<f32>().ok()))
626 .unwrap_or(10.0);
627
628 fields.push(dreamwell_quantum::DecoherenceField {
629 position: obj.transform.position,
630 radius,
631 target_form: to_quantum_form(target_form),
632 gamma,
633 animation_clip: clip,
634 source_tag: "isDecoherenceZone".into(),
635 });
636 }
637 fields
638 }
639
640 #[cfg(feature = "bundled-skybox")]
641 fn default_skybox_bytes() -> &'static [u8] {
642 include_bytes!("../assets/skyboxes/textures/basic_skybox.jpeg")
643 }
644
645 #[cfg(not(feature = "bundled-skybox"))]
646 fn default_skybox_bytes() -> &'static [u8] {
647 &[]
648 }
649
650 fn infer_primitive_kind(tags: &[String], name: &str) -> Option<PrimitiveKind> {
653 let has_tag = |t: &str| tags.iter().any(|s| s == t);
654 if has_tag("isSkybox") || has_tag("isInputReceiver") || has_tag("isPlayer") {
656 return None;
657 }
658 if name.starts_with("Zone ") {
660 return None;
661 }
662 if has_tag("isGround") {
664 return Some(PrimitiveKind::Cube);
665 }
666 if has_tag("isWall") || has_tag("isCollider") {
668 return Some(PrimitiveKind::Cube);
669 }
670 if has_tag("poi") || has_tag("isInteractable") || has_tag("isFractal") {
672 return Some(PrimitiveKind::Sphere);
673 }
674 if !tags.is_empty() {
676 return Some(PrimitiveKind::Cube);
677 }
678 None
679 }
680
681 fn hydrate_tags_from_template(scene: &mut dreamwell_engine::game_object::GameObjectScene) {
685 if scene.objects.iter().any(|o| !o.property_tags.is_empty()) {
686 for obj in &mut scene.objects {
688 if matches!(obj.mesh, dreamwell_engine::game_object::MeshBinding::None) {
689 if let Some(kind) = Self::infer_primitive_kind(&obj.property_tags, &obj.name) {
690 obj.mesh = dreamwell_engine::game_object::MeshBinding::Primitive {
691 kind,
692 color: [0.7, 0.7, 0.7, 1.0],
693 };
694 }
695 }
696 }
697 return;
698 }
699 let tmpl = dreamwell_sdk::templates::shapes_and_dimensions();
700 for obj in &mut scene.objects {
701 if let Some(tmpl_obj) = tmpl.objects.iter().find(|t| t.name == obj.name) {
702 obj.property_tags = tmpl_obj.tags.clone();
703 for tag in &obj.property_tags {
704 if let Some(rest) = tag.strip_prefix("isTopologyLayer") {
705 if let Ok(layer) = rest.parse::<u8>() {
706 obj.topology_layer = layer.min(9);
707 }
708 }
709 }
710 if matches!(obj.mesh, dreamwell_engine::game_object::MeshBinding::None) {
712 if let Some(kind) = Self::infer_primitive_kind(&obj.property_tags, &obj.name) {
713 obj.mesh = dreamwell_engine::game_object::MeshBinding::Primitive {
714 kind,
715 color: [0.7, 0.7, 0.7, 1.0],
716 };
717 }
718 }
719 }
720 }
721 log::info!("Tag hydration: applied template tags to stale lock");
722 }
723}
724
725impl ApplicationHandler for RuntimeApp {
726 fn resumed(&mut self, event_loop: &ActiveEventLoop) {
727 if self.window_state.is_some() {
728 return;
729 }
730
731 let window_state = WindowState::new(event_loop, &self.config.title, self.config.width, self.config.height);
732 let renderer = SceneRenderer::with_msaa(&window_state, self.config.msaa_samples);
733 self.window_state = Some(window_state);
734 self.renderer = Some(renderer);
735
736 let naga_report = dreamwell_naga::validate_startup();
738 if naga_report.is_ok() {
739 log::info!(
740 "Naga: {}/{} shaders OK",
741 naga_report.passed,
742 naga_report.shader_results.len()
743 );
744 } else {
745 log::warn!("Naga validation warnings:\n{}", naga_report.summary());
746 }
747
748 self.seed_demo_scene();
750 if let (Some(ws), Some(r)) = (&self.window_state, &mut self.renderer) {
751 let aspect = ws.surface_config.width as f32 / ws.surface_config.height.max(1) as f32;
753 self.scene.camera.update_projection(aspect, 60.0);
754
755 r.fabric.upload_scene(&ws.device, &self.scene.game_objects);
756
757 {
760 let sun_dir = glam::Vec3::new(0.3, -1.0, 0.4).normalize();
761 r.fabric.gpu_scene_mut().scene_lights.add_directional(
762 dreamwell_engine::lighting::DirectionalLightDesc {
763 direction: sun_dir.to_array(),
764 color: [1.0, 0.98, 0.92],
765 intensity_lux: 2.0,
766 },
767 );
768 }
769
770 let skybox_bytes = Self::default_skybox_bytes();
772 if !skybox_bytes.is_empty() {
773 if r.fabric.load_skybox_image(&ws.device, &ws.queue, skybox_bytes) {
774 log::info!("Skybox loaded ({} bytes)", skybox_bytes.len());
775 } else {
776 log::warn!("Skybox decode failed");
777 }
778 }
779
780 if let Some(ref sim) = self.simulation {
784 if !sim.particle_offsets.is_empty() {
785 let spawn_pos = sim
786 .encoder
787 .as_ref()
788 .map(|e| e.kernel().position)
789 .or_else(|| sim.kernel.as_ref().map(|k| k.position))
790 .unwrap_or([0.0, 0.5, 0.0]);
791 let dreamlets = dreamwell_gpu::dreamlet_catalog::generate_particle(
792 spawn_pos,
793 sim.particle_offsets.len() as u32,
794 0.8,
795 [0.4, 0.6, 1.0, 0.85],
796 &sim.particle_offsets,
797 );
798 r.fabric.ensure_dreamlet_catalog_ready(&ws.device, &ws.queue);
799 r.fabric.init_particle_physics(&ws.device);
800 r.fabric.upload_dreamlets(&ws.queue, &dreamlets);
801 r.fabric.mark_particle_uploaded();
802
803 let particle_count = dreamlets.len() as u32;
805 r.fabric.init_quantum_bridge(&ws.device, particle_count);
806
807 let quantum_kernels = sim
809 .encoder
810 .as_ref()
811 .map(|e| &e.kernel().quantum.kernels)
812 .or_else(|| sim.kernel.as_ref().map(|k| &k.quantum.kernels));
813 if let Some(kernels) = quantum_kernels {
814 let gpu_kernels = Self::convert_interference_kernels(kernels, particle_count);
815 r.fabric.upload_interference_kernels(&ws.queue, &gpu_kernels);
816 }
817
818 log::info!(
819 "Particle dreamlets uploaded: {} particles, quantum bridge active",
820 dreamlets.len()
821 );
822 }
823 }
824
825 let mut warmup = dreamwell_fabric::WarmupPhase::standard_entries();
827 warmup.complete_all(&ws.device, r.fabric.pipeline_cache_mut());
828 r.fabric.init_post_process_pipelines(&ws.device);
829 log::info!("Tapestry warmed: {} pipelines compiled", warmup.total());
830 }
831
832 if let (Some(_ws), Some(r)) = (&self.window_state, &mut self.renderer) {
836 r.set_scene_dream_mode(dreamwell_engine::material::SceneDreamMode::PbrDefault);
837 r.set_post_process_config(dreamwell_gpu::post::PostProcessConfig::for_pbr());
839 log::info!("Render mode: PbrDefault, post-processing: bloom + ACES tonemap");
840 }
841
842 log::info!("Runtime initialized");
843 }
844
845 fn window_event(&mut self, event_loop: &ActiveEventLoop, _window_id: WindowId, event: WindowEvent) {
846 match event {
847 WindowEvent::CloseRequested => {
848 event_loop.exit();
849 }
850 WindowEvent::Resized(size) => {
851 if let Some(ws) = &mut self.window_state {
852 ws.resize(size.width, size.height);
853 if let Some(r) = &mut self.renderer {
854 r.resize(size.width, size.height, &ws.device);
855 }
856 }
857 }
858 WindowEvent::KeyboardInput { event, .. } => {
859 self.input.handle_keyboard(&event);
860 if let winit::keyboard::PhysicalKey::Code(code) = event.physical_key {
861 use winit::event::ElementState;
862 use winit::keyboard::KeyCode;
863 match (code, event.state) {
864 (KeyCode::Tab, ElementState::Pressed) => {
866 if let Some(ref mut sim) = self.simulation {
867 let name = sim.cycle_metaphor();
868 log::info!("Metaphor: {name}");
869 }
870 }
871 (KeyCode::KeyQ, ElementState::Pressed) => {
873 if let Some(ref mut sim) = self.simulation {
874 sim.set_coherence_target(1.0);
875 }
876 }
877 (KeyCode::KeyE, ElementState::Pressed) => {
879 if let Some(ref mut sim) = self.simulation {
880 sim.set_coherence_target(0.0);
881 }
882 }
883 (KeyCode::ControlLeft | KeyCode::ControlRight, ElementState::Pressed) => {
885 if let Some(ref mut sim) = self.simulation {
886 sim.set_gather(true);
887 }
888 }
889 (KeyCode::ControlLeft | KeyCode::ControlRight, ElementState::Released) => {
890 if let Some(ref mut sim) = self.simulation {
891 sim.set_gather(false);
892 }
893 }
894 _ => {}
895 }
896 }
897 }
898 WindowEvent::MouseInput { state, button, .. } => {
899 self.input.handle_mouse_button(button, state);
900 if button == winit::event::MouseButton::Left {
902 if let Some(ref mut sim) = self.simulation {
903 match state {
904 winit::event::ElementState::Pressed => sim.set_emit_strength(1.0),
905 winit::event::ElementState::Released => sim.set_emit_strength(0.0),
906 }
907 }
908 }
909 }
910 WindowEvent::CursorMoved { position, .. } => {
911 self.input.handle_cursor_move(position.x as f32, position.y as f32);
912 }
913 WindowEvent::MouseWheel { delta, .. } => {
914 self.input.handle_scroll(&delta);
915 }
916 WindowEvent::RedrawRequested => {
917 self.timer.tick();
918
919 if self.simulation.is_none() {
923 self.input
924 .apply_to_camera(&mut self.scene.camera, self.timer.delta_time());
925 } else {
926 self.input
928 .apply_mouse_to_camera(&mut self.scene.camera, self.timer.delta_time());
929 }
930
931 #[cfg(feature = "gamepad")]
933 self.input.handle_gamepad(&mut self.gilrs, &mut self.scene.camera);
934
935 self.authority_events.clear();
937 self.authority.poll_events(&mut self.authority_events);
938
939 {
943 let staging = &mut self.staging;
944 let events = &mut self.authority_events;
945 staging.extend(events.drain(..));
946 }
947
948 for event in self.staging.drain() {
950 match event {
951 AuthorityEvent::Ack { .. } => { }
952 AuthorityEvent::Reject { seq, reason } => {
953 log::debug!("Authority rejected intent {seq}: {reason}");
954 }
955 AuthorityEvent::CanonEvent { tick, event_type, .. } => {
956 log::trace!("Canon event at tick {tick}: {event_type}");
957 }
958 AuthorityEvent::SnapshotChunk { .. } => { }
959 }
960 }
961
962 self.game_state.update(self.timer.delta_time());
964
965 if let Some(ref mut sim) = self.simulation {
969 let dt_secs = self.timer.delta_time();
970
971 #[cfg(feature = "gamepad")]
973 {
974 use dreamwell_engine::input::VirtualKey;
975 const DEADZONE: f32 = 0.2;
976 use gilrs::Axis;
977 if let Some((_id, gamepad)) = self.gilrs.gamepads().next() {
978 let lx = gamepad.value(Axis::LeftStickX);
979 let ly = gamepad.value(Axis::LeftStickY);
980 if ly > DEADZONE {
981 self.input.fabric.key_down(VirtualKey::W);
982 }
983 if ly < -DEADZONE {
984 self.input.fabric.key_down(VirtualKey::S);
985 }
986 if lx < -DEADZONE {
987 self.input.fabric.key_down(VirtualKey::A);
988 }
989 if lx > DEADZONE {
990 self.input.fabric.key_down(VirtualKey::D);
991 }
992 if gamepad.is_pressed(gilrs::Button::South) {
993 self.input.fabric.key_down(VirtualKey::Space);
994 }
995 if gamepad.is_pressed(gilrs::Button::LeftTrigger2) {
996 self.input.fabric.key_down(VirtualKey::ShiftLeft);
997 }
998 }
999 }
1000
1001 self.camera_orbit_yaw += self.input.orbit_dx;
1003 self.camera_orbit_pitch =
1004 (self.camera_orbit_pitch + self.input.orbit_dy).clamp(MIN_PITCH, MAX_PITCH);
1005 self.camera_target_distance =
1006 (self.camera_target_distance - self.input.scroll_delta).clamp(MIN_CAMERA_DIST, MAX_CAMERA_DIST);
1007
1008 let frame = self.input.fabric.build_frame();
1012 let packet = dreamwell_engine::input::InputPacket::from_frame(
1013 &frame,
1014 self.camera_orbit_yaw,
1015 dt_secs,
1016 sim.elapsed_time as f64,
1017 sim.tick + 1,
1018 sim.encoder.as_ref().map(|e| e.kernel().grounded).unwrap_or(true),
1019 sim.coherence_current,
1020 sim.gather_active,
1021 sim.emit_strength,
1022 );
1023
1024 let zoom_t = 1.0 - (-ZOOM_LERP_RATE * dt_secs).exp();
1026 self.camera_distance += (self.camera_target_distance - self.camera_distance) * zoom_t;
1027
1028 if sim.frame == 0 {
1029 log::info!(
1030 "Sim pre-tick: input_enabled={} readiness={:?} encoder={} movement=[{:.2},{:.2}]",
1031 sim.input_enabled(),
1032 sim.readiness,
1033 sim.encoder.is_some(),
1034 packet.movement[0],
1035 packet.movement[1],
1036 );
1037 }
1038 if let Some(result) = sim.tick(&packet) {
1039 if sim.frame <= 3 || sim.frame % 600 == 0 {
1040 log::info!(
1041 "Sim tick #{}: pos=[{:.2},{:.2},{:.2}] mode={} bridge={}",
1042 sim.frame,
1043 result.position[0],
1044 result.position[1],
1045 result.position[2],
1046 result.locomotion_mode,
1047 sim.last_bridge_packet.is_some(),
1048 );
1049 }
1050 self.game_state.player_position = glam::Vec3::from_array(result.position);
1052 if result.layer_changed {
1053 if let Some(layer) = TopologyLayer::from_u8(result.topology_layer) {
1054 self.game_state.active_layer = layer;
1055 }
1056 }
1057 self.scene.game_objects = sim.scene.clone();
1059
1060 sim.tick_collisions();
1062 sim.tick_poi_interactions();
1063
1064 let sprint_extra = if packet.sprint { SPRINT_PULL_BACK } else { 0.0 };
1067 let effective_dist = self.camera_distance + sprint_extra;
1068
1069 let kernel_pos = sim
1071 .encoder
1072 .as_ref()
1073 .map(|e| e.kernel().position)
1074 .or_else(|| sim.kernel.as_ref().map(|k| k.position));
1075 if let Some(kpos) = kernel_pos {
1076 let pos = glam::Vec3::from_array(kpos);
1077 let yaw = self.camera_orbit_yaw;
1078 let pitch = self.camera_orbit_pitch;
1079
1080 let horiz = pitch.cos() * effective_dist;
1083 let height = pitch.sin() * effective_dist;
1084 let offset = glam::Vec3::new(
1085 -yaw.cos() * horiz + yaw.sin() * SHOULDER_OFFSET,
1086 height,
1087 -yaw.sin() * horiz - yaw.cos() * SHOULDER_OFFSET,
1088 );
1089 let target_cam_pos = pos + offset;
1090 let pos_t = 1.0 - (-CAMERA_POSITION_LERP_RATE * dt_secs).exp();
1093 let look_t = 1.0 - (-CAMERA_LOOKAT_LERP_RATE * dt_secs).exp();
1094 self.scene.camera.position = self.scene.camera.position.lerp(target_cam_pos, pos_t);
1095 self.scene.camera.center = self
1096 .scene
1097 .camera
1098 .center
1099 .lerp(pos + glam::Vec3::Y * LOOKAT_HEIGHT, look_t);
1100 self.scene.camera.update_view_matrix();
1103 }
1104
1105 }
1107 }
1108
1109 #[cfg(feature = "audio")]
1111 {
1112 let _ = &self.audio;
1113 }
1114
1115 let mut decoder_handled = false;
1118 if let (Some(ref mut sim), Some(ref mut decoder), Some(r)) =
1119 (&mut self.simulation, &mut self.decoder, &mut self.renderer)
1120 {
1121 if let Some(ref packet) = sim.last_bridge_packet {
1122 let input = DecoderInput::from_bridge_packet(packet);
1123 decoder.submit(&mut r.fabric, &input);
1124 decoder_handled = true;
1125 }
1126 }
1127
1128 let dt = self.timer.delta_time();
1133 let player_pos = self.game_state.player_position();
1134 let active_layer = self.game_state.active_layer();
1135
1136 let _ = decoder_handled;
1140
1141 if let (Some(ws), Some(r)) = (&self.window_state, &mut self.renderer) {
1143 r.render(ws, &self.scene, dt, player_pos, active_layer);
1144 }
1145
1146 if let Some(r) = &mut self.renderer {
1148 self.sync_packet.clear();
1149 r.fabric.drain_sync(&mut self.sync_packet);
1150 if !self.sync_packet.intents.is_empty() {
1151 self.authority.submit_intents(&self.sync_packet.intents);
1152 }
1153 }
1154
1155 self.input.end_frame();
1157
1158 if let Some(ws) = &self.window_state {
1160 ws.window.request_redraw();
1161 }
1162 }
1163 _ => {}
1164 }
1165 }
1166}