`oxmpl` - sprint-004
Sprint 004 — Motion validator and SpaceInformation (provisional)
Provisional scaffold for release 0.9.0 (E4), written 2026-10-08 so the release isn't lost. Nothing here is committed: the stories carry no
oxmpl/sprint/004tag until planning confirms them against the owner's stated availability. Dates are re-derived at the previous sprint's retro if it slipped.
- Dates (provisional): Thu 2026-11-19 → Wed 2026-12-09 (three weeks)
- Epic: E4 — Validation (0.9.0)
- Goal (provisional): One pluggable motion validator behind
SpaceInformation; all ~147setupcall sites migrated — main green, nothing released.
Provisional commitment
- S4.4 — Motion validator trait and discrete validator (size-M; after E3, S4.2, S4.3)
- S4.5 — SpaceInformation migration (size-L; after E3, S4.4)
Order: S4.4 → S4.5. S4.5 is the big break: no feature work mixed in.
Entry criteria: S4.2 and S4.3 done. Pull-forward candidates if capacity allows: S4.6 (needs S4.2), S4.8 (needs S4.3).
Blind pickup: how to (re)generate the tickets
Tickets for these stories already exist in TaskNotes (created 2026-10-08). Once a ticket exists, its TaskNotes details are authoritative and this page is only the regeneration spec. If a ticket is missing or was lost, recreate it from the spec below:
tasknotes_create_taskwith the title, projects, tags and priority shown. MCP quirks: create drops the description and defaultsscheduledto today, so follow withtasknotes_update_taskto setdetails(the body below) andscheduled: null.- Wire
blockedByby editing the task file's YAML frontmatter directly (no MCP tool exposes it). A story lists all upstream dependencies, including done ones. - Verify with
tasknotes_get_task:isBlocked: trueon the story while its upstreams are open, andisBlockingon each upstream. After any laterupdate_task, re-check thatblockedBysurvived. - If the epic E4 is missing, recreate it first from the spec in
oxmpl- sprint-003 § Epic E4. - At this sprint's planning (owner confirms availability): add the
oxmpl/sprint/NNNtag, setscheduled/dueto the sprint dates, removesprint-status: provisionalfrom this page's frontmatter, and rewrite the "Provisional commitment" heading as "Commitment".
Conventions: oxmpl - planning. Repo context: ADR docs/planning/adr/0003-motion-validation.md (decisions), CONTEXT.md (glossary). Line references are as of origin/main c558556 (2026-10-08), before E3. E3 renames things (rand Rng→RngExt, PyO3 with_gil→attach, edition 2024), so re-grep before trusting a line number.
Story specs
S4.4 — Motion validator trait and discrete validator (size-M)
# TaskNotes task — file: task_notes/tasks/S4.4 — Motion validator trait and discrete validator.md
title: "S4.4 — Motion validator trait and discrete validator"
status: open
priority: normal
projects: ["[[E4 Validation — 0.9.0]]"]
tags: [oxmpl, oxmpl/epic, oxmpl/story/size-M] # add oxmpl/sprint/004 only when committed at planning
scheduled: null # set to sprint start when committed
due: null # set to sprint end when committed
blockedBy: # wire in frontmatter by hand (no MCP tool exposes it)
- uid: "[[E3 Toolchain refresh — 0.8.0]]"
reltype: FINISHTOSTART
- uid: "[[S4.2 — Fallible, thread-safe validation core]]"
reltype: FINISHTOSTART
- uid: "[[S4.3 — Space resolution valid segment count]]"
reltype: FINISHTOSTART
details body:
Intent
Motion validation is today four private check_motion copies with a hidden ×0.1 oversampling factor (each step is longest_valid_segment_length × 0.1). Replace them with a public, pluggable MotionValidator and a default DiscreteMotionValidator, modelled on OMPL. Planners build the default internally in this story; how users attach their own validator is S4.5. Supersedes the rest of S2.2 (cancelled).
Decisions (ADR-0003)
- Glossary (
CONTEXT.md§ Validity): a motion is the transition traced by the space'sinterpolate; motion validation decides whether every state along it is valid, assuming the start is valid; resolution is owned by the space; checking strategy is owned by the validator, which may ignore the space's resolution. - Trait:
pub trait MotionValidator<S: State>: Send + Sync { fn check_motion(&self, s1: &S, s2: &S) -> Result<bool, UserError>; }. Object-safe (used asArc<dyn MotionValidator<S>>). - The trait docs state from day one that s1 is assumed valid (needed so a later last-valid method can default soundly).
- Exactly one required method. OMPL's second overload (last valid state + fraction) is deferred; when a planner needs it, it arrives as a provided method
check_motion_last_valid(&self, s1, s2) -> Result<Result<(), f64>, UserError>(exact shape decided then) defaulting toErr(0.0)on failure. Do not add it now. - No valid/invalid counters in the trait (a decorating validator can count later).
DiscreteMotionValidatorholds the space and the state validity checker; uses the space'svalid_segment_count(S4.3); checks s2 first, then interior states in bisection (BFS-midpoint) order, as OMPL'sDiscreteMotionValidator::checkMotion(s1, s2). Linear order is the cut-fallback if bisection slips.- The default validator is always the discrete one; per-space defaults (e.g. Dubins) may come later as a provided
StateSpacemethod.
Acceptance criteria
Tasks
Risks
- Behaviour drift: tests with tight geometry may start passing or failing differently because effective resolution changes (2× coarser for single spaces, finer for compound). S4.1's budgets absorb the PRM side; check RRT-family tests individually.
S4.5 — SpaceInformation migration (size-L)
# TaskNotes task — file: task_notes/tasks/S4.5 — SpaceInformation migration.md
title: "S4.5 — SpaceInformation migration"
status: open
priority: normal
projects: ["[[E4 Validation — 0.9.0]]"]
tags: [oxmpl, oxmpl/epic, oxmpl/story/size-L] # add oxmpl/sprint/004 only when committed at planning
scheduled: null # set to sprint start when committed
due: null # set to sprint end when committed
blockedBy: # wire in frontmatter by hand (no MCP tool exposes it)
- uid: "[[E3 Toolchain refresh — 0.8.0]]"
reltype: FINISHTOSTART
- uid: "[[S4.4 — Motion validator trait and discrete validator]]"
reltype: FINISHTOSTART
details body:
Intent
Introduce SpaceInformation — the state space plus the state validity checker plus the motion validator — as the single source of validity, so planners and the future path simplifier (0.10.0) can never judge validity differently. This is 0.9.0's big breaking change; do it alone, with no feature work mixed in.
Decisions (ADR-0003; grilling Q1/Q8/Q9/Q9′/Q10/Q11/Q12)
- Name
SpaceInformation, as in OMPL (glossary:CONTEXT.md§ Validity, "Space information"). SpaceInformation::new(space: Arc<SP>, checker: Arc<dyn StateValidityChecker<S>>)— the checker is required (OMPL silently installs an all-valid checker; we don't). The discrete motion validator is installed by default.set_motion_validator(&mut self, mv: Arc<dyn MotionValidator<S>>)keeps OMPL's name but needs exclusive access: configure first, then share asArc<SpaceInformation<..>>. Once shared it is frozen — this prevents a PRM roadmap built under one validator being queried under another (a silent hazard in OMPL's mutable model) and keeps locks off the hottest path.ProblemDefinitionholdsArc<SpaceInformation<..>>instead of itspub spacefield. Exactly one path to the space.Planner::setup(&mut self, problem_def)— the checker argument goes. Bindings dropsetupentirely: their planner constructors already receive the problem definition.- Bindings: Python
SpaceInformation(space, is_valid_fn, motion_validator=None); JSnew SpaceInformation(space, isValidFn)plussetMotionValidator. JS'sStateValidityCheckerwrapper class is removed. The checker stays a plain function in both bindings. - Binding setters use
Arc::get_mut; if aProblemDefinitionalready holds the SpaceInformation, raise "SpaceInformation is already used by a ProblemDefinition; configure it first" (typed asConfigurationErroronce S4.6 lands). - Binding spaces are shared, not copied: today
ProblemDefinitionconstructors snapshot the space (lock().unwrap().clone()— py 3 sites, js 6 sites), so configuring a space afterwards is a silent no-op. After this story, mutating a space already used by a SpaceInformation raises the same "already in use" error. - Out of scope (additive later): reusing a PRM roadmap across problem definitions sharing one SpaceInformation ptr_eq.
Acceptance criteria
Tasks
Risks
- Scale: mechanical but large; keep the PR to the migration only. Consider a codemod (sed/ast-grep) for the test suites.
- Python/JS API shape is public and permanent until 0.10.0 — get names right in this PR.
Progress log
- 2026-10-08 — Scaffolded as provisional during sprint-002 planning (owner request); expanded the same day into a full regeneration spec (owner request: "enough that a blind pickup can generate all the tickets"). Not yet planned or committed.
Review & retro
At sprint end: done / carried over / dropped, what was over- or underestimated, and at most one process change for the next sprint.