condor-pathfinding-geometry 0.4.0

Continuous polygonal pathfinding algorithms and geometry primitives for Condor.
Documentation
//! Private candidate: prepared geometry kernel for fixed-source polygonal maps.
//!
//! # Hypothesis and contract
//!
//! A prepared scene-geometry kernel with a direct source-to-goal incumbent can
//! improve repeated-query work while preserving the exact Euclidean path cost
//! and witness contract of [`crate::shortest_path_map::ContinuousShortestPathMap`]
//! for one source on one immutable, validated scene.
//!
//! The candidate is private and unexported. It must not expand the prepared
//! contract to arbitrary changing start-goal pairs or expose a second public
//! preprocessing surface.
//!
//! # Non-negotiable constraints
//!
//! - The prepared scene is an immutable validated snapshot; changing geometry
//!   requires a new preprocess step.
//! - Source and goal validation, no-path classification, path witness, and
//!   Euclidean cost semantics must remain identical to the established map.
//! - One-shot timing captures are not benchmark authority. A promotion claim
//!   needs a reproducible multi-goal workload and a declared break-even.
//!
//! # Evidence and promotion
//!
//! Use the ordinary geometry owner route during implementation:
//! `just test-fast continuous` and `just clippy-target continuous`. Before a
//! public decision, require `just test-geometry-conformance` plus reproducible
//! repeated-query evidence through `just bench-continuous-core` and, for stress
//! workload claims, `just bench-continuous-stress`. This candidate must first be
//! integrated into those existing benchmark lanes; it does not create its own.
//! Promote only when exact cost/witness parity is established and the
//! preprocess/query break-even is explicit for the measured workload.

use crate::continuous::{PolygonPath, PolygonSearchResult};
use crate::polygonal::{Point2, PolygonScene};
use crate::shortest_path_map::{
    ContinuousShortestPathMap, PolygonShortestPathMap, PolygonShortestPathMapBuildError,
    PolygonShortestPathMapBuilder, PreparedContinuousShortestPathMap,
};

const EPSILON: f64 = 1e-9;

/// Builder: immutable geometry kernel with direct source–goal incumbent query.
///
/// Preprocess wraps [`ContinuousShortestPathMap`] and retains a scene snapshot
/// for the direct-segment incumbent check. Query tries the walkable source→goal
/// segment first; otherwise it falls through to the prepared source-rooted map.
#[derive(Debug, Clone, Copy, Default)]
pub struct ContinuousMapPreparedGeometryKernelBuilder;

impl ContinuousMapPreparedGeometryKernelBuilder {
    /// Stable portfolio id for the repeated polygonal fixed-source family.
    pub const CANDIDATE_ID: &str = "repeated-polygonal-fixed-source/prepared-geometry-kernel";
}

impl PolygonShortestPathMapBuilder for ContinuousMapPreparedGeometryKernelBuilder {
    type Map = PreparedGeometryKernelMap;

    fn name(&self) -> &'static str {
        "prepared-geometry-kernel"
    }

    fn preprocess(
        &self,
        scene: &PolygonScene,
        source: Point2,
    ) -> Result<Self::Map, PolygonShortestPathMapBuildError> {
        let inner = ContinuousShortestPathMap.preprocess(scene, source)?;
        Ok(PreparedGeometryKernelMap {
            scene: scene.clone(),
            inner,
        })
    }
}

/// Prepared map: direct-incumbent query over a source-rooted continuous kernel.
#[derive(Debug, Clone)]
pub struct PreparedGeometryKernelMap {
    scene: PolygonScene,
    inner: PreparedContinuousShortestPathMap,
}

