Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/publish-cvcgl-wasm.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: publish-cvcgl-wasm

# Build + publish libcvc and cvcgl for the wasm-mt platform (WebAssembly +
# pthreads), so downstream wasm consumers (CVC-DBG/cvcdbg's demos) can
# pthreads), so downstream wasm consumers (browser apps built on cvcGL) can
# `cvcpkg install cvc/cvcgl --platform wasm-mt` instead of rebuilding cvcGL from
# source. The wasm-mt DEPS (vtk/imgui/boost/codecs, all wasm32) are already on
# the catalog; only libcvc + cvcgl were missing. cvcgl is built WITH imgui, so
Expand Down Expand Up @@ -88,9 +88,9 @@ jobs:
done

- name: Build + install libcvc + cvcGL (wasm-mt, with imgui + sdl3)
# The proven build from CVC-DBG/cvcdbg wasm/build-standalone.sh step 1:
# ONE emscripten configure builds both cvc + cvcGL targets and installs
# them into $INST. -DCVC_BUILD_EXAMPLES=OFF: libcvc's own demos never build.
# them into $INST (the same single-configure shape a standalone downstream
# wasm-mt app build uses). -DCVC_BUILD_EXAMPLES=OFF: libcvc's own demos never build.
run: |
# shellcheck disable=SC1091
source "$CVC_EMSDK_DIR/emsdk_env.sh"
Expand Down
2 changes: 1 addition & 1 deletion CMake/cvcConfig.cmake.in
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ if(@CVC_ENABLE_IMAGEMAGICK@)
# find_library, and it wins on a wasm consumer (it finds the static libs in the prefix
# without pkg-config). Gating on _cvc_im_synth skipped the append for that stock target,
# so a wasm consumer's link failed with undefined BZ2_*/lzma_*/xml* symbols — which is
# what broke the cvcdbg demo3 gallery even after the export was made relocatable. Any
# what kept a downstream wasm app from linking even after the export was made relocatable. Any
# MagickCore that reaches here is either a static one that NEEDS the closure or a shared
# one for which the append is a harmless dedup, so run it whenever the target exists.
if(TARGET ImageMagick::MagickCore)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -505,7 +505,7 @@ Measured 3.7–6.0× fewer triangles on the Austin bundle; `solve()` for a
- **[docs/CUDA_GUIDE.md](docs/CUDA_GUIDE.md)** - CUDA usage guide
- **[docs/NAV_TRAINING.md](docs/NAV_TRAINING.md)** - `cvc::nav` self-supervised policy training (torch-free, CPU + CUDA; surrogate vs bicycle rollout)
- **[docs/NAV_VEHICLE.md](docs/NAV_VEHICLE.md)** - `cvc::nav` optional vehicle refinements: multi-disc footprint (+ the `body_gain` correction it needs), inner-wheel steering lock, and a material grip field
- **[docs/NAV_STATS.md](docs/NAV_STATS.md)** - `cvc::nav::nav_stats` base navigation telemetry: the per-vehicle/per-episode collector + corpus scorecard, the `sim_world` internal collector, and the `min_clearance_world()` units contract (RF-free base of the two-layer nav-stats design)
- **[docs/NAV_STATS.md](docs/NAV_STATS.md)** - `cvc::nav::nav_stats` base navigation telemetry: the per-vehicle/per-episode collector + corpus scorecard, the `sim_world` internal collector, and the `min_clearance_world()` units contract (domain-neutral base of the two-layer nav-stats design)
- **[docs/LOD_API.md](docs/LOD_API.md)** - `cvc::lod` level-of-detail selection math: rung selection, budget solver, presets, and the user-facing knobs
- **[docs/STREAMING.md](docs/STREAMING.md)** - `cvc::ariadne::stream` real-time frame transport: zero-copy video/audio/sensor streams, the host interface (DSL `stream-open`/`gl-bind-stream`, the `/streams/<id>` descriptor, §4.1 scoping), GL/pycvc sinks, and the peripheral `frame_source` model (SDL3 camera + the composable audio-source family) with examples

Expand Down
7 changes: 4 additions & 3 deletions bindings/pycvc/pymod_gl/scenes.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
a :class:`pycvc_gl.lab.Lab`.

