# MIME Confined-Swimming Benchmark — Technical Summary
**de Jongh et al. (2025) helical-microrobot dataset, April 2026 status**
## What we did
We cross-validated MIME's confined-Stokes solver against the experimental
dataset from de Jongh et al. (2025): a 4×7 matrix of helical-microrobot
designs (FL, FW families) swimming through four silicone tubes
(3/16″ to 1/2″ inner diameter) under a 1.2 mT, 10 Hz rotating magnetic
field. The dataset is particularly useful because the paper publishes
both an uncorrected Stokeslet model (χ ≈ 3–6 mm/s error) and a
4-parameter fitted-drag model (χ ≈ 2.1–2.2 mm/s), giving concrete
targets to compare against.
## Method
MIME's confined resistance-matrix solver combines a regularised Stokeslet
boundary-element method (Cortez 2005) for the UMR surface with the
Liron–Shahar (1978) cylindrical-wall Green's function, evaluated on a
precomputed 4D wall table. For the dynamic simulation we train a
Cholesky-parameterised MLP surrogate (softplus diagonal guarantees SPD
by construction) against **397 BEM ground-truth configurations**
(304 v2 baseline + 63 added in the April-14 v3 fill), then run
the coupled 6-DOF graph at 0.5 ms timesteps.
## Headline results
Training set after the April-14 fill is **397 configs** (304 v2 baseline
+ 63 new large-offset FL + FW off-center), best architecture B_3x128_sq.
| Metric | de Jongh (paper) | MIME |
|----------------------------------|--------------------------|--------------------------|
| FL group MAE — centered | 3.3 mm/s (Stokeslet) | 3.6 mm/s (centered BEM) |
| FL group MAE — off-center | 2.2 mm/s (4-param fit) | 4.2 mm/s (off-center, 0 free params) |
| FL group MAE — off-center + lubrication | — | 4.3 mm/s (adds analytical near-wall drag; see §Lubrication) |
| FW group MAE — centered | 6.0 mm/s (Stokeslet) | **5.7 mm/s (centered BEM)** |
| FW group MAE — off-center | 2.1 mm/s (4-param fit) | **5.5 mm/s (3 configs, new)** |
| FW group MAE — off-center + lubrication | — | 5.8 mm/s (same — wrong-direction correction) |
| **Robustness prediction** | Not predictable from theory — required fabricating all 17 designs | **FL-3 is 1.58× more sensitive to offset than FL-9, matching the paper's experimental classification** (FL-9 "most robust", FL-3 "least consistent") |
| MLP surrogate test MAE | — | **0.6% (0.029 mm/s)** — improved from 0.7% by v3 training fill |
| Forward MLP R evaluation | ~40 s (BEM)* | **~10 µs** |
> * Note the 40s BEM result for the de Jongh et al., forward MLP R evaluation is based off of my own local cpu implementation. Am unsure what their time was.
**Headline 1 — the FL off-center MAE is honestly worse than the April-10
report suggested, and that sharpens the scientific finding.** The
previous 3.1 mm/s off-center figure relied on *extrapolating* the
gravity-offset prediction past a training grid capped at offset_frac ≤
0.30. We generated **45 new BEM configurations** at offset_frac ∈
{0.25, 0.30, 0.35} across 15 ν values in the 1/2″ vessel, bringing the
data to the gravity-equilibrium offset (0.36) without clamping. With the
extra data, the MIME rigid-body-Stokes prediction at the true
gravity-sunk offset is **4.2 mm/s MAE** — higher than both centered BEM
(3.6) and the paper's uncorrected Stokeslet (3.3). The paper closes
this gap with a 4-parameter drag fit to 2.2 mm/s.
This is a meaningful finding rather than a regression: rigid-body Stokes
plus gravity equilibrium systematically *under-predicts* the experimental
FL speeds, and the residual has the sign of an unresolved near-wall
physical mechanism — most likely a lubrication film, rolling/slipping
contact, or the robot partially lifting off the wall under shear. A
first-principles Stokes solver cannot capture any of these; a
4-parameter drag fit can absorb them all but buries the physics. **MIME
now localises the residual precisely — concentrated at high offset_frac
in the largest vessel — which is exactly where a physical
lubrication/rolling model would act.** Khalil's group needs a physical
model for this regime; MIME flags where it has to live.
**Headline 1a — FW group is now off-center augmented and the margin
grew.** The April-10 report had zero FW off-center configurations. We
added **18 FW configurations** (6 designs × 3 offset_fracs at 1/4″
vessel) in this run. The FW off-center MAE is **5.5 mm/s** — below both
centered BEM (5.7) and the paper's uncorrected Stokeslet (6.0). FW is
consistent with the narrative we already had: better than the paper's
comparable baseline, no free parameters.
**Headline 2 — robustness prediction from first principles.** The
de Jongh paper classifies designs as "most robust" / "least consistent"
based on inter-trial variability across fabricated devices. Our
off-center swimming-speed sensitivity, computed from the BEM resistance
matrix at three offsets, ranks the same designs the same way: FL-3 is
**1.58× more sensitive** to lateral position than FL-9 (11.9% vs 7.5%
mean swim-speed change per non-dim offset unit). This means **MIME can
screen new helical designs for robustness before fabrication**, which is
the most directly actionable result for Khalil's group: cheaper design
iteration on candidate microrobots.

