khora_lanes/physics_lane/mod.rs
1// Copyright 2025 eraflo
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Physics Lane.
16//!
17//! After the Phase A refactor, `StandardPhysicsLane` is a thin wrapper
18//! around [`PhysicsProvider::step`]: it does **no** World queries and
19//! **no** structural mutations. The surrounding work is split into three
20//! Substrate-Pass stages:
21//!
22//! 1. [`khora_data::flow::PhysicsFlow::adapt`] — AGDF detach/reattach
23//! plus the `ECS → provider` sync (`sync_to_world`-equivalent) that
24//! feeds the simulation its inputs.
25//! 2. **This lane** — `provider.step(dt)`. Pure simulation, no World
26//! access at all.
27//! 3. [`khora_data::ecs::systems::physics_world_writeback`] — pulls the
28//! new transforms, kinematic results, and collision events back into
29//! the World during the `Maintenance` phase.
30
31use khora_core::physics::PhysicsProvider;
32
33/// The standard physics lane for industrial-grade simulation.
34#[derive(Debug, Default)]
35pub struct StandardPhysicsLane;
36
37impl StandardPhysicsLane {
38 /// Creates a new `StandardPhysicsLane`.
39 pub fn new() -> Self {
40 Self
41 }
42}
43
44impl khora_core::lane::Lane for StandardPhysicsLane {
45 fn strategy_name(&self) -> &'static str {
46 "StandardPhysics"
47 }
48
49 fn lane_kind(&self) -> khora_core::lane::LaneKind {
50 khora_core::lane::LaneKind::Physics
51 }
52
53 fn execute(
54 &self,
55 ctx: &mut khora_core::lane::LaneContext,
56 ) -> Result<(), khora_core::lane::LaneError> {
57 use khora_core::lane::{LaneError, OutputDeck, Slot};
58
59 let dt = ctx
60 .get::<khora_core::lane::PhysicsDeltaTime>()
61 .ok_or(LaneError::missing("PhysicsDeltaTime"))?
62 .0;
63 let provider = ctx
64 .get::<Slot<dyn PhysicsProvider>>()
65 .ok_or(LaneError::missing("Slot<dyn PhysicsProvider>"))?
66 .get();
67
68 provider.step(dt);
69
70 // Mark the simulation as having advanced this frame. The
71 // `physics_world_writeback` DataSystem checks this slot and only
72 // pulls fresh transforms from the provider when it's present —
73 // unifying the Lane → Deck → DataSystem pattern across audio and
74 // physics, and avoiding stale writebacks when the agent is paused.
75 if let Some(deck_slot) = ctx.get::<Slot<OutputDeck>>() {
76 *deck_slot
77 .get()
78 .slot::<khora_data::flow::PhysicsStepResult>() =
79 khora_data::flow::PhysicsStepResult { dt };
80 }
81 Ok(())
82 }
83
84 fn as_any(&self) -> &dyn std::any::Any {
85 self
86 }
87
88 fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
89 self
90 }
91}