These are generic loaders for a ``geometry_bundle`` export — a ``terrain.json``
heightfield plus a ``buildings.glb`` (glTF 2.0) city mesh, as produced by the
CVC-DBG ``geometry-scene-gen`` tool (e.g. the Austin bundle). The glTF is loaded
heightfield plus a ``buildings.glb`` (glTF 2.0) city mesh, as produced by an
OpenStreetMap + SRTM scene generator (e.g. the Austin scene bundle the nav demos
load, ``scene-austin-south-small`` on cvcpkg). The glTF is loaded
NATIVELY via libcvc (``pycvc.load_model`` → ``cvc::model``, the Assimp-backed
mesh loader — no ``vtkGLTFReader``, no trimesh/pygltflib) and added as a single
native geometry node; the terrain becomes a draped surface mesh; a bilinear
Expand All @@ -29,7 +30,7 @@ def terrain_grid(path: str):
``bounds2d`` = ``(min_x, min_y, max_x, max_y)``.

The stored grid is TOP-DOWN (row 0 = north = ``max_y``, the raster/SRTM/GeoTIFF
convention the geometry-scene-gen tool inherits). Our mesh + sampler use the
convention the scene generator inherits). Our mesh + sampler use the
opposite, bottom-up convention (row 0 -> ``min_y``, so a rising world ``y``
walks up the row index). We normalize here by reversing the rows so BOTH the
terrain mesh and the drape sampler agree with the glTF's world frame — without
Expand Down
4 changes: 2 additions & 2 deletions cvcpkg/recipes/cvcgl/recipe.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ recipe:
# so cvc::gl::ImGuiOverlay actually renders for downstream GUI consumers instead of
# being an inert stub. imgui is now a runtime dep (the export links the relocatable
# $<INSTALL_INTERFACE:imgui>). Prior bundles shipped imgui-less, forcing consumers
# (cvcdbg demos) to rebuild cvcGL from source.
# that draw an overlay to rebuild cvcGL from source.
# Bumped 2 -> 3: republish cvcGL against libcvc 3.4.0+cvc.3. The published +cvc.2 was built
# against libcvc +cvc.2 and leaks an EXACT pin to it (cvcpkg records the built-against dep
# version); that dragged the pre-guard libcvc +cvc.2 into wasm-mt consumer installs, so
Expand All @@ -40,7 +40,7 @@ recipe:
# Bumped 5 -> 6: rebuild against the relocatable-export libcvc +cvc.5 (libcvc #453). cvcGL source
# is unchanged, but cvcgl exact-pins the libcvc it built against — the published +cvc.5 pins the
# broken-export libcvc +cvc.4, so `cvcpkg install cvc/libcvc cvc/cvcgl --platform wasm-mt` dragged
# +cvc.4 back in and the demo3 wasm link still failed. This reships cvcgl +cvc.6 built against (and
# +cvc.4 back in and a consumer's wasm link still failed. This reships cvcgl +cvc.6 built against (and
# re-pinning) libcvc +cvc.5, so the wasm consumer install stays on the clean export end-to-end.
# Bumped 6 -> 7: rebuild against + re-pin libcvc +cvc.6 (the consumer-side ImageMagick
# delegate-closure fix in cvcConfig.cmake.in). cvcGL source unchanged; the exact-pin means
Expand Down
12 changes: 6 additions & 6 deletions cvcpkg/recipes/libcvc/recipe.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,17 +72,17 @@ recipe:
# Bumped 1 -> 2 (3.4.0 lineage): republish the family so the bundle ships the
# nav::sim_world::set_material_lam live setter (#433). It is an inline header
# method (no exported symbol / ABI change), but consumers that compile against
# the SDK headers need it present -- notably the cvcdbg demo3 force-bias slider
# (CVC-DBG/cvcdbg#142), whose wasm-mt build installs cvc/libcvc from the catalog
# the SDK headers need it present -- notably a downstream wasm-mt app's live
# force-bias control, whose build installs cvc/libcvc from the catalog
# and will not compile until 3.4.0+cvc.2 (with the setter) is published. The
# published 3.4.0+cvc.1 predates #433. cvcgl already sits at +cvc.2, so this
# brings the tested-together family back to a single revision.
# Bumped 2 -> 3 (3.4.0 lineage): republish the family carrying the coef_mlp .cvcnav format v2
# (two-head sigmoid λ_soft/λ_hard, #436). The wasm-mt republish also (a) turns CVC_STATE_EXEC back
# ON for wasm (it was wrongly forced OFF — the state_exec evaluator has no gRPC dependency and must
# always build), and (b) picks up the gRPC/xmlrpc dep guard above so `cvcpkg install cvc/libcvc
# --platform wasm-mt` resolves again. A v2 .cvcnav hard-fails on a pre-v2 host, so hosts (demo3 /
# cvcdbg fleet) must land 3.4.0+cvc.3 before any v2 blob ships.
# --platform wasm-mt` resolves again. A v2 .cvcnav hard-fails on a pre-v2 host, so every host that
# loads .cvcnav blobs must land 3.4.0+cvc.3 before any v2 blob ships.
# Bumped 3 -> 4 (3.4.0 lineage): the +cvc.3 comment above was WRONG — wasm-mt did NOT resolve. The
# +cvc.3 gRPC/xmlrpc guard only scoped abseil/protobuf/grpc; `pack` bakes a bundle's required_deps
# from depends.runtime FILTERED BY the target platform (builder.py generate_manifest), so the wasm-mt
Expand All @@ -98,8 +98,8 @@ recipe:
# Bumped 4 -> 5 (3.4.0 lineage): the +cvc.4 wasm-mt bundle resolved cleanly but its EXPORTED
# cvcTargets.cmake baked 15 build-machine absolute paths into cvc::cvc's INTERFACE_LINK_LIBRARIES
# (libMagick++/libMagickCore + the codec delegates + libzstd, all under /home/runner/.../wasm-deps),
# so a relocated consumer's link died with "libMagick++-7.Q16HDRI.a ... missing" — which is why the
# cvcdbg wasm demo3 gallery could never link. libcvc #453 makes the STATIC wasm export relocatable
# so a relocated consumer's link died with "libMagick++-7.Q16HDRI.a ... missing" — which is why a
# downstream wasm app could never link. libcvc #453 makes the STATIC wasm export relocatable
# (synthesise ImageMagick::Magick++/::MagickCore + PkgConfig::ZSTD imported targets so the export
# names targets, not paths; cvcConfig.cmake.in re-resolves them per-consumer). A recipe edit can't
# fix the already-published +cvc.4 bundle — this reships a clean +cvc.5 wasm-mt export. Native is
Expand Down
9 changes: 4 additions & 5 deletions docs/NAV_MATERIAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,8 @@ reconstructs the exact map. A v2 blob **hard-fails on a pre-v2 host** (the loade
requires format v2 for `kFlagLamSigmoid`); the whole round-trip — widen to two
heads, serialize, reload, drive — is pinned by `nav_material_deploy_test`.

