Skip to main content

condor/
solver_portfolio.rs

1//! Root-owned recommendation catalog for Condor's endorsed public solver entrypoints.
2//!
3//! # Facade role
4//!
5//! **Root-owned** on the public facade: no owner-crate dependency. Productization
6//! metadata and capture reports live in the developer bench package; this module
7//! is the stable, dependency-free catalog consumers and docs can rely on.
8//!
9//! # Contract
10//!
11//! First stop for mapping a problem model ([`SolverUseCase`]) to the current
12//! recommended public surface ([`SolverSurface`]). This is a **compile-time
13//! catalog**, not runtime dispatch:
14//!
15//! - [`SolverPortfolio::recommend`] always returns a static catalog row.
16//! - Solvers are still invoked through their own traits and builders
17//!   (`Pathfinder`, `GridReplanner`, `InterpolatedGridReplanner`,
18//!   `PreparedNavmeshBuilder`, …).
19//! - [`SolverRecommendationStatus::UseNow`] vs [`SolverRecommendationStatus::Watch`]
20//!   signals public readiness of the surface, not algorithmic correctness.
21//!
22//! # Examples
23//!
24//! ```
25//! use condor::{SolverPortfolio, SolverUseCase};
26//!
27//! let recommendation = SolverPortfolio::recommend(SolverUseCase::AnyAngleGrid);
28//! assert_eq!(recommendation.solver(), "Anya");
29//! assert_eq!(recommendation.status().slug(), "use-now");
30//! ```
31
32/// Problem-model bucket used by [`SolverPortfolio`] recommendations.
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum SolverUseCase {
35    /// Static unweighted grid search.
36    StaticUnweightedGrid,
37    /// Weighted grid search.
38    WeightedGrid,
39    /// Dynamic grid replanning.
40    DynamicGridReplanning,
41    /// Any-angle grid pathfinding.
42    AnyAngleGrid,
43    /// Exact polygonal shortest-path queries.
44    ExactPolygonalScene,
45    /// Repeated polygonal queries from a fixed source.
46    RepeatedPolygonalFixedSource,
47    /// Exact routing on a prepared navmesh.
48    ExactNavmeshRouting,
49    /// Interpolated dynamic replanning surfaces that are still expanding.
50    InterpolatedDynamicReplanning,
51}
52
53impl SolverUseCase {
54    /// Returns the stable slug used in exported recommendation artifacts.
55    #[must_use]
56    pub const fn slug(self) -> &'static str {
57        match self {
58            Self::StaticUnweightedGrid => "static-unweighted-grid",
59            Self::WeightedGrid => "weighted-grid",
60            Self::DynamicGridReplanning => "dynamic-grid-replanning",
61            Self::AnyAngleGrid => "any-angle-grid",
62            Self::ExactPolygonalScene => "exact-polygonal-scene",
63            Self::RepeatedPolygonalFixedSource => "repeated-polygonal-fixed-source",
64            Self::ExactNavmeshRouting => "exact-navmesh-routing",
65            Self::InterpolatedDynamicReplanning => "interpolated-dynamic-replanning",
66        }
67    }
68}
69
70/// Stability signal for a [`SolverPortfolioRecommendation`].
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
72pub enum SolverRecommendationStatus {
73    /// Endorsed for current public use.
74    UseNow,
75    /// Publicly visible, but still expanding or intentionally watch-only.
76    Watch,
77}
78
79impl SolverRecommendationStatus {
80    /// Returns the stable artifact label for this status.
81    #[must_use]
82    pub const fn slug(self) -> &'static str {
83        match self {
84            Self::UseNow => "use-now",
85            Self::Watch => "watch",
86        }
87    }
88}
89
90/// Named public surface recommended for a given [`SolverUseCase`].
91///
92/// Each variant names a **documented crate-root entrypoint** (type or builder),
93/// not a runtime handle. Pair with [`SolverSurface::integration_surface`] for
94/// the trait or workflow consumers should implement against.
95#[derive(Debug, Clone, Copy, PartialEq, Eq)]
96pub enum SolverSurface {
97    /// Grid search via the public `AStar` surface.
98    AStar,
99    /// Weighted grid search via the public `Dijkstra` surface.
100    Dijkstra,
101    /// Dynamic grid replanning via the public `DStarLite` surface.
102    DStarLite,
103    /// Any-angle grid search via the public `Anya` surface.
104    Anya,
105    /// Exact polygonal search via the public `VisibilityGraph` surface.
106    VisibilityGraph,
107    /// Repeated polygonal queries via the public `ContinuousShortestPathMap` surface.
108    ContinuousShortestPathMap,
109    /// Exact navmesh routing via the public `TRAStarBuilder` surface.
110    TRAStarBuilder,
111    /// Interpolated dynamic replanning via the public `FieldDStar` surface.
112    FieldDStar,
113}
114
115impl SolverSurface {
116    /// Returns the primary public type or builder name shown in docs.
117    #[must_use]
118    pub const fn public_entrypoint(self) -> &'static str {
119        match self {
120            Self::AStar => "AStar",
121            Self::Dijkstra => "Dijkstra",
122            Self::DStarLite => "DStarLite",
123            Self::Anya => "Anya",
124            Self::VisibilityGraph => "VisibilityGraph",
125            Self::ContinuousShortestPathMap => "ContinuousShortestPathMap",
126            Self::TRAStarBuilder => "TRAStarBuilder",
127            Self::FieldDStar => "FieldDStar",
128        }
129    }
130
131    /// Returns the trait or workflow label typically paired with the surface.
132    #[must_use]
133    pub const fn integration_surface(self) -> &'static str {
134        match self {
135            Self::AStar | Self::Dijkstra => "Pathfinder",
136            Self::DStarLite => "GridReplanner",
137            Self::Anya => "AnyAnglePathfinder",
138            Self::VisibilityGraph => "PolygonPathfinder",
139            Self::ContinuousShortestPathMap => "preprocess/query map",
140            Self::TRAStarBuilder => "PreparedNavmeshBuilder",
141            Self::FieldDStar => "InterpolatedGridReplanner",
142        }
143    }
144}
145
146/// One static recommendation row from [`SolverPortfolio::catalog`].
147///
148/// Always paired with exactly one [`SolverUseCase`]; not a runtime dispatch result.
149#[derive(Debug, Clone, Copy, PartialEq, Eq)]
150pub struct SolverPortfolioRecommendation {
151    use_case: SolverUseCase,
152    solver_surface: SolverSurface,
153    status: SolverRecommendationStatus,
154    rationale: &'static str,
155    next_step: &'static str,
156}
157
158impl SolverPortfolioRecommendation {
159    /// Returns the problem-model bucket that produced this recommendation.
160    #[must_use]
161    pub const fn use_case(self) -> SolverUseCase {
162        self.use_case
163    }
164
165    /// Returns the recommended public surface.
166    #[must_use]
167    pub const fn solver_surface(self) -> SolverSurface {
168        self.solver_surface
169    }
170
171    /// Returns whether this recommendation is use-now or watch-only.
172    #[must_use]
173    pub const fn status(self) -> SolverRecommendationStatus {
174        self.status
175    }
176
177    /// Returns the short rationale shown in public portfolio surfaces.
178    #[must_use]
179    pub const fn rationale(self) -> &'static str {
180        self.rationale
181    }
182
183    /// Returns the suggested next user action for this recommendation.
184    #[must_use]
185    pub const fn next_step(self) -> &'static str {
186        self.next_step
187    }
188
189    /// Returns the primary public type or builder name.
190    #[must_use]
191    pub const fn solver(self) -> &'static str {
192        self.solver_surface.public_entrypoint()
193    }
194
195    /// Returns the trait or workflow name typically paired with the solver.
196    #[must_use]
197    pub const fn integration_surface(self) -> &'static str {
198        self.solver_surface.integration_surface()
199    }
200}
201
202const SOLVER_PORTFOLIO_CATALOG: [SolverPortfolioRecommendation; 8] = [
203    SolverPortfolioRecommendation {
204        use_case: SolverUseCase::StaticUnweightedGrid,
205        solver_surface: SolverSurface::AStar,
206        status: SolverRecommendationStatus::UseNow,
207        rationale: "Current README pick for static unweighted grids.",
208        next_step: "Call AStar.search(&grid, request).",
209    },
210    SolverPortfolioRecommendation {
211        use_case: SolverUseCase::WeightedGrid,
212        solver_surface: SolverSurface::Dijkstra,
213        status: SolverRecommendationStatus::UseNow,
214        rationale: "Current README pick for weighted grids.",
215        next_step: "Call Dijkstra.search(&grid, request) on weighted cells.",
216    },
217    SolverPortfolioRecommendation {
218        use_case: SolverUseCase::DynamicGridReplanning,
219        solver_surface: SolverSurface::DStarLite,
220        status: SolverRecommendationStatus::UseNow,
221        rationale: "Current README pick for dynamic grid replanning.",
222        next_step: "Initialize a DStarLite-style replanner and feed grid updates.",
223    },
224    SolverPortfolioRecommendation {
225        use_case: SolverUseCase::AnyAngleGrid,
226        solver_surface: SolverSurface::Anya,
227        status: SolverRecommendationStatus::UseNow,
228        rationale: "Current README pick for any-angle grid paths.",
229        next_step: "Call Anya.search(&grid, request).",
230    },
231    SolverPortfolioRecommendation {
232        use_case: SolverUseCase::ExactPolygonalScene,
233        solver_surface: SolverSurface::VisibilityGraph,
234        status: SolverRecommendationStatus::UseNow,
235        rationale: "Current README pick for exact polygonal scenes.",
236        next_step: "Call VisibilityGraph.search(&scene, request).",
237    },
238    SolverPortfolioRecommendation {
239        use_case: SolverUseCase::RepeatedPolygonalFixedSource,
240        solver_surface: SolverSurface::ContinuousShortestPathMap,
241        status: SolverRecommendationStatus::UseNow,
242        rationale: "Current README pick for repeated polygonal queries from one fixed source.",
243        next_step: "Preprocess once from a fixed source, then query(goal).",
244    },
245    SolverPortfolioRecommendation {
246        use_case: SolverUseCase::ExactNavmeshRouting,
247        solver_surface: SolverSurface::TRAStarBuilder,
248        status: SolverRecommendationStatus::UseNow,
249        rationale: "Current README pick for exact navmesh routing on the prepared TRA* lane.",
250        next_step: "Preprocess the navmesh with TRAStarBuilder, then search(query).",
251    },
252    SolverPortfolioRecommendation {
253        use_case: SolverUseCase::InterpolatedDynamicReplanning,
254        solver_surface: SolverSurface::FieldDStar,
255        status: SolverRecommendationStatus::Watch,
256        rationale: "README marks FieldDStar as the current interpolated replanning surface to watch while that lane is still expanding.",
257        next_step: "Treat FieldDStar as a watch-only lane until that surface settles further.",
258    },
259];
260
261/// Static recommendation facade over Condor's current public problem models.
262///
263/// # Contract
264///
265/// - Recommendations are compile-time catalog rows, not runtime solver dispatch.
266/// - [`Self::recommend`] always returns a row from [`Self::catalog`]; there is
267///   no fallback or dynamic selection.
268/// - `UseNow` vs `Watch` signals public readiness, not algorithmic correctness.
269///
270/// # Examples
271///
272/// ```
273/// use condor::{SolverPortfolio, SolverUseCase};
274///
275/// let recommendation = SolverPortfolio::recommend(SolverUseCase::ExactPolygonalScene);
276/// assert_eq!(recommendation.solver(), "VisibilityGraph");
277/// ```
278#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
279pub struct SolverPortfolio;
280
281impl SolverPortfolio {
282    /// Returns the curated recommendation for one problem model.
283    #[must_use]
284    pub const fn recommend(use_case: SolverUseCase) -> SolverPortfolioRecommendation {
285        match use_case {
286            SolverUseCase::StaticUnweightedGrid => SOLVER_PORTFOLIO_CATALOG[0],
287            SolverUseCase::WeightedGrid => SOLVER_PORTFOLIO_CATALOG[1],
288            SolverUseCase::DynamicGridReplanning => SOLVER_PORTFOLIO_CATALOG[2],
289            SolverUseCase::AnyAngleGrid => SOLVER_PORTFOLIO_CATALOG[3],
290            SolverUseCase::ExactPolygonalScene => SOLVER_PORTFOLIO_CATALOG[4],
291            SolverUseCase::RepeatedPolygonalFixedSource => SOLVER_PORTFOLIO_CATALOG[5],
292            SolverUseCase::ExactNavmeshRouting => SOLVER_PORTFOLIO_CATALOG[6],
293            SolverUseCase::InterpolatedDynamicReplanning => SOLVER_PORTFOLIO_CATALOG[7],
294        }
295    }
296
297    /// Returns the full static recommendation catalog.
298    ///
299    /// # Examples
300    ///
301    /// ```
302    /// use condor::SolverPortfolio;
303    ///
304    /// let catalog = SolverPortfolio::catalog();
305    /// assert!(catalog.len() >= 8);
306    /// ```
307    #[must_use]
308    pub const fn catalog() -> &'static [SolverPortfolioRecommendation] {
309        &SOLVER_PORTFOLIO_CATALOG
310    }
311}