condor-for-games 0.4.0

Rust pathfinding library for grids, polygonal scenes, navmeshes, and replanning.
Documentation
//! Root-owned recommendation catalog for Condor's endorsed public solver entrypoints.
//!
//! # Facade role
//!
//! **Root-owned** on the public facade: no owner-crate dependency. Productization
//! metadata and capture reports live in the developer bench package; this module
//! is the stable, dependency-free catalog consumers and docs can rely on.
//!
//! # Contract
//!
//! First stop for mapping a problem model ([`SolverUseCase`]) to the current
//! recommended public surface ([`SolverSurface`]). This is a **compile-time
//! catalog**, not runtime dispatch:
//!
//! - [`SolverPortfolio::recommend`] always returns a static catalog row.
//! - Solvers are still invoked through their own traits and builders
//!   (`Pathfinder`, `GridReplanner`, `InterpolatedGridReplanner`,
//!   `PreparedNavmeshBuilder`, …).
//! - [`SolverRecommendationStatus::UseNow`] vs [`SolverRecommendationStatus::Watch`]
//!   signals public readiness of the surface, not algorithmic correctness.
//!
//! # Examples
//!
//! ```
//! use condor::{SolverPortfolio, SolverUseCase};
//!
//! let recommendation = SolverPortfolio::recommend(SolverUseCase::AnyAngleGrid);
//! assert_eq!(recommendation.solver(), "Anya");
//! assert_eq!(recommendation.status().slug(), "use-now");
//! ```

/// Problem-model bucket used by [`SolverPortfolio`] recommendations.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SolverUseCase {
    /// Static unweighted grid search.
    StaticUnweightedGrid,
    /// Weighted grid search.
    WeightedGrid,
    /// Dynamic grid replanning.
    DynamicGridReplanning,
    /// Any-angle grid pathfinding.
    AnyAngleGrid,
    /// Exact polygonal shortest-path queries.
    ExactPolygonalScene,
    /// Repeated polygonal queries from a fixed source.
    RepeatedPolygonalFixedSource,
    /// Exact routing on a prepared navmesh.
    ExactNavmeshRouting,
    /// Interpolated dynamic replanning surfaces that are still expanding.
    InterpolatedDynamicReplanning,
}

impl SolverUseCase {
    /// Returns the stable slug used in exported recommendation artifacts.
    #[must_use]
    pub const fn slug(self) -> &'static str {
        match self {
            Self::StaticUnweightedGrid => "static-unweighted-grid",
            Self::WeightedGrid => "weighted-grid",
            Self::DynamicGridReplanning => "dynamic-grid-replanning",
            Self::AnyAngleGrid => "any-angle-grid",
            Self::ExactPolygonalScene => "exact-polygonal-scene",
            Self::RepeatedPolygonalFixedSource => "repeated-polygonal-fixed-source",
            Self::ExactNavmeshRouting => "exact-navmesh-routing",
            Self::InterpolatedDynamicReplanning => "interpolated-dynamic-replanning",
        }
    }
}

/// Stability signal for a [`SolverPortfolioRecommendation`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SolverRecommendationStatus {
    /// Endorsed for current public use.
    UseNow,
    /// Publicly visible, but still expanding or intentionally watch-only.
    Watch,
}

impl SolverRecommendationStatus {
    /// Returns the stable artifact label for this status.
    #[must_use]
    pub const fn slug(self) -> &'static str {
        match self {
            Self::UseNow => "use-now",
            Self::Watch => "watch",
        }
    }
}

/// Named public surface recommended for a given [`SolverUseCase`].
///
/// Each variant names a **documented crate-root entrypoint** (type or builder),
/// not a runtime handle. Pair with [`SolverSurface::integration_surface`] for
/// the trait or workflow consumers should implement against.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SolverSurface {
    /// Grid search via the public `AStar` surface.
    AStar,
    /// Weighted grid search via the public `Dijkstra` surface.
    Dijkstra,
    /// Dynamic grid replanning via the public `DStarLite` surface.
    DStarLite,
    /// Any-angle grid search via the public `Anya` surface.
    Anya,
    /// Exact polygonal search via the public `VisibilityGraph` surface.
    VisibilityGraph,
    /// Repeated polygonal queries via the public `ContinuousShortestPathMap` surface.
    ContinuousShortestPathMap,
    /// Exact navmesh routing via the public `TRAStarBuilder` surface.
    TRAStarBuilder,
    /// Interpolated dynamic replanning via the public `FieldDStar` surface.
    FieldDStar,
}