To steer on the learned grip/risk policy **while** an external force (e.g.
cvc::dbg's RF/comm push) also acts, use the fused `drive_step_material_ext` /
To steer on the learned grip/risk policy **while** an external force (e.g. a
downstream layer's own steering push) also acts, use the fused `drive_step_material_ext` /
`bicycle_rollout_material_ext` — `sim_world::step()` selects it automatically when
both a material stack and an `ext_force` are attached (otherwise material would
take priority and drop the ext force). The whole round-trip — a widened net
Expand All @@ -173,10 +173,9 @@ learned-lam reroute) — is pinned by `nav_material_deploy_test`.
> *means*. The two-head material soft/hard channel documented here is the
> reusable template — a precomputed risk/hazard field driving `−λ_s∇r̃ −
> λ_h b′(φ)∇φ`. A downstream layer can re-source that template from a different
> field entirely and inject it through the same seam without any change here; the
> RF/comm re-sourcing (the `cvc::dbg` layer) is exactly that, and its
> field entirely and inject it through the same seam without any change here; its
> domain-specific logic stays in that layer, never in this core (as with the
> RF-free base statistics — see `NAV_STATS.md`).
> domain-neutral base statistics and their extension seam — see `NAV_STATS.md`).

### Choosing constants

Expand Down
61 changes: 33 additions & 28 deletions docs/NAV_STATS.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
# cvc::nav navigation statistics