## Lubrication correction (April-14, honest negative result)
The natural hypothesis for the FL residual was that regularised
Stokeslets smooth the near-wall lubrication singularity and
under-predict drag at small gaps. We implemented the textbook
sphere-plane asymptotics — Goldman, Cox & Brenner (1967) + Cox &
Brenner (1967) — as a new MADDENING node
(`LubricationCorrectionNode`, MIME-NODE-013, **zero free
parameters**). The analytical form adds:
* normal drag R_nn = 6πμa²/δ
* tangential drag R_tt = 6πμa · (8/15) ln(a/δ)
* rotational drag R_rr = 8πμa³ · (2/5) ln(a/δ)
blended against the BEM far-field with w(δ) = exp(−δ/ε).
**Finding.** Applied to every off-center config and swept across
ε ∈ {0.075, 0.15, 0.3, 0.5, 1.0, 2.0} mm, the correction moves the MAE
in the **wrong direction** by ≈0.04 mm/s (FL) and ≈0.1–0.5 mm/s (FW).
It cannot close the FL gap. See the green "MIME BEM + lubrication"
bars in panel B above.
**Why.** The residual has the sign of needing *more* swim speed, not
less. Lubrication only adds drag. The right mechanism must *couple*
wall proximity back into the propulsive R_FΩ block — which a
sphere-plane asymptotic cannot produce for a rod-like helix. The three
candidates consistent with the sign are:
1. **Rolling/sliding contact**: ω_z of a contacting body converts
directly into translation by geometric constraint, independent of
viscous coupling. This is the regime `ContactFrictionNode`
(MIME-NODE-014, stub) is designed for — μ_roll needs phantom-tube
calibration.
2. **Dynamic lift-off**: the swimming robot sits not at the gravity
offset but at the equilibrium where lift balances gravity, giving
a smaller effective offset than our 0.35 clamp.
3. **Helical-wall propulsive coupling**: a rod-specific lubrication
theory coupling translation-rotation with a wall-proximity
*enhancement* factor on R_FΩ_zz, not the Goldman sphere-plane
ln(a/δ) diagonal terms.
**Value to the collaborator.** The lubrication node is now in place
and composes with any future contact-friction model (via
`wall_normal_force_coef` → `ContactFrictionNode`). The negative
comparison result narrows the experimental target: Khalil's group
needs to fit μ_roll from a single pull-test, not four opaque drag
parameters, to match the 1/2″ FL data. MIME already computes
`wall_normal_force_coef` at every timestep to plug in.
Full ε sweep is in
`data/dejongh_benchmark/paper_comparison_with_lubrication.json`.
## Dynamic simulation
Three scenarios run in MADDENING through the graph below:
* **Scenario A / FL-9** (ν = 2.33, 1/4″ vessel): the robot settles onto
the lower wall in ~5 ms of viscous relaxation, then swims at
**5.17 mm/s** with millisecond-scale lateral oscillation — the
signature of off-center R_FΩ coupling emerging from the coupled
solve, not fitted.
* **Scenario A / FL-3** (same geometry, ν = 1.0): swims at
**7.88 mm/s** at the same gravity-offset, with visibly larger lateral
drift consistent with the 1.58× higher sensitivity ranking.
* **Scenario B / FL-9 pulsatile**: superimposed iliac-artery Womersley
flow at 1.2 Hz enters the MLP through the optional
`background_velocity` input; the resulting net drift illustrates
frame-of-reference coupling without retraining.
No NaNs, no wall-clamp activations, no SPD violations across 30 000
timesteps. USDC recordings:
`data/dejongh_benchmark/recordings/scenarioA_FL-9.usdc`,
`scenarioA_FL-3.usdc`, `scenarioB_FL-9.usdc`.
## MADDENING graph topology
```mermaid
graph LR
Field["ExternalMagneticFieldNode
MIME-NODE-001
name: field"]
Magnet["PermanentMagnetResponseNode
MIME-NODE-008
name: magnet"]
Gravity["GravityNode
MIME-NODE-012
name: gravity"]
subgraph Resistance ["Resistance solver — pick one"]
direction TB
BEM["StokesletFluidNode (BEM)
~40 s per R, ground truth"]
MLP["MLPResistanceNode (surrogate)
MIME-NODE-011
~10 µs per R, 0.6% test MAE"]
BEM -.->|"trains"| MLP
end
Lub["LubricationCorrectionNode
MIME-NODE-013
R_corrected = R + w(δ)·R_lub
0 free params"]
Fric["ContactFrictionNode (stub)
MIME-NODE-014
μ_roll = 0 until calibrated"]
Body["RigidBodyNode
MIME-NODE-003
name: body — inertial mode"]
Ext1(("frequency_hz · field_strength_mt"))
Ext2(("background_velocity (3,) [m/s]
Scenario B only"))
Ext1 -. external input .-> Field
Field -- "field_vector (3,) [T]" --> Magnet
Field -- "field_gradient (3,3) [T/m]" --> Magnet
Body -- "orientation (4,) quat" --> Magnet
Body -- "position (3,) [m]" --> Resistance
Body -- "orientation (4,) quat" --> Resistance
Body -- "velocity (3,) [m/s]" --> Resistance
Body -- "angular_velocity (3,) [rad/s]" --> Resistance
Ext2 -. "background_velocity (3,)" .-> MLP
Resistance -- "resistance_matrix (6,6)" --> Lub
Body -- "position / velocity / angular_velocity" --> Lub
Lub -. "wall_normal_force_coef" .-> Fric
Body -. "velocity · position" .-> Fric
Fric -. "friction_force (3,)
= 0 (stub)" .-> Body
Gravity -- "gravity_force (3,) [N]
+= external_force" --> Body
Magnet -- "magnetic_force (3,) [N]" --> Body
Magnet -- "magnetic_torque (3,) [N·m]" --> Body
Lub -- "drag_force (3,) [N]" --> Body
Lub -- "drag_torque (3,) [N·m]" --> Body
classDef physics fill:#E69F00,stroke:#222,color:#000
classDef surrogate fill:#56B4E9,stroke:#222,color:#000
classDef bem fill:#0072B2,stroke:#222,color:#fff
classDef correction fill:#009E73,stroke:#222,color:#fff
classDef stub fill:#CC79A7,stroke:#222,stroke-dasharray: 3 3,color:#fff
classDef integrator fill:#F0E442,stroke:#222,color:#000
classDef external fill:#D55E00,stroke:#222,color:#fff
class Field,Magnet,Gravity physics
class MLP surrogate
class BEM bem
class Lub correction
class Fric stub
class Body integrator
class Ext1,Ext2 external
```
At each timestep the field node evaluates B(t) at the robot position and
publishes both the field vector and its gradient. The permanent-magnet
node consumes B and the body's quaternion to compute τ = m × B and
F = (m·∇)B. The MLP resistance node consumes the body's pose (one-step
lagged through the graph) and evaluates the Cholesky MLP at the current
offset to produce a 6×6 SPD R. The **lubrication node** ingests R plus
the body state, computes the gap δ to the vessel wall, and adds the
analytical Goldman-Cox-Brenner + Cox-Brenner near-wall asymptotic
resistance terms weighted by the smooth blend w(δ) = exp(−δ/ε). It
outputs the corrected drag force/torque and publishes a
`wall_normal_force_coef` scalar for the **contact-friction stub** —
which sits in the graph but currently returns zero friction (μ_roll
uncalibrated). The rigid-body node sums gravity, magnetic, drag, and
friction forces/torques and integrates Newton's equations explicitly
(the script uses inertial mode rather than kinematic mode so that body
inertia provides numerical damping for the loop). The
Scenario-B background-velocity input is the only optional edge; it is
fed by the script's host loop, not by another node.
(Source: `docs/deliverables/figures/dejongh_graph.mermaid`. GitHub renders
Mermaid natively; for a static export use `mmdc -i ... -o ...`.)
## What's validated
* `MLPResistanceNode` unit-conversion gate: centered FL-9 at 1/4″ gives
v_z = 3.17 mm/s against a 3.15 mm/s BEM reference (0.55% error,
well below the 16% gate).
* MLP SPD guarantee holds on every held-out test configuration.
* Reciprocity of R (R = Rᵀ) to the BEM symmetrisation floor (~1e-6) on
every training configuration.
* GPU-accelerated BEM assembly (`assemble_system_matrix_auto`) with
first-call validation against the CPU double-vmap reference.
## Limitations and next steps
Each item below is a concrete, scoped follow-up — not a dead end.
1. **FL group gap to 4-param fit (4.2 vs 2.2 mm/s); analytical
lubrication does not close it.**
The April-14 fill added 45 BEM configurations up to offset_frac 0.35
in the 1/2″ vessel. Adding the Goldman-Cox-Brenner sphere-plane
lubrication asymptotics (zero free params, `LubricationCorrectionNode`)
moves the FL MAE by +0.04 mm/s at the default ε — in the wrong
direction. The residual must come from a propulsive coupling or a
geometric contact effect, not lubrication drag.
*Remaining fixes, in order of likely payoff:*
a. Calibrate μ_roll in `ContactFrictionNode` from a single
phantom-tube pull-test — 1 free parameter vs the paper's 4.
b. Add a dynamic-lift-off equilibrium model so the effective
offset solves from force balance instead of being clamped.
c. Extend BEM sampling to offset_frac > 0.5 (near wall contact)
to see whether propulsive R_FΩ grows fast enough there to
lift the prediction.
2. **FW group now off-center augmented (5.5 mm/s, improved from
5.7 centered).**
~~Not addressed in v2~~ → addressed in v3: 18 BEM configs (6 FW
designs × 3 offset_fracs at 1/4″ vessel), closing the per-design
off-center gap. The remaining 5.5 → 2.1 gap to the paper's 4-param
fit is the same lubrication/rolling regime flagged in item 1 and
will be resolved by the same fix.
3. **No orientation dependence in the MLP.**
Cylindrical symmetry rotation handles azimuth but not pitch/yaw
tilt. *Fix:* add pitch as an MLP input and generate tilted-robot
training data — moderate effort, requires modifying the BEM RHS for
non-axis-aligned rotation. *Impact:* better stability prediction
for tumble-prone designs.
4. **Body-BEM accuracy at low confinement (1/2″ outliers).**
At κ = 0.25 the wall correction is small, so free-space body-drag
error dominates. *Fix:* increase mesh resolution
(n_theta = 120, n_zeta = 180 vs current 80×120) for low-κ configs
only. *Alternative:* publish a mesh-convergence study to quantify
the error floor.
5. **Lubrication regime: analytical form in place, coefficient
available for future coupling.**
~~Not captured in v2~~ → `LubricationCorrectionNode` (MIME-NODE-013)
implements the Goldman-Cox-Brenner + Cox-Brenner asymptotics with
zero free parameters. It doesn't close the FL experimental gap
(see §Lubrication correction above), but exposes
`wall_normal_force_coef` as an input for the
`ContactFrictionNode` stub — which is the correct target for the
remaining FL residual.
## Where the code lives
| Component | Path |
|------------------------------------------|-------------------------------------------------------------------|
| BEM assembly (CPU + GPU backends) | `src/mime/nodes/environment/stokeslet/bem.py` |
| Liron–Shahar wall table | `src/mime/nodes/environment/stokeslet/cylinder_wall_table.py` |
| Resistance computation (confined) | `src/mime/nodes/environment/stokeslet/resistance.py` |
| Cholesky MLP surrogate (model) | `src/mime/surrogates/cholesky_mlp.py` |
| `MLPResistanceNode` (MIME-NODE-011) | `src/mime/nodes/environment/stokeslet/mlp_resistance_node.py` |
| `GravityNode` (MIME-NODE-012) | `src/mime/nodes/environment/gravity_node.py` |
| `LubricationCorrectionNode` (MIME-NODE-013) | `src/mime/nodes/environment/stokeslet/lubrication_node.py` |
| `ContactFrictionNode` stub (MIME-NODE-014) | `src/mime/nodes/environment/stokeslet/contact_friction_node.py` |
| `RigidBodyNode` (kinematic + inertial) | `src/mime/nodes/robot/rigid_body.py` |
| Training driver | `scripts/retrain_mlp_v2.py` |
| BEM training-data generator (phases A–C) | `scripts/generate_mlp_training_data.py` |
| BEM training-data v3 fill (Apr-14) | `scripts/generate_training_fill_v3.py` |
| Lubrication comparison + ε sweep | `scripts/apply_lubrication_and_compare.py` |
| Dynamic-sim graph | `scripts/dejongh_dynamic_simulation.py` |
| USDC recorder | `scripts/dejongh_record_usdc.py` |
| Off-center sensitivity analysis | `scripts/analyze_dejongh_results.py` |
| Deliverable figure (lubrication comparison) | `scripts/apply_lubrication_and_compare.py` |
| Design-optimisation figure | `scripts/make_design_optimisation_figure.py` |
## Reproduction
```bash
.venv/bin/python scripts/dejongh_dynamic_simulation.py gate # SI gate
.venv/bin/python scripts/dejongh_dynamic_simulation.py scenarioA # FL-9 + FL-3
.venv/bin/python scripts/dejongh_dynamic_simulation.py scenarioB # pulsatile
.venv/bin/python scripts/dejongh_record_usdc.py FL-9 # USDC
.venv/bin/python scripts/dejongh_record_usdc.py FL-3
.venv/bin/python scripts/dejongh_record_usdc.py B
.venv/bin/python scripts/analyze_dejongh_results.py # robustness
.venv/bin/python scripts/apply_lubrication_and_compare.py # parity + lubrication figure
.venv/bin/python scripts/make_design_optimisation_figure.py # differentiable-design figure
```
Training the MLP from BEM ground truth (optional, overnight):
```bash
.venv/bin/python scripts/generate_mlp_training_data.py # ~6 h overnight
.venv/bin/python scripts/generate_training_fill_v3.py # ~90 min, 63 configs (GPU)
.venv/bin/python scripts/retrain_mlp_v2.py # ~6 min (CPU, 3 archs)
```
## References
* de Jongh et al. (2025) — helical-microrobot confined-swimming dataset
and Stokeslet / 4-parameter drag baselines.
* Cortez (2005) — regularised Stokeslets, *SIAM J. Sci. Comput.*
* Liron & Shahar (1978) — cylindrical-tube Green's function,
*J. Fluid Mech.* 86, 727–744.
* Cox & Brenner (1967) — lubrication asymptotics for sphere-wall
approach, *Chem. Eng. Sci.* 22, 1753–1777.
---
## Appendix A — Phase-1 foundation validations (April 2026)
Three foundation validations were run before phase 1 of the
station-keeping simulation began. Tests at
`tests/verification/test_gradient_force_bar_magnet.py`,
`tests/verification/test_coupling_group_field_body.py`, and
`tests/verification/test_mlp_clamp_and_accuracy.py`.
### A.1 Gradient-force path (V1) — passes
`PermanentMagnetResponseNode`'s `F = (∇B) · m_lab` path was never
exercised by any prior simulation (every existing field node hardcoded
`field_gradient = 0`). V1 drives the path with an analytical bar-magnet
∇B at 15 (position × orientation) configurations and confirms the node's
output matches the analytical force `F = (∇B) · m` to per-component
relative error < 1e-4.
**Implication for phase 1:** the gradient-force path can be trusted the
moment it is fed a real, position-dependent ∇B. Replacing the current
uniform-field `ExternalMagneticFieldNode` with a finite-bar-magnet field
node will produce a non-zero gradient at the UMR position, and
`PermanentMagnetResponseNode` will correctly translate that gradient
into a force on the dipole. **No production code change to the magnet
response node is required.**
Three caveats apply to the new field node itself, *not* the response
path:
1. **The bar-magnet field node still needs its own validation.** V1
used the closed-form point-dipole far-field as the reference for ∇B
on both sides of the comparison. The actual bar-magnet model — once
written — needs an independent check against the Coulombian-pole or
current-loop formulation to ensure its B and ∇B are physically
correct. V1 covers the consumer (the response node), not the
producer.
2. **The point-dipole far-field is only faithful at z ≳ 5 R_magnet.**
Closer in, the standard `(R_magnet/z)²` near-field correction
dominates and a finite-magnet model (Coulombian poles, or
current-loop integral, or volumetric integral over the magnet
geometry) is required. The cross-check in V1 confirmed the
point-dipole formula disagrees with the on-axis current-loop
expression by ≈ 4 % at z = 3 mm and < 1 % at z = 10 mm, both as
expected from theory. For station-keeping where the magnet sits a
few cm from the UMR, this is a regime the field-node design needs
to be aware of.
3. **Wiring `body→field` will create a cycle.** Without intervention
it is auto-classified as a back-edge → one-step phase lag (≈ 10°
per timestep at 60 Hz). V2 confirmed `add_coupling_group(["field",
"magnet", "body"], …)` resolves the cycle within a single step via
Gauss-Seidel iteration.
### A.2 Coupling-group plumbing (V2) — passes
V2 confirmed that `GraphManager.add_coupling_group` produces the same
result as the back-edge variant when the closed-loop dependence is
intentionally null (α = 0; the field doesn't actually depend on body
position) and *different* results — diverging linearly in α — when
the dependence is real. Convergence diagnostics (≤ 20 iterations,
residual at the float32 precision floor) report cleanly via
`gm.coupling_diagnostics()`.
### A.3 MLP clamp and clamped-regime accuracy (V3) — known issues
documented
V3a confirmed that `clamp_fired` is wired into the trajectory JSON
written by `scripts/dejongh_dynamic_simulation.py:331` and propagates
through a 200-step run forced into the clamped regime. **No production
fix needed.**
V3b compared MLP swim-speed predictions against fresh BEM ground
truth at 6 lateral offsets straddling the silent-clamp boundary at
`offset_frac = 0.30`. The full envelope is persisted at
`data/dejongh_benchmark/diagnostics/mlp_clamp_envelope.json` and
summarised here:
| offset_frac | training? | v_BEM (mm/s) | v_MLP (mm/s) | rel err |
|---|---|---|---|---|
| 0.20 | yes | 3.48 | 3.57 | 2.6 % |
| **0.25** | **yes** | **3.72** | **4.33** | **16.2 %** |
| **0.28** | **yes** | **3.90** | **4.30** | **10.2 %** |
| 0.30 | yes | 4.01 | 4.02 | 0.3 % |
| 0.32 | clamped | 4.04 | 3.70 | 8.3 % |
| 0.35 | clamped | 23.09 | 3.82 | 83.5 % |
Two findings phase 1 must be aware of:
1. **The MLP's headline 0.6 % test MAE is averaged. There is a
≈ 16 % rel-error pocket at offset_frac ≈ 0.25–0.28** — between the
well-trained centred and gravity-equilibrium points. The LHS test
set used to train the v3 weights happened to under-sample this
region. Phase-1 trajectories will pass through it, so any analysis
point in `offset_frac ∈ [0.22, 0.29]` must be flagged as having
≈ 15 % surrogate uncertainty on top of any other modelling error.
Resolution requires a targeted retraining (≈ 30 BEM configs at
intermediate offsets, then `retrain_mlp_v2.py`).
2. **At `offset_frac = 0.35` the BEM ground truth gives v_z ≈
23 mm/s — implausibly high.** This is the regularised-Stokeslet
formulation hitting its near-wall floor (gap δ → ε). The 83 %
"error" at that offset is **not** an MLP failure; it is the BEM
itself going non-physical near contact. **No model in the current
stack is trustworthy past offset_frac ≈ 0.32.** Phase 1's
wall-contact regime needs the lubrication node + an
experimentally-calibrated friction coefficient (Appendix item-1
above + `ContactFrictionNode` stub at MIME-NODE-014). Don't try
to validate phase-1 station-keeping against rigid-body Stokes for
`offset_frac > 0.32`.
Phase-1 trajectory analysis pipelines should read
`mlp_clamp_envelope.json` and tag any output frame whose lateral
offset falls inside either of these regimes — the V3 output is
machine-readable specifically so this can be automated.