impl SolverSurface {
    /// Returns the primary public type or builder name shown in docs.
    #[must_use]
    pub const fn public_entrypoint(self) -> &'static str {
        match self {
            Self::AStar => "AStar",
            Self::Dijkstra => "Dijkstra",
            Self::DStarLite => "DStarLite",
            Self::Anya => "Anya",
            Self::VisibilityGraph => "VisibilityGraph",
            Self::ContinuousShortestPathMap => "ContinuousShortestPathMap",
            Self::TRAStarBuilder => "TRAStarBuilder",
            Self::FieldDStar => "FieldDStar",
        }
    }

    /// Returns the trait or workflow label typically paired with the surface.
    #[must_use]
    pub const fn integration_surface(self) -> &'static str {
        match self {
            Self::AStar | Self::Dijkstra => "Pathfinder",
            Self::DStarLite => "GridReplanner",
            Self::Anya => "AnyAnglePathfinder",
            Self::VisibilityGraph => "PolygonPathfinder",
            Self::ContinuousShortestPathMap => "preprocess/query map",
            Self::TRAStarBuilder => "PreparedNavmeshBuilder",
            Self::FieldDStar => "InterpolatedGridReplanner",
        }
    }
}

/// One static recommendation row from [`SolverPortfolio::catalog`].
///
/// Always paired with exactly one [`SolverUseCase`]; not a runtime dispatch result.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SolverPortfolioRecommendation {
    use_case: SolverUseCase,
    solver_surface: SolverSurface,
    status: SolverRecommendationStatus,
    rationale: &'static str,
    next_step: &'static str,
}

impl SolverPortfolioRecommendation {
    /// Returns the problem-model bucket that produced this recommendation.
    #[must_use]
    pub const fn use_case(self) -> SolverUseCase {
        self.use_case
    }

    /// Returns the recommended public surface.
    #[must_use]
    pub const fn solver_surface(self) -> SolverSurface {
        self.solver_surface
    }

    /// Returns whether this recommendation is use-now or watch-only.
    #[must_use]
    pub const fn status(self) -> SolverRecommendationStatus {
        self.status
    }

    /// Returns the short rationale shown in public portfolio surfaces.
    #[must_use]
    pub const fn rationale(self) -> &'static str {
        self.rationale
    }

    /// Returns the suggested next user action for this recommendation.
    #[must_use]
    pub const fn next_step(self) -> &'static str {
        self.next_step
    }

    /// Returns the primary public type or builder name.
    #[must_use]
    pub const fn solver(self) -> &'static str {
        self.solver_surface.public_entrypoint()
    }

    /// Returns the trait or workflow name typically paired with the solver.
    #[must_use]
    pub const fn integration_surface(self) -> &'static str {
        self.solver_surface.integration_surface()
    }
}

