1use dreamwell_attention::encoder::CausalEngineEncoder;
14use dreamwell_attention::{CausalComputeKernel, InputPacket, KernelResult};
15use dreamwell_engine::game_object::GameObjectScene;
16use dreamwell_engine::input::particle::particle_sphere_offsets;
17use dreamwell_engine::physics::simulation::SuperpositionObserver;
18use dreamwell_fabric::causal_observer_lane::{causal_observer_lane_channel, CausalObserverLaneReceiver};
19use dreamwell_gates::{DreamGate, GateConfig};
20use dreamwell_gpu::quantum_bridge::QuantumBridge;
21use dreamwell_sdk::{
22 decohere::{Decoherence, DecoherenceConfig, DecoherenceInputMode},
23 ledger_lane_channel,
24 loom_budgeter::LoomBudgeter,
25 loom_limiter::LoomLimiter,
26 LedgerLaneReceiver, Oracle, OracleConfig,
27};
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum SimulationMode {
32 Preview,
34 Published,
36}
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40pub enum RuntimeReadiness {
41 Pending,
42 Syncing,
43 Ready,
44 Failed,
45}
46
47#[derive(Debug, Clone)]
49pub struct SimulationJournalEntry {
50 pub frame: u64,
51 pub kind: String,
52 pub detail: String,
53}
54
55pub struct SimulationService {
57 pub scene: GameObjectScene,
59 pub kernel: Option<CausalComputeKernel>,
61 pub encoder: Option<CausalEngineEncoder>,
63 pub bridge: Option<QuantumBridge>,
65 pub last_bridge_packet: Option<dreamwell_gpu::quantum_bridge::BridgePacket>,
67 pub decoherence: Option<Decoherence>,
69 pub oracle: Option<Oracle>,
71 pub decoherence_fields: Vec<dreamwell_quantum::DecoherenceField>,
73 pub last_result: Option<KernelResult>,
75 pub last_descriptor: Option<dreamwell_attention::SuperBodyFrameDescriptor>,
77 pub player_object_id: u64,
79 pub particle_offsets: Vec<[f32; 3]>,
81 pub observer: SuperpositionObserver,
83 pub journal: Vec<SimulationJournalEntry>,
85 pub console: Vec<String>,
87 pub mode: SimulationMode,
89 pub readiness: RuntimeReadiness,
91 pub frame: u64,
93 pub tick: u64,
95 pub quantum_cull_radius: f32,
97 pub elapsed_time: f32,
99 pub gate: Option<DreamGate>,
101 pub limiter: Option<LoomLimiter>,
103 pub budgeter: Option<LoomBudgeter>,
105 pub ledger_rx: Option<LedgerLaneReceiver>,
107 pub causal_observer_rx: Option<CausalObserverLaneReceiver>,
109 pub causal_observer_tx: Option<dreamwell_fabric::CausalObserverLaneSender>,
111 pub active_zone_index: usize,
113 pub metaphor_profile: dreamwell_metaphors::MetaphorProfile,
115 pub active_particle_count: f32,
117 pub coherence_target: f32,
119 pub coherence_current: f32,
121 pub gather_active: bool,
123 pub emit_strength: f32,
125 pub universe_snapshot_rx: Option<crossbeam_channel::Receiver<dreamwell_universe::FieldSnapshot>>,
127 pub universe_update_tx: Option<crossbeam_channel::Sender<dreamwell_universe::ClientUpdate>>,
129 pub universe_client_id: u64,
131 pub universe_connected: bool,
133}
134
135impl SimulationService {
136 pub const DEFAULT_QUANTUM_CULL_RADIUS: f32 = 9.14;
138
139 pub fn new(scene_name: &str, mode: SimulationMode) -> Self {
141 Self {
142 scene: GameObjectScene::new(scene_name.into()),
143 kernel: None,
144 encoder: None,
145 bridge: None,
146 last_bridge_packet: None,
147 decoherence: None,
148 oracle: None,
149 decoherence_fields: Vec::new(),
150 last_result: None,
151 last_descriptor: None,
152 player_object_id: 0,
153 particle_offsets: Vec::new(),
154 observer: SuperpositionObserver::new([0.0; 3], Self::DEFAULT_QUANTUM_CULL_RADIUS),
155 journal: Vec::new(),
156 console: Vec::new(),
157 mode,
158 readiness: RuntimeReadiness::Pending,
159 frame: 0,
160 tick: 0,
161 quantum_cull_radius: Self::DEFAULT_QUANTUM_CULL_RADIUS,
162 elapsed_time: 0.0,
163 gate: None,
164 limiter: None,
165 budgeter: None,
166 ledger_rx: None,
167 causal_observer_rx: None,
168 causal_observer_tx: None,
169 active_zone_index: 9,
170 metaphor_profile: dreamwell_metaphors::MetaphorProfile::wave(),
171 active_particle_count: dreamwell_metaphors::DEFAULT_PARTICLE_COUNT as f32,
172 coherence_target: 1.0,
173 coherence_current: 1.0,
174 gather_active: false,
175 emit_strength: 0.0,
176 universe_snapshot_rx: None,
177 universe_update_tx: None,
178 universe_client_id: 0,
179 universe_connected: false,
180 }
181 }
182
183 pub fn input_enabled(&self) -> bool {
185 self.readiness == RuntimeReadiness::Ready && self.encoder.is_some()
186 }
187
188 pub fn connect_universe(&mut self) -> crossbeam_channel::Sender<()> {
191 let config = dreamwell_universe::UniverseConfig::default();
192 let (shutdown_tx, shutdown_rx) = crossbeam_channel::bounded(1);
193
194 let mut field = dreamwell_universe::QuantumField::new(&config);
197 let mut connections = dreamwell_universe::ConnectionProtocol::new(config.max_clients);
198
199 let (client_id, snapshot_rx, update_tx) = connections
201 .connect(&mut field, 9)
202 .expect("Failed to connect to universe");
203
204 self.universe_snapshot_rx = Some(snapshot_rx);
205 self.universe_update_tx = Some(update_tx);
206 self.universe_client_id = client_id;
207 self.universe_connected = true;
208
209 log::info!("Connected to universe on layer 9 (client_id={})", client_id);
210
211 let config_clone = config.clone();
213 std::thread::Builder::new()
214 .name("dreamwell-universe".into())
215 .spawn(move || {
216 let mut kernel = dreamwell_universe::UniverseKernel {
217 config: config_clone.clone(),
218 field,
219 connections,
220 metrics: dreamwell_universe::metrics::UniverseMetrics::new(),
221 shutdown_rx,
222 };
223 kernel.run();
225 })
226 .expect("Failed to spawn universe thread");
227
228 shutdown_tx
229 }
230
231 pub fn set_coherence_target(&mut self, t: f32) {
233 self.coherence_target = t.clamp(0.0, 1.0);
234 }
235
236 pub fn set_gather(&mut self, active: bool) {
238 self.gather_active = active;
239 }
240
241 pub fn set_emit_strength(&mut self, s: f32) {
243 self.emit_strength = s.clamp(0.0, 1.0);
244 }
245
246 pub fn initialize(
251 &mut self,
252 scene: GameObjectScene,
253 spawn_pos: [f32; 3],
254 particle_count: u32,
255 ) -> Result<(), String> {
256 self.scene = scene;
258 self.journal.push(SimulationJournalEntry {
259 frame: 0,
260 kind: "mount_braid".into(),
261 detail: format!("scene: {}, objects: {}", self.scene.name, self.scene.objects.len()),
262 });
263 self.console.push(format!(
264 "[Info] Braid mounted from Chronoshift checkpoint (tick: {}).",
265 self.tick,
266 ));
267
268 if self.scene.objects.is_empty() {
270 self.readiness = RuntimeReadiness::Failed;
271 return Err("reality_check: empty scene".into());
272 }
273 self.console.push("[Info] Reality check: all phases passed.".into());
274
275 let obj_count = self.scene.objects.len();
277 self.console
278 .push(format!("[Info] Scene instantiated: {obj_count} objects."));
279
280 let kernel = CausalComputeKernel::new(spawn_pos, particle_count);
282 self.kernel = Some(kernel);
283 self.encoder = Some(CausalEngineEncoder::new(spawn_pos, particle_count, 42));
284 let mut bridge = QuantumBridge::new(particle_count);
285 bridge.set_metaphor_profile(self.metaphor_profile.clone());
286 self.bridge = Some(bridge);
287
288 let decoherence = Decoherence::with_config(DecoherenceConfig {
289 input_mode: DecoherenceInputMode::Particle,
290 ..DecoherenceConfig::default()
291 });
292 self.decoherence = Some(decoherence);
293
294 let mut oracle = Oracle::new(OracleConfig::default());
295 let (tx, rx) = ledger_lane_channel();
296 oracle.connect_ledger(tx);
297 oracle.begin_frame(0);
298 self.oracle = Some(oracle);
299 self.ledger_rx = Some(rx);
300
301 let (causal_observer_tx, causal_observer_rx) = causal_observer_lane_channel();
303 self.causal_observer_tx = Some(causal_observer_tx);
304 self.causal_observer_rx = Some(causal_observer_rx);
305
306 let hw = dreamwell_sdk::loom_budgeter::HardwareProfile::mid_range();
308 let budgeter = LoomBudgeter::from_hardware(hw);
309 let mut limiter = LoomLimiter::default();
310 budgeter.apply_to_limiter(&mut limiter);
311 let gate = DreamGate::new(GateConfig::from_services(&limiter, &budgeter));
312 self.gate = Some(gate);
313 self.limiter = Some(limiter);
314 self.budgeter = Some(budgeter);
315
316 self.particle_offsets = particle_sphere_offsets(particle_count);
317
318 self.player_object_id = self
320 .scene
321 .objects
322 .iter()
323 .find(|o| {
324 o.property_tags
325 .iter()
326 .any(|t| t == "isInputReceiver" || t == "isPlayer")
327 })
328 .map(|o| o.id)
329 .unwrap_or(1);
330
331 let starting_layer = self
332 .scene
333 .objects
334 .iter()
335 .filter(|o| o.property_tags.iter().any(|t| t == "isInputReceiver"))
336 .flat_map(|o| o.property_tags.iter())
337 .find_map(|t| {
338 t.strip_prefix("isStartingOnTopologyLayer")
339 .and_then(|v| v.parse::<u8>().ok())
340 })
341 .unwrap_or(9);
342 if let Some(ref mut k) = self.kernel {
343 k.active_topology_layer = starting_layer;
344 }
345 if let Some(ref mut enc) = self.encoder {
346 enc.kernel_mut().active_topology_layer = starting_layer;
347 }
348 self.active_zone_index = starting_layer as usize;
349
350 self.observer.position = spawn_pos;
351 self.observer.active_radius = self.quantum_cull_radius;
352 self.console.push(format!(
353 "[Info] CausalComputeKernel mounted at [{:.1}, {:.1}, {:.1}] ({particle_count} particles).",
354 spawn_pos[0], spawn_pos[1], spawn_pos[2],
355 ));
356 self.journal.push(SimulationJournalEntry {
357 frame: 0,
358 kind: "avatar_init".into(),
359 detail: format!("particles: {particle_count}, pos: {spawn_pos:?}, sdk: active"),
360 });
361
362 self.console.push("[Info] GPU resources synced.".into());
364
365 if self.encoder.is_none() || self.scene.objects.is_empty() {
367 self.readiness = RuntimeReadiness::Failed;
368 return Err("stitch: render graph validation failed".into());
369 }
370 self.readiness = RuntimeReadiness::Syncing;
371 self.console
372 .push("[Info] Stitch passed: render graph validated.".into());
373
374 self.readiness = RuntimeReadiness::Ready;
376 self.console
377 .push("[Info] Seer is happy, reality rendered without concern.".into());
378 self.console
379 .push("[Info] Input controls attached to CausalComputeKernel.".into());
380 self.console.push(format!(
381 "[Info] Quantum pipeline: {} particles, density matrix 5×5, adaptive Hamiltonian (Model 1).",
382 particle_count,
383 ));
384 self.console
385 .push("[Info] Couplings will adapt to movement patterns via parameter shift gradient.".into());
386 log::info!(
387 "Quantum locomotion active: {} particles, F(12)={}, adaptive Hamiltonian learning enabled",
388 particle_count,
389 particle_count == 144,
390 );
391
392 let _universe_shutdown = self.connect_universe();
395 self.console
396 .push("[Info] Universe connected: persistent quantum field at 1618Hz (φ-scaled).".into());
397
398 if let (Some(ref decoherence), Some(ref mut gate)) = (&self.decoherence, &mut self.gate) {
400 let weave = decoherence.build_session_begin_weave(0);
401 gate.submit(weave, 100);
402 }
403
404 Ok(())
405 }
406
407 pub fn tick(&mut self, packet: &InputPacket) -> Option<KernelResult> {
418 if !self.input_enabled() {
419 return None;
420 }
421
422 self.frame += 1;
423 self.tick += 1;
424 self.elapsed_time += packet.dt;
425
426 let coherence_rate = 6.0_f32;
429 let coherence_t = 1.0 - (-coherence_rate * packet.dt).exp();
430 self.coherence_current += (self.coherence_target - self.coherence_current) * coherence_t;
431
432 let gate = self.gate.as_mut()?;
433 let tick = self.tick;
434
435 gate.begin_frame(tick);
436 if let Some(ref mut limiter) = self.limiter {
437 limiter.begin_frame(tick);
438 }
439
440 if let Some(ref mut decoherence) = self.decoherence {
442 let actions = packet.actions_as_u8();
443 let movement = packet.movement;
444 let look = [packet.camera_yaw, 0.0];
445 let input_weave = decoherence.build_input_weave(tick, actions, movement, look, 0.0);
446 gate.submit(input_weave, 100);
447 }
448
449 let encoder = self.encoder.as_mut()?;
455 let mut enc_packet = packet.clone();
456 enc_packet.grounded = encoder.kernel().grounded;
457 enc_packet.timestamp = self.elapsed_time as f64;
458 enc_packet.coherence_target = self.coherence_current;
459 let encode_result = encoder.encode_frame(&enc_packet, &self.decoherence_fields);
460
461 if self.frame % 2 == 0 {
466 let kernel = encoder.kernel_mut();
467 let bias = kernel.quantum.hamiltonian.bias;
468 let couplings = kernel.quantum.hamiltonian.couplings;
469 let moving = kernel.velocity[0].abs() > 0.01 || kernel.velocity[2].abs() > 0.01;
471 let sprinting = enc_packet.sprint && moving;
472 let input_bias = [
473 if !moving { 2.0 } else { 0.0 },
474 if moving && !sprinting { 2.0 } else { 0.0 },
475 if enc_packet.jump { 3.0 } else { 0.0 },
476 if kernel.landing_timer > 0.0 { 2.0 } else { 0.0 },
477 if sprinting { 2.0 } else { 0.0 },
478 ];
479 let shift = std::f32::consts::FRAC_PI_2;
480 let lr = 0.005_f32;
481
482 let mut new_couplings = couplings;
483 for k in 0..5 {
484 let mut c_plus = couplings;
485 c_plus[k] = (c_plus[k] + shift).min(2.0);
486 let f_plus = kernel.quantum.rho.free_energy(&bias, &input_bias, &c_plus);
487
488 let mut c_minus = couplings;
489 c_minus[k] = (c_minus[k] - shift).max(0.1);
490 let f_minus = kernel.quantum.rho.free_energy(&bias, &input_bias, &c_minus);
491
492 let grad = (f_plus - f_minus) / 2.0;
493 new_couplings[k] = (new_couplings[k] - lr * grad).clamp(0.1, 2.0);
495 }
496 kernel.quantum.hamiltonian.couplings = new_couplings;
497
498 if self.frame % 120 == 0 {
499 log::info!(
500 "Adaptive Hamiltonian: couplings=[{:.3}, {:.3}, {:.3}, {:.3}, {:.3}] F={:.4}",
501 new_couplings[0],
502 new_couplings[1],
503 new_couplings[2],
504 new_couplings[3],
505 new_couplings[4],
506 kernel.quantum.rho.free_energy(&bias, &input_bias, &new_couplings),
507 );
508 }
509 }
510
511 let pos = encode_result.kernel_result.position;
514
515 if let Some(ref mut k) = self.kernel {
517 k.position = pos;
518 k.velocity = encoder.kernel().velocity;
519 k.grounded = encoder.kernel().grounded;
520 k.facing_yaw = encoder.kernel().facing_yaw;
521 }
522
523 if let Some(ref mut decoherence) = self.decoherence {
525 let facing_yaw = encoder.kernel().facing_yaw;
526 let obs_weave = decoherence.build_observe_weave(tick, pos, facing_yaw);
527 gate.submit(obs_weave, 100);
528
529 if let Some(ref prev) = self.last_result {
531 if encode_result.kernel_result.locomotion_mode != prev.locomotion_mode {
532 let loco_state = match encode_result.kernel_result.locomotion_mode {
533 0 => dreamwell_sdk::decohere::LocomotionState::Idle,
534 1 => dreamwell_sdk::decohere::LocomotionState::Walking,
535 2 => dreamwell_sdk::decohere::LocomotionState::Running,
536 3 => dreamwell_sdk::decohere::LocomotionState::Jumping,
537 4 => dreamwell_sdk::decohere::LocomotionState::Falling,
538 _ => dreamwell_sdk::decohere::LocomotionState::Idle,
539 };
540 let loco_weave = decoherence.build_locomotion_weave(tick, loco_state);
541 gate.submit(loco_weave, 90);
542 }
543 }
544
545 if encode_result.kernel_result.layer_changed {
547 self.active_zone_index = encode_result.kernel_result.topology_layer as usize;
548 }
549 }
550
551 if let Some(ref mut oracle) = self.oracle {
553 let (_accepted, _rejected, _mutations) = gate.dispatch_through_oracle(oracle, &mut self.scene);
554 oracle.end_frame();
555 oracle.begin_frame(self.tick);
556 }
557
558 if let Some(ref causal_observer_rx) = self.causal_observer_rx {
560 let gpu_events = causal_observer_rx.drain();
561 if !gpu_events.is_empty() {
562 log::trace!("causal_observer_lane: drained {} GPU frame events", gpu_events.len());
563 }
564 }
565
566 let tag_frame = self.evaluate_tag_influences(pos);
569 if let Some(ref mut bridge) = self.bridge {
570 bridge.set_tag_frame(tag_frame);
571 let (bp, _validation) = bridge.bridge(&encode_result, packet.dt, self.elapsed_time);
572 self.last_bridge_packet = Some(bp);
573 }
574
575 self.update_particle_siphon(pos, packet.dt);
577
578 self.observer.position = pos;
581 let result = encode_result.kernel_result;
582
583 self.last_result = Some(KernelResult {
584 position: result.position,
585 locomotion_mode: result.locomotion_mode,
586 form: result.form,
587 coherence: result.coherence,
588 populations: result.populations,
589 entropy: result.entropy,
590 purity: result.purity,
591 active_clip: String::new(),
592 moved: result.moved,
593 coalesce_events: Vec::new(),
594 topology_layer: result.topology_layer,
595 layer_changed: false,
596 });
597
598 if self.universe_connected {
600 if let Some(ref tx) = self.universe_update_tx {
602 if let Some(ref enc) = self.encoder {
603 let k = enc.kernel();
604 let update = dreamwell_universe::ClientUpdate {
605 client_id: self.universe_client_id,
606 populations: k.quantum.rho.populations(),
607 coherences: k.quantum.rho.off_diagonal_pairs(),
608 position: k.position,
609 layer: self.active_zone_index as u8,
610 frame: self.frame,
611 };
612 let _ = tx.try_send(update);
613 }
614 }
615 if let Some(ref rx) = self.universe_snapshot_rx {
617 if let Ok(snapshot) = rx.try_recv() {
618 if let Some(ref mut enc) = self.encoder {
619 let layer = self.active_zone_index;
620 let channel = &snapshot.channels[layer];
621 let coupling_eps = 0.03 * packet.dt;
622 let field_coh = channel.coherence_magnitude;
623 let rho = &mut enc.kernel_mut().quantum.rho;
624 for i in 0..5 {
625 for j in 0..5 {
626 if i != j {
627 let retain = 1.0 - coupling_eps * (1.0 - field_coh);
628 rho.entries[i * 5 + j] = rho.entries[i * 5 + j].scale(retain.max(0.0));
629 }
630 }
631 }
632 if self.frame % 300 == 0 {
633 log::info!(
634 "Universe sync: tick={} field_coh={:.4} field_F={:.4}",
635 snapshot.tick,
636 field_coh,
637 channel.free_energy,
638 );
639 }
640 }
641 }
642 }
643 }
644
645 Some(result)
646 }
647
648 fn observer_position(&self) -> Option<[f32; 3]> {
650 self.encoder
651 .as_ref()
652 .map(|e| e.kernel().position)
653 .or_else(|| self.kernel.as_ref().map(|k| k.position))
654 }
655
656 pub fn check_collisions(&self, threshold: f32) -> Vec<(u64, f32)> {
658 let pos = match self.observer_position() {
659 Some(p) => p,
660 None => return Vec::new(),
661 };
662
663 self.scene
664 .objects
665 .iter()
666 .filter_map(|o| {
667 let is_collider = o.property_tags.iter().any(|t| t == "isWall" || t == "isCollider");
668 if !is_collider {
669 return None;
670 }
671 let dx = o.transform.position[0] - pos[0];
672 let dy = o.transform.position[1] - pos[1];
673 let dz = o.transform.position[2] - pos[2];
674 let dist = (dx * dx + dy * dy + dz * dz).sqrt();
675 if dist <= threshold {
676 Some((o.id, dist))
677 } else {
678 None
679 }
680 })
681 .collect()
682 }
683
684 pub fn check_poi_proximity(&self, radius: f32) -> Vec<(u64, f32)> {
686 let pos = match self.observer_position() {
687 Some(p) => p,
688 None => return Vec::new(),
689 };
690
691 self.scene
692 .objects
693 .iter()
694 .filter_map(|o| {
695 let is_poi = o.property_tags.iter().any(|t| t == "poi" || t == "isInteractable");
696 if !is_poi {
697 return None;
698 }
699 let dx = o.transform.position[0] - pos[0];
700 let dy = o.transform.position[1] - pos[1];
701 let dz = o.transform.position[2] - pos[2];
702 let dist = (dx * dx + dy * dy + dz * dz).sqrt();
703 if dist <= radius {
704 Some((o.id, dist))
705 } else {
706 None
707 }
708 })
709 .collect()
710 }
711
712 pub fn tick_poi_interactions(&mut self) {
714 let pois = self.check_poi_proximity(5.0);
715 if pois.is_empty() {
716 return;
717 }
718
719 let gate = match &mut self.gate {
720 Some(g) => g,
721 None => return,
722 };
723 let decoherence = match &self.decoherence {
724 Some(d) => d,
725 None => return,
726 };
727 let tick = self.tick;
728
729 for (poi_id, dist) in &pois {
730 if *dist < 2.0 {
731 let weave = decoherence.build_interaction_weave(tick, *poi_id);
732 gate.submit(weave, 80);
733 }
734 }
735 }
736
737 pub fn tick_collisions(&mut self) {
739 let collisions = self.check_collisions(2.0);
740 if collisions.is_empty() {
741 return;
742 }
743
744 let kernel = if let Some(ref mut enc) = self.encoder {
746 enc.kernel_mut()
747 } else if let Some(ref mut k) = self.kernel {
748 k
749 } else {
750 return;
751 };
752 for (obj_id, dist) in &collisions {
753 if let Some(obj) = self.scene.find(*obj_id) {
754 if *dist < 0.01 {
755 continue;
756 }
757 let push_str = (1.0 - dist / 2.0).max(0.0) * 0.5;
758 let dx = kernel.position[0] - obj.transform.position[0];
759 let dz = kernel.position[2] - obj.transform.position[2];
760 let len = (dx * dx + dz * dz).sqrt().max(0.001);
761 kernel.position[0] += (dx / len) * push_str;
762 kernel.position[2] += (dz / len) * push_str;
763 }
764 }
765 }
766
767 fn evaluate_tag_influences(&self, pos: [f32; 3]) -> dreamwell_metaphors::TagInfluenceFrame {
770 let mut frame = dreamwell_metaphors::TagInfluenceFrame::empty();
771 let aoi_radius = self.quantum_cull_radius;
772
773 for obj in &self.scene.objects {
774 let is_poi = obj
775 .property_tags
776 .iter()
777 .any(|t| t == "isPointOfInterest" || t == "poi" || t == "isInteractable");
778 if !is_poi {
779 continue;
780 }
781
782 let dx = obj.transform.position[0] - pos[0];
783 let dy = obj.transform.position[1] - pos[1];
784 let dz = obj.transform.position[2] - pos[2];
785 let dist = (dx * dx + dy * dy + dz * dz).sqrt();
786 if dist > aoi_radius {
787 continue;
788 }
789
790 let coupling = (1.0 - dist / aoi_radius).max(0.0);
792 let siphon_eligible = obj.property_tags.iter().any(|t| t == "isSiphon" || t == "isFractal");
793
794 frame.poi_couplings.push(dreamwell_metaphors::PoiCoupling {
795 poi_id: obj.id,
796 distance: dist,
797 coupling,
798 siphon_eligible,
799 });
800 }
801
802 for obj in &self.scene.objects {
804 let is_zone = obj
805 .property_tags
806 .iter()
807 .any(|t| t.starts_with("isTopologyTransition") || t == "isDecoherenceZone");
808 if !is_zone {
809 continue;
810 }
811
812 let dx = obj.transform.position[0] - pos[0];
813 let dz = obj.transform.position[2] - pos[2];
814 let dist = (dx * dx + dz * dz).sqrt();
815 if dist > aoi_radius {
816 continue;
817 }
818
819 let proximity = (1.0 - dist / aoi_radius).max(0.0);
820 frame.zone_biases.push(dreamwell_metaphors::ZoneBias {
821 zone_id: obj.id,
822 morph_bias: proximity * 0.3,
823 coherence_bias: -proximity * 0.2, });
825 }
826
827 frame
828 }
829
830 fn update_particle_siphon(&mut self, pos: [f32; 3], dt: f32) {
834 let default_count = dreamwell_metaphors::DEFAULT_PARTICLE_COUNT as f32;
835 let siphon_rate = 30.0; let regen_rate = 10.0; let mut total_siphon = 0.0f32;
839 for obj in &self.scene.objects {
840 let is_siphon = obj.property_tags.iter().any(|t| t == "isSiphon" || t == "isFractal");
841 if !is_siphon {
842 continue;
843 }
844
845 let dx = obj.transform.position[0] - pos[0];
846 let dy = obj.transform.position[1] - pos[1];
847 let dz = obj.transform.position[2] - pos[2];
848 let dist = (dx * dx + dy * dy + dz * dz).sqrt();
849 let coupling = (1.0 - dist / self.quantum_cull_radius).max(0.0);
850 if coupling > 0.1 {
851 total_siphon += coupling;
852 }
853 }
854
855 if total_siphon > 0.0 {
856 self.active_particle_count -= siphon_rate * total_siphon * dt;
857 } else if self.active_particle_count < default_count {
858 self.active_particle_count += regen_rate * dt;
859 }
860
861 self.active_particle_count = self.active_particle_count.clamp(16.0, default_count);
863 }
864
865 pub fn set_metaphor_profile(&mut self, profile: dreamwell_metaphors::MetaphorProfile) {
867 self.metaphor_profile = profile.clone();
868 if let Some(ref mut bridge) = self.bridge {
869 bridge.set_metaphor_profile(profile);
870 }
871 }
872
873 pub fn cycle_metaphor(&mut self) -> &'static str {
876 let new = dreamwell_metaphors::cycle_next(self.metaphor_profile.name);
877 let name = new.name;
878 self.set_metaphor_profile(new);
879 name
880 }
881
882 pub fn end_session(&mut self) {
884 if let (Some(ref decoherence), Some(ref mut oracle), Some(ref mut gate)) =
886 (&self.decoherence, &mut self.oracle, &mut self.gate)
887 {
888 let weave = decoherence.build_session_end_weave(self.tick);
889 gate.submit(weave, 100);
890 let _ = gate.dispatch_through_oracle(oracle, &mut self.scene);
891 }
892
893 self.readiness = RuntimeReadiness::Pending;
894 self.kernel = None;
895 self.encoder = None;
896 self.bridge = None;
897 self.last_bridge_packet = None;
898 self.decoherence = None;
899 self.oracle = None;
900 self.causal_observer_rx = None;
901 self.causal_observer_tx = None;
902 self.decoherence_fields.clear();
903 self.last_result = None;
904 self.last_descriptor = None;
905 self.console.push("[Info] Client closing.. Chronoshift success.".into());
906 self.console
907 .push("[Info] Reality checked and loaded. Editor session restored.".into());
908 self.journal.push(SimulationJournalEntry {
909 frame: self.frame,
910 kind: "end_session".into(),
911 detail: "play".into(),
912 });
913 }
914}
915
916#[cfg(test)]
919mod tests {
920 use super::*;
921
922 fn test_scene() -> GameObjectScene {
923 let mut s = GameObjectScene::new("test".into());
924 s.spawn("Avatar Start".into()).unwrap();
925 s.objects[0].property_tags.push("isPlayer".into());
926 s.objects[0].property_tags.push("isInputReceiver".into());
927 s.objects[0].transform.position = [0.0, 0.5, 0.0];
928 s.spawn("Ground".into()).unwrap();
929 s.objects[1].property_tags.push("isStatic".into());
930 s.objects[1].property_tags.push("isGround".into());
931 s
932 }
933
934 #[test]
935 fn simulation_new() {
936 let sim = SimulationService::new("test", SimulationMode::Preview);
937 assert_eq!(sim.readiness, RuntimeReadiness::Pending);
938 assert!(!sim.input_enabled());
939 }
940
941 #[test]
942 fn simulation_initialize_7_steps() {
943 let mut sim = SimulationService::new("test", SimulationMode::Preview);
944 let scene = test_scene();
945 let result = sim.initialize(scene, [0.0, 0.5, 0.0], 128);
946 assert!(result.is_ok(), "init should succeed: {:?}", result.err());
947 assert_eq!(sim.readiness, RuntimeReadiness::Ready);
948 assert!(sim.input_enabled());
949 assert!(sim.kernel.is_some());
950 assert!(sim.encoder.is_some());
951 assert!(sim.bridge.is_some());
952 assert!(sim.decoherence.is_some());
953 assert!(sim.oracle.is_some());
954 assert!(sim.console.iter().any(|m| m.contains("Seer is happy")));
955 assert!(sim.console.iter().any(|m| m.contains("CausalComputeKernel")));
956 }
957
958 #[test]
959 fn simulation_input_gated_before_ready() {
960 let mut sim = SimulationService::new("test", SimulationMode::Preview);
961 let packet = InputPacket {
962 movement: [0.0, 1.0],
963 ..InputPacket::idle(1.0 / 60.0, 0)
964 };
965 let result = sim.tick(&packet);
966 assert!(result.is_none(), "input should be gated before init");
967 }
968
969 #[test]
970 fn simulation_tick_moves_player() {
971 let mut sim = SimulationService::new("test", SimulationMode::Preview);
972 sim.initialize(test_scene(), [0.0, 0.5, 0.0], 128).unwrap();
973 let packet = InputPacket {
974 movement: [0.0, 1.0],
975 ..InputPacket::idle(1.0 / 60.0, 1)
976 };
977 let result = sim.tick(&packet);
978 assert!(result.is_some());
979 let r = result.unwrap();
980 assert!(r.position != [0.0, 0.5, 0.0], "player should move after forward input");
981 }
982
983 #[test]
984 fn simulation_collision_detection() {
985 let mut sim = SimulationService::new("test", SimulationMode::Preview);
986 let mut scene = test_scene();
987 scene.spawn("Wall".into()).unwrap();
988 let wall_idx = scene.objects.len() - 1;
989 scene.objects[wall_idx].property_tags.push("isWall".into());
990 scene.objects[wall_idx].property_tags.push("isCollider".into());
991 scene.objects[wall_idx].transform.position = [0.5, 0.5, 0.0];
992 sim.initialize(scene, [0.0, 0.5, 0.0], 128).unwrap();
993
994 let collisions = sim.check_collisions(1.0);
995 assert!(!collisions.is_empty());
996 }
997
998 #[test]
999 fn simulation_poi_proximity() {
1000 let mut sim = SimulationService::new("test", SimulationMode::Preview);
1001 let mut scene = test_scene();
1002 scene.spawn("POI".into()).unwrap();
1003 let poi_idx = scene.objects.len() - 1;
1004 scene.objects[poi_idx].property_tags.push("isInteractable".into());
1005 scene.objects[poi_idx].transform.position = [0.0, 0.5, -1.0];
1006 sim.initialize(scene, [0.0, 0.5, 0.0], 128).unwrap();
1007
1008 let pois = sim.check_poi_proximity(2.0);
1009 assert!(!pois.is_empty());
1010 }
1011
1012 #[test]
1013 fn simulation_end_session() {
1014 let mut sim = SimulationService::new("test", SimulationMode::Preview);
1015 sim.initialize(test_scene(), [0.0, 0.5, 0.0], 128).unwrap();
1016 sim.end_session();
1017 assert_eq!(sim.readiness, RuntimeReadiness::Pending);
1018 assert!(!sim.input_enabled());
1019 assert!(sim.kernel.is_none());
1020 assert!(sim.encoder.is_none());
1021 assert!(sim.bridge.is_none());
1022 assert!(sim.decoherence.is_none());
1023 assert!(sim.oracle.is_none());
1024 assert!(sim.console.iter().any(|m| m.contains("Chronoshift success")));
1025 }
1026
1027 #[test]
1028 fn simulation_empty_scene_fails() {
1029 let mut sim = SimulationService::new("test", SimulationMode::Preview);
1030 let scene = GameObjectScene::new("empty".into());
1031 let result = sim.initialize(scene, [0.0; 3], 128);
1032 assert!(result.is_err());
1033 assert_eq!(sim.readiness, RuntimeReadiness::Failed);
1034 }
1035
1036 #[test]
1037 fn simulation_mode_variants() {
1038 assert_ne!(SimulationMode::Preview, SimulationMode::Published);
1039 }
1040
1041 #[test]
1042 fn cycle_metaphor_changes_profile() {
1043 let mut sim = SimulationService::new("test", SimulationMode::Preview);
1044 sim.initialize(test_scene(), [0.0, 0.5, 0.0], 128).unwrap();
1045 assert_eq!(sim.metaphor_profile.name, "wave");
1046 let name = sim.cycle_metaphor();
1047 assert_eq!(name, "keynote_fibonacci");
1048 assert_eq!(sim.metaphor_profile.name, "keynote_fibonacci");
1049 let name = sim.cycle_metaphor();
1050 assert_eq!(name, "cohered_stream");
1051 let name = sim.cycle_metaphor();
1052 assert_eq!(name, "wave");
1053 }
1054
1055 #[test]
1056 fn default_profile_is_wave() {
1057 let sim = SimulationService::new("test", SimulationMode::Preview);
1058 assert_eq!(sim.metaphor_profile.name, "wave");
1059 }
1060
1061 #[test]
1062 fn simulation_quantum_cull_radius() {
1063 let sim = SimulationService::new("test", SimulationMode::Preview);
1064 assert!((sim.quantum_cull_radius - SimulationService::DEFAULT_QUANTUM_CULL_RADIUS).abs() < 0.01);
1065 }
1066
1067 #[test]
1068 fn simulation_observer_position_updates() {
1069 let mut sim = SimulationService::new("test", SimulationMode::Preview);
1070 sim.initialize(test_scene(), [0.0, 0.5, 0.0], 128).unwrap();
1071 let packet = InputPacket {
1072 movement: [0.0, 1.0],
1073 ..InputPacket::idle(1.0, 1)
1074 };
1075 sim.tick(&packet);
1076 assert!(
1077 sim.observer.position != [0.0, 0.5, 0.0],
1078 "observer should follow kernel position"
1079 );
1080 }
1081
1082 #[test]
1083 fn simulation_journal_records_lifecycle() {
1084 let mut sim = SimulationService::new("test", SimulationMode::Preview);
1085 sim.initialize(test_scene(), [0.0, 0.5, 0.0], 128).unwrap();
1086 assert!(sim.journal.iter().any(|e| e.kind == "mount_braid"));
1087 assert!(sim.journal.iter().any(|e| e.kind == "avatar_init"));
1088 sim.end_session();
1089 assert!(sim.journal.iter().any(|e| e.kind == "end_session"));
1090 }
1091}