impl PolygonShortestPathMap for PreparedGeometryKernelMap {
    fn name(&self) -> &'static str {
        "prepared-geometry-kernel"
    }

    fn source(&self) -> Point2 {
        self.inner.source()
    }

    fn query(&self, goal: Point2) -> PolygonSearchResult {
        let source = self.inner.source();

        if points_equal(source, goal) {
            return crate::continuous::found(
                PolygonPath::from_points(vec![source])
                    .expect("polygon path contains at least one point"),
                1,
            );
        }

        // Goal validation matches the established map (InvalidGoal on sealed /
        // non-traversable endpoints).
        if self.scene.validate_goal(goal).is_err() {
            return Err(crate::continuous::PolygonSearchError::InvalidGoal { point: goal });
        }

        // Direct source–goal incumbent: when the straight segment is free, it is
        // the unique Euclidean optimum (no detour can be shorter).
        if self.scene.segment_is_walkable(source, goal) {
            let cost = source.distance_to(goal);
            return crate::continuous::found(
                PolygonPath::from_points_with_cost(vec![source, goal], cost)
                    .expect("polygon path contains at least one point"),
                1,
            );
        }

        // Blocked direct segment: exact multi-terminal scan via prepared kernel.
        self.inner.query(goal)
    }
}

fn points_equal(left: Point2, right: Point2) -> bool {
    (left.x - right.x).abs() <= EPSILON && (left.y - right.y).abs() <= EPSILON
}

#[cfg(test)]
mod tests {
    use super::ContinuousMapPreparedGeometryKernelBuilder;
    use crate::polygonal::{Point2, Polygon, PolygonScene, WorldBounds};
    use crate::shortest_path_map::{
        ContinuousShortestPathMap, PolygonShortestPathMap, PolygonShortestPathMapBuilder,
    };

    #[test]
    fn query_parity_vs_continuous_shortest_path_map() {
        let scene = PolygonScene {
            world_bounds: WorldBounds::new(Point2::new(0.0, 0.0), Point2::new(12.0, 12.0)),
            obstacles: vec![Polygon::new(vec![
                Point2::new(4.0, 4.0),
                Point2::new(6.0, 4.0),
                Point2::new(6.0, 8.0),
                Point2::new(4.0, 8.0),
            ])],
        };
        let source = Point2::new(1.0, 1.0);
        let builder = ContinuousMapPreparedGeometryKernelBuilder;
        let map = builder
            .preprocess(&scene, source)
            .expect("kernel map should preprocess");
        let baseline = ContinuousShortestPathMap
            .preprocess(&scene, source)
            .expect("baseline map should preprocess");

        assert_eq!(
            ContinuousMapPreparedGeometryKernelBuilder::CANDIDATE_ID,
            "repeated-polygonal-fixed-source/prepared-geometry-kernel"
        );
        assert_eq!(builder.name(), "prepared-geometry-kernel");
        assert_eq!(map.name(), "prepared-geometry-kernel");
        assert_eq!(map.source(), source);

        for goal in [
            Point2::new(5.0, 1.0),
            Point2::new(10.0, 5.0),
            Point2::new(10.0, 10.0),
            Point2::new(1.0, 1.0),
        ] {
            let candidate = map.query(goal).expect("valid goal");
            let reference = baseline.query(goal).expect("valid goal");
            assert_eq!(
                candidate.is_found(),
                reference.is_found(),
                "found parity for goal {goal:?}"
            );
            match (candidate.cost(), reference.cost()) {
                (Some(left), Some(right)) => {
                    assert!(
                        (left - right).abs() <= 1e-9,
                        "cost parity for goal {goal:?}: {left} vs {right}"
                    );
                }
                (None, None) => {}
                other => panic!("cost shape mismatch for goal {goal:?}: {other:?}"),
            }
        }
    }

    #[test]
    fn no_path_for_separator_goal() {
        let scene = PolygonScene {
            world_bounds: WorldBounds::new(Point2::new(0.0, 0.0), Point2::new(10.0, 10.0)),
            obstacles: vec![Polygon::new(vec![
                Point2::new(4.0, 0.0),
                Point2::new(6.0, 0.0),
                Point2::new(6.0, 10.0),
                Point2::new(4.0, 10.0),
            ])],
        };
        let map = ContinuousMapPreparedGeometryKernelBuilder
            .preprocess(&scene, Point2::new(2.0, 5.0))
            .expect("source should preprocess");

        let result = map
            .query(Point2::new(8.0, 5.0))
            .expect("valid goal endpoint");

        assert!(!result.is_found());
        assert!(result.path().is_none());
        assert_eq!(result.cost(), None);
        assert_eq!(
            ContinuousMapPreparedGeometryKernelBuilder::CANDIDATE_ID,
            "repeated-polygonal-fixed-source/prepared-geometry-kernel"
        );
    }
}