`cvc::nav::nav_stats` (`inc/cvc/nav/nav_stats.h`) is the **base, RF-free** navigation-telemetry
layer: per-vehicle + per-episode motion/clearance/collision/material/time/fuel/budget stats over a
`sim_world` drive, plus a corpus scorecard for ranking training checkpoints. It is the shared base of
the two-layer nav-stats design (the RF/comms extension lives in `cvc::dbg`, in the CVC-DBG/cvcdbg
repo, and joins this record by `veh_index`; it never lives here). Field names mirror the Python
`grl_snam.scorecard` schema, and the two are held to the same hand-computed numbers by parity tests.
`cvc::nav::nav_stats` (`inc/cvc/nav/nav_stats.h`) is the **base, domain-neutral**
navigation-telemetry layer: per-vehicle + per-episode motion/clearance/collision/material/time/fuel/
budget stats over a `sim_world` drive, plus a corpus scorecard for ranking training checkpoints. It is
the open core of a two-layer design. Everything here is generic navigation; the second layer is an
**extension seam**. A downstream layer that needs domain-specific metrics (for example, about an
external force it injects through `ext_force`) keeps its own per-vehicle record, joins it to this one
by `veh_index`, and nests it in the JSON itself. Nothing domain-specific lives here. Field names
mirror the Python `grl_snam.scorecard` schema, and the two are held to the same hand-computed numbers
by parity tests.

The collector is stdlib-only (no libcvc-internal dependency beyond the STL): it consumes the
`sim_world::snapshot` arrays plus optional position samplers, so grl-snam, the cvcdbg demos/harness,
and `sim_world` itself all drive it identically.
`sim_world::snapshot` arrays plus optional position samplers, so grl-snam, the cvcGL nav demos, a
downstream app or test harness, and `sim_world` itself all drive it identically.

## Types

Expand All @@ -26,18 +29,19 @@ and `sim_world` itself all drive it identically.
then feed `drive_sample` from `drive_telemetry_data()`); otherwise `mu_mean` stays 1, `mrisk_mean`
0 (the no-material/no-grip neutral).
- **`episode_nav_stats`** — one per episode: the reduced fleet fields + `per_vehicle`, and `to_json()`
(the base record; a DBG consumer nests an `"rf"` member itself). The `1e30` "unmeasured" sentinel
(the base record; an extension layer nests its own member itself). The `1e30` "unmeasured" sentinel
for `min_clearance_m`/`min_sep_m` serializes as JSON `null`, not a huge finite number.
- **`nav_stats_params`** (thresholds) and **`budget_policy`** (time / ETA-multiple / fuel bounds).
- **`nav_samplers`** — optional per-position hooks: `material_id(x,y)`, `occupied(x,y)`, and
`min_clearance_m` (a `const double*` **in metres** — see the units note below).
- **`nav_scorecard`** + **`aggregate_nav(episodes, checkpoint)`** — reduce a corpus of episodes into
one RF-free fitness row (arrival, economy, safety, material) for ranking base-policy checkpoints.
one domain-neutral fitness row (arrival, economy, safety, material) for ranking base-policy
checkpoints.
Includes the fleet drive-telemetry means (`mean_alpha`/`mean_beta`/`mean_gamma`, `mean_mu`,
`mean_mrisk`, `mean_ext_force`) and `material_time_share[kNumMaterials]` — the signals a grip/risk
tuning or trained-policy A/B is judged on (lower `mean_mrisk` + off-hazard time-share at held
arrival). Note `composite_score` / grip-margin are reserved to a downstream (cvc::dbg) scorecard,
not computed here.
arrival). A weighted composite score or a grip margin is left to a downstream extension's own
scorecard, not computed here.

## Collecting