const SOLVER_PORTFOLIO_CATALOG: [SolverPortfolioRecommendation; 8] = [
    SolverPortfolioRecommendation {
        use_case: SolverUseCase::StaticUnweightedGrid,
        solver_surface: SolverSurface::AStar,
        status: SolverRecommendationStatus::UseNow,
        rationale: "Current README pick for static unweighted grids.",
        next_step: "Call AStar.search(&grid, request).",
    },
    SolverPortfolioRecommendation {
        use_case: SolverUseCase::WeightedGrid,
        solver_surface: SolverSurface::Dijkstra,
        status: SolverRecommendationStatus::UseNow,
        rationale: "Current README pick for weighted grids.",
        next_step: "Call Dijkstra.search(&grid, request) on weighted cells.",
    },
    SolverPortfolioRecommendation {
        use_case: SolverUseCase::DynamicGridReplanning,
        solver_surface: SolverSurface::DStarLite,
        status: SolverRecommendationStatus::UseNow,
        rationale: "Current README pick for dynamic grid replanning.",
        next_step: "Initialize a DStarLite-style replanner and feed grid updates.",
    },
    SolverPortfolioRecommendation {
        use_case: SolverUseCase::AnyAngleGrid,
        solver_surface: SolverSurface::Anya,
        status: SolverRecommendationStatus::UseNow,
        rationale: "Current README pick for any-angle grid paths.",
        next_step: "Call Anya.search(&grid, request).",
    },
    SolverPortfolioRecommendation {
        use_case: SolverUseCase::ExactPolygonalScene,
        solver_surface: SolverSurface::VisibilityGraph,
        status: SolverRecommendationStatus::UseNow,
        rationale: "Current README pick for exact polygonal scenes.",
        next_step: "Call VisibilityGraph.search(&scene, request).",
    },
    SolverPortfolioRecommendation {
        use_case: SolverUseCase::RepeatedPolygonalFixedSource,
        solver_surface: SolverSurface::ContinuousShortestPathMap,
        status: SolverRecommendationStatus::UseNow,
        rationale: "Current README pick for repeated polygonal queries from one fixed source.",
        next_step: "Preprocess once from a fixed source, then query(goal).",
    },
    SolverPortfolioRecommendation {
        use_case: SolverUseCase::ExactNavmeshRouting,
        solver_surface: SolverSurface::TRAStarBuilder,
        status: SolverRecommendationStatus::UseNow,
        rationale: "Current README pick for exact navmesh routing on the prepared TRA* lane.",
        next_step: "Preprocess the navmesh with TRAStarBuilder, then search(query).",
    },
    SolverPortfolioRecommendation {
        use_case: SolverUseCase::InterpolatedDynamicReplanning,
        solver_surface: SolverSurface::FieldDStar,
        status: SolverRecommendationStatus::Watch,
        rationale: "README marks FieldDStar as the current interpolated replanning surface to watch while that lane is still expanding.",
        next_step: "Treat FieldDStar as a watch-only lane until that surface settles further.",
    },
];

/// Static recommendation facade over Condor's current public problem models.
///
/// # Contract
///
/// - Recommendations are compile-time catalog rows, not runtime solver dispatch.
/// - [`Self::recommend`] always returns a row from [`Self::catalog`]; there is
///   no fallback or dynamic selection.
/// - `UseNow` vs `Watch` signals public readiness, not algorithmic correctness.
///
/// # Examples
///
/// ```
/// use condor::{SolverPortfolio, SolverUseCase};
///
/// let recommendation = SolverPortfolio::recommend(SolverUseCase::ExactPolygonalScene);
/// assert_eq!(recommendation.solver(), "VisibilityGraph");
/// ```
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct SolverPortfolio;

impl SolverPortfolio {
    /// Returns the curated recommendation for one problem model.
    #[must_use]
    pub const fn recommend(use_case: SolverUseCase) -> SolverPortfolioRecommendation {
        match use_case {
            SolverUseCase::StaticUnweightedGrid => SOLVER_PORTFOLIO_CATALOG[0],
            SolverUseCase::WeightedGrid => SOLVER_PORTFOLIO_CATALOG[1],
            SolverUseCase::DynamicGridReplanning => SOLVER_PORTFOLIO_CATALOG[2],
            SolverUseCase::AnyAngleGrid => SOLVER_PORTFOLIO_CATALOG[3],
            SolverUseCase::ExactPolygonalScene => SOLVER_PORTFOLIO_CATALOG[4],
            SolverUseCase::RepeatedPolygonalFixedSource => SOLVER_PORTFOLIO_CATALOG[5],
            SolverUseCase::ExactNavmeshRouting => SOLVER_PORTFOLIO_CATALOG[6],
            SolverUseCase::InterpolatedDynamicReplanning => SOLVER_PORTFOLIO_CATALOG[7],
        }
    }

    /// Returns the full static recommendation catalog.
    ///
    /// # Examples
    ///
    /// ```
    /// use condor::SolverPortfolio;
    ///
    /// let catalog = SolverPortfolio::catalog();
    /// assert!(catalog.len() >= 8);
    /// ```
    #[must_use]
    pub const fn catalog() -> &'static [SolverPortfolioRecommendation] {
        &SOLVER_PORTFOLIO_CATALOG
    }
}