v0.4 — slope & jump dynamics plan¶
Status: M0–M5 implemented (2026-06-08, branch feat/v0.4-slope-jump-m5).
Default stunt plant: Free3DDynamics (create_stunt_dof); legacy rail via create_legacy_stunt_dof.
Loop default: tire-driven arc (loop_rail_guide=false); set rail_guide: true for educative kinematic rail.
Jump spec to port: docs/tasks/T23_jump_14dof/REPORT.md.
1. Goals¶
| Capability | User-visible outcome | Dynamics level |
|---|---|---|
| Grade / bank | Uphill, downhill, banked curve, heightmap terrain | L2 (now partial) → L3 ride follows surface |
| Ramp jump | Take-off, ballistic flight, landing impulses | L3+ world-z (T23 port) |
| Vertical loop | Inverted apex, mesh track | Ld5 (later in same v0.4 tag) |
Flat-road L2/L3 ISO behaviour stays default and frozen; slope/jump modes are
opt-in (level=L3 + stunt scenario, or level=L5 for loop).
2. Today (v0.3) — what works vs gaps¶
Shipped¶
| Layer | Slope-related | Jump-related |
|---|---|---|
IContactProvider |
InclinedGround, HeightmapGround, per-wheel normal, position.z, road_dz |
ContactPoint.is_valid field exists |
L2 (seven_dof) |
Grade/bank gravity in quasi-static Fz; tire μ from contact | is_valid / penetration not consumed (comment ~L375) |
L3 (fourteen_dof) |
Heave/roll/pitch on flat z=0; road_dz roughness only |
g_z_effective ≈ 0; tire always tied to z=0 → no flight |
| Runtime | GUI road_grade / road_bank; terrain.bin heightmap to cosim |
T23 Python prototype + sanity checks only |
| Theory | ch.06 L3 small-angle vertical | T23 REPORT = C++ target spec |
Gap summary¶
contact (z, normal, is_valid)
│
▼
L2 planar ──► uses μ, normal (partial); ignores airborne
│
▼
L3 vertical ──► perturbation about static eq; ground always z=0
경사: L2는 완만한 grade/bank에서 planar 주행 가능하나, L3 서스펜션·차고 z는 노면을 따라가지 않음. 점프: 구조적으로 불가 (공중 Fz=0 분기 없음).
3. Target architecture¶
flowchart TB
subgraph contact [Contact layer]
FG[FlatGround]
IG[InclinedGround]
HM[HeightmapGround]
RG[RampGround - new]
MG[MeshGround - loop]
end
subgraph dyn [Dynamics]
L2[L2 seven-DOF planar]
L3[L3+ world-z vertical]
L5[Ld5 full 3D body]
end
FG --> L2
IG --> L2
HM --> L2
RG --> L3
HM --> L3
MG --> L5
L2 --> L3
L3 --> L5
Per-wheel each step:
ground->query()→z_road,n,is_valid,μ,road_dz- L2: if
!is_valid→ Fx=Fy=0, skip quasi-static Fz; else forces in contact frame - L3+:
Fz_tire = max(0, k_tire · deflection)with deflection fromz_road + r − z_u - Ld5: same contact branch; body attitude from quaternion (no small-angle)
4. Build phases¶
P0 — Contact contract (blocker for all)¶
Files: seven_dof_dynamics.cpp, fourteen_dof_dynamics.cpp, contact_providers.cpp
| Task | Detail |
|---|---|
Wire is_valid |
Off-ground → no longitudinal/lateral tire force; no L2 quasi-static Fz split |
Use position.z |
Per-wheel road height (not hard-coded 0) |
| Surface normal | Long/lat tire axes from contact normal (already partial on L2) |
state.position.z |
Sprung CG world z published in STATE / FMI / Python |
Exit: unit test — wheel with is_valid=false → zero Fz contribution from that corner;
inclined contact → non-zero pitch tendency on steady climb (L2).
ISO impact: default flat L2/L3 ctest path unchanged (flat ground only).
P1 — Grade & terrain (no jump)¶
Goal: drive on slope/bank/heightmap with credible load transfer; L3 ride follows surface.
| Task | L2 | L3 |
|---|---|---|
| Steady grade hold | verify + test 5–10 % grade coast / throttle | — |
| Heightmap | per-wheel z + normal from HeightmapGround |
ground_z[i] into vertical EOM |
| Vertical gravity | — | option A: keep perturbation + road-following offset; option B: world-z (merges into P2) |
| GUI | grade/bank already in setup; terrain mesh preview | telemetry z, per-wheel Fz |
Exit: tests/integration/test_grade_l2.cpp — constant grade, monotonic z or speed change;
tests/integration/test_terrain_l3.cpp — heightmap bump, heave response, wheels stay in contact.
Not in P1: airborne, ramp lip, loop.
P2 — Jump foundation (T23 → C++)¶
Port T23 REPORT into fourteen_dof_dynamics.cpp + L2 coupling.
| From (C++ L3 today) | To (L3+) |
|---|---|
| z perturbation about static eq | world z (sprung + unsprung) |
g_z_effective ≈ 0 |
full −g on sprung & unsprung |
tire k·(−z_u) vs z=0 |
max(0, k·((z_road+r)−z_u)) |
| linear spring only | droop stop + bump stop (progressive, not hard clip) |
| damper always on | damper off when spring topped-out (hanging wheel) |
New provider: RampGround — 1D profile (half-cosine + lip + cliff); reuse for T23 regression.
Scenario: configs/scenarios/jump_ramp.yaml (or configs/maneuvers/).
Exit: tests/stunt/test_jump_l3.cpp — five T23 sanity checks:
- static Fz ≈ theory (±2 %)
- ramp entry → front Fz spike + nose-up pitch
- airborne interval → all Fz = 0
- ballistic z_s vs time (spot check)
- landing → front-before-rear Fz phasing
ISO: stunt suite isolated; no change to default ISO matrix.
P3 — Ld5 full 3D (loop / extreme attitude)¶
Only required when bank + jump + inverted apex exceed L3 small-angle limits.
| Item | Notes |
|---|---|
New level L5 / Ld5_Full3D |
quaternion body; separate factory create_full_3d() |
| Tire | Pacejka/LuGre in wheel contact frame at arbitrary γ, inverted normal |
| Ground | CurvedGround, MeshGround for loop surface |
| Flat default | L2/L3 unchanged; level=L5 opt-in |
Exit: jump regression on Ld5; loop MVP per V0.4_PLAN.md M5–M6.
5. Milestones (slope + jump track)¶
| # | Milestone | Phase | Delivers |
|---|---|---|---|
| M0 | Contact wired in L2 | P0 | is_valid, position.z |
| M1 | Grade L2 verified + tests | P1 | uphill/downhill coast |
| M2 | L3 follows heightmap | P1 | terrain ride |
| M3 | T23 jump in C++ | P2 | test_jump_l3.cpp green |
| M4 | RampGround + GUI preset | P2 | jump_ramp.yaml, stunt panel stub |
| M5 | Ld5 + loop | P3 | vertical_loop.yaml (see parent plan) |
Tag v0.4.0 when parent V0.4_PLAN.md M1–M6 are green; M0–M4
are the slope + jump critical path.
6. Suggested implementation order¶
P0 (contact) ──► P1a (L2 grade tests) ──► P2 (T23 / jump)
│
└──► P1b (L3 heightmap) ──► P2
└──► P3 (Ld5 / loop)
Parallel safe: P1a after P0; T23 port design can start before P1b if interfaces frozen.
7. Parameters & YAML¶
New or extended VehicleParams (names TBD):
| Param | Role |
|---|---|
spring_travel_compression_max |
bump stop engagement |
spring_travel_extension_max |
droop stop |
bump_stop_stiffness / curve |
progressive rubber (replace T23 hard clip) |
tire_vertical_stiffness |
already implicit; expose per corner if needed |
Scenes:
# configs/scenes/jump_ramp_demo.yaml (sketch)
level: L3
ground: ramp
profile: half_cosine
height_m: 0.6
lip_m: 0.4
v0_mps: 15
8. Validation tiers¶
| Tier | Suite | Claim |
|---|---|---|
| Default | 210 ctest flat L2/L3 | ISO / analytic unchanged |
| Slope | test_grade_*, test_terrain_* |
load transfer & ride on surface |
| Stunt | tests/stunt/* |
jump phase / landing phasing (not quantitative peak Fz) |
Landing peak Fz: phasing & relative magnitude only (T23 REPORT — bump model educative).
9. Non-goals (this plan)¶
- V2V collision, corkscrew, track CAD editor
- Replacing default L3 for normal road sims
- Quantitative stunt load sign-off vs real vehicle
- Fz hunting /
ay_prevLPF on L2 (not required for slope/jump)
10. Risks¶
| Risk | Mitigation |
|---|---|
| P2 breaks flat L3 | stunt behind scenario flag; regression on flat path |
| L2 quasi-static Fz wrong on steep grade | cap grade in P1 scenarios; Ld5 for extreme |
| GUI scope | M4 = one ramp preset + telemetry, not full editor |
| LuGre landing grip | keep LuGre default before M5 loop |
11. Links¶
| Doc | Role |
|---|---|
V0.4_PLAN.md |
Full v0.4 WS-A..E, loop MVP |
T23_jump_14dof/REPORT.md |
Jump numbers to regression |
theory/06_ld3_fourteen_dof.md |
Current L3 vertical model |
ROADMAP.md |
§1 dynamics ladder, §11 v0.4 |
HANDOFF.md |
Sprint entry |