Expand Down Expand Up @@ -85,24 +89,25 @@ homogeneous convoy.
`veh_nav_stats::arrived` / `time_to_goal_s` latch on the **rising edge of the sim's `reached[i]`**
flag (`nav_stats_params::reach_eps`), i.e. when a vehicle actually gets within `sim_world`'s
`reach_tol` of *its own* goal. That is the per-vehicle truth. A **convoy/harness may carry its own
coarser arrival tolerance** for a whole-column "done" check — e.g. cvcdbg's `ConvoyController::arrive_m()
= N·standoff + 40 m`, ~172 m for a 6-vehicle column. That column tolerance is fine as a formation
check but **hides a tail follower that parked short**: the harness can print `atObjective=6/6` while
two followers never latched `reached`. When you are debugging "did each vehicle arrive?", read the
per-vehicle `arrived`/`time_to_goal_s` (`time_to_goal_s < 0` = never reached), **not** the aggregate
column count.

Worked example — the cvcdbg demo3 tail-follower loss was isolated entirely with this schema via
`cvcdbg-nativedemo/tools/dbg_arrival_check3.cpp` (`--json` per-vehicle records): comm-off arrives 6/6
in every condition, while the bounded comm-steer force loses the two tail followers under sustained
jamming (`reached=0`, `turn_total_rad` 12→252 = looping, `wall_entries` 0→19). Two traps that turn
these stats into noise if ignored: (1) the coarse column tolerance above, and (2) a harness whose jam
schedule scales with total run length — hold the run length fixed when A/B-ing. See
`cvcdbg-nativedemo/docs/demo3-follower-loss.md` for the full case and the `turn_total_rad` /
`wall_entries` / `time_stopped_s` interpretation used to distinguish "looping" from "frozen."
coarser arrival tolerance** for a whole-column "done" check — e.g. one that grows with the number of
vehicles times their standoff, so a long column's tolerance is many times a single vehicle's
`reach_tol`. That column tolerance is fine as a formation check but **hides a tail follower that
parked short**: the harness can report the whole column at the objective while some followers never
latched `reached`. When you are debugging "did each vehicle arrive?", read the per-vehicle
`arrived`/`time_to_goal_s` (`time_to_goal_s < 0` = never reached), **not** the aggregate column
count.

The per-vehicle records (`to_json()`) are enough on their own to isolate a follower that fails to
arrive. A/B the same scenario with and without the suspect influence (e.g. an extra steering force
injected through `ext_force`): a follower that is **looping** shows `reached=0` with `turn_total_rad`
and `wall_entries` far above the baseline run, while a **frozen** one shows `time_stopped_s` climbing
instead. Two traps turn these stats into noise if ignored: (1) the coarse column tolerance above, and
(2) a harness whose disturbance schedule scales with total run length — hold the run length fixed
when A/B-ing.

## Tests

`src/cvc/tests/nav_stats_test.cpp` (the base accumulators/scorecard over a scripted trajectory) and
`nav_test.cpp`'s `NavSimWorld` suite (the `sim_world` internal collector + `min_clearance_world`). The
scripted corpus + numbers are mirrored in cvcdbg's and grl-snam's tests — the shared-schema contract.
scripted corpus + numbers are mirrored in grl-snam's tests (and in any downstream extension that
re-checks the base record) — the shared-schema contract.
4 changes: 2 additions & 2 deletions docs/NAV_VEHICLE.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ untouched.
|---|---|
| `bicycle_rollout` | CPU, threaded |
| `bicycle_rollout_material` | CPU + the material force |
| `bicycle_rollout_ext` | CPU + a generic external force (e.g. cvc::dbg's RF/comm force) |
| `bicycle_rollout_ext` | CPU + a generic external force (e.g. a downstream layer's steering force) |
| `bicycle_rollout_material_ext` | CPU + **both** the material force AND the external force, fused |
| `bicycle_rollout_cuda` | GPU, **given** coefficients — the unfused device twin |
| `drive_step`, `drive_step_material` | fused: `coef_feats` → MLP → rollout |
Expand All @@ -85,7 +85,7 @@ so the fused entry points just pass BOTH; a null `ext.sample` reproduces
`drive_step_material` byte-for-byte, a null material stack reproduces
`drive_step_ext`. `sim_world::step()` now selects `drive_step_material_ext`
whenever a material stack AND an external force are both attached, so a run can
steer on a learned grip/risk policy WHILE the comm/RF force also pushes.
steer on a learned grip/risk policy WHILE the external force also pushes.

`bicycle_rollout_cuda` was added with earlier work. CUDA previously had only
`sdf_sample_cuda` and the *fused* `drive_step_cuda`, so the device vehicle math
Expand Down
Loading
Loading