diff --git a/CONSTRAINTS.md b/CONSTRAINTS.md index 2d29133..d3b74eb 100644 --- a/CONSTRAINTS.md +++ b/CONSTRAINTS.md @@ -4,7 +4,7 @@ Invariants this project holds itself to. Every entry is either currently true or is a stated blocker; nothing here is aspirational. Ratchets are measured, not estimated, and the command to re-measure each one is given. -Last measured: 1319 tests collected, all of which pass except the deliberate +Last measured: 1322 tests collected, all of which pass except the deliberate xfails and the skips that `pytest -rs` lists, and 94.3% line coverage (10033/10638 executable lines) on **CPython 3.13**, measured with `python scripts/coverage_report.py`. The figures come from `docs/data/metrics.json`, which `scripts/metrics.py` checks in CI. Coverage is quoted from that script alone: @@ -83,7 +83,7 @@ These are floors, not goals. Each must not regress; raising one is welcome. | Ratchet | Floor | Current (CPython 3.13) | Re-measure with | |---|---:|---:|---| -| Tests collected | 405 | 1319 | `pytest`, `scripts/metrics.py --check` | +| Tests collected | 405 | 1322 | `pytest`, `scripts/metrics.py --check` | | Tests passing | 300 | all collected, except the deliberate xfails and the listed skips | `pytest -rs` | | Line coverage | 75% | 94.3% | `scripts/coverage_report.py`, `scripts/metrics.py --check-coverage` | | `io/trajectory.py` coverage | 85% | 95.0% | as above | diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5b311f1..7e72ec8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -54,6 +54,12 @@ provider fails to authenticate rather than the file failing to load. Rotating the key is a manual step for the key's owner; ignore rules keep it out of Git but do not invalidate it anywhere it has already been exposed. +The template was checked against OpenCode 1.19.0 with a local OpenAI-compatible +mock endpoint. `@ai-sdk/openai-compatible` is the supported package for this +kind of provider. To switch a built-in provider off, list it in +`disabled_providers`: a `"disabled": true` key inside a `provider` entry is not +part of the schema and is dropped without a warning, so the provider stays on. + ## Before opening a pull request All of these must pass locally. They are what CI runs. @@ -70,7 +76,7 @@ mypy src --ignore-missing-imports `ruff format src tests scripts` applies formatting; the `--check` form is the gate. -The suite collects 1319 tests and all of them pass on a fresh clone, except the deliberate +The suite collects 1322 tests and all of them pass on a fresh clone, except the deliberate xfails and the skips. `pytest -rs` lists the skips: two need TUM VI reference data that is not vendored (see below), and the doc-table check runs once `python scripts/run_benchmark.py` has written `results/benchmark.json`. The count and the other numbers the docs quote are in diff --git a/README.md b/README.md index 67305d4..9994015 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,31 @@ table below. It is a synthetic result, the default is unchanged, and visual fusi > IMU with simulated GNSS and, in one study, real camera frames. No number here is a field > measurement. +## How it works + + +```mermaid +flowchart TB + IMU["IMU, always on"] --> F + GNSS["GNSS, can be denied"] --> FDIR["Fault detection: screens each measurement"] + CAM["Camera motion, opt-in"] -.-> FDIR + FDIR --> F["Navigation filter: position, and how sure it is"] + F --> ACC["Accuracy: error against the truth"] + F --> HON["Honesty: is the truth inside the claimed area?"] + TRUTH["Known truth: synthetic runs, recorded datasets"] --> ACC + TRUTH --> HON + ACC --> GATE{"Gate B1"} + HON --> GATE + GATE -- "met" --> ON["Camera fusion on"] + GATE -- "not met" --> OFF["Camera fusion off by default (ADR-0003)"] +``` + +Every run is scored twice: how far the estimate is from the truth, and whether the truth falls +inside the area the filter claims. A feature that improves the first and breaks the second stays +off. A short captioned explainer is on the [project page](https://telschow.github.io/contested-nav/); +the module-level design is in [docs/architecture.md](docs/architecture.md). + + ## Quickstart Python 3.11 to 3.13. From a fresh clone this took **18.7 s** (clone, environment, install and a diff --git a/ROADMAP.md b/ROADMAP.md index bb47bd4..eeee7a4 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -4,7 +4,7 @@ Sequenced by dependency, not by ambition. Each item names its exit test. Status markers: `[x]` done, `[~]` in progress, `[ ]` not started, `[!]` blocked. -Measured baseline: 1319 tests collected and 94.3% line coverage on CPython +Measured baseline: 1322 tests collected and 94.3% line coverage on CPython 3.13 (`docs/data/metrics.json`, from `scripts/metrics.py` and `scripts/coverage_report.py`). Every number below re-measures against that, not against a remembered value. diff --git a/docs/assets/video/contested-nav-explainer.mp4 b/docs/assets/video/contested-nav-explainer.mp4 new file mode 100644 index 0000000..6e457c8 Binary files /dev/null and b/docs/assets/video/contested-nav-explainer.mp4 differ diff --git a/docs/assets/video/explainer-poster.jpg b/docs/assets/video/explainer-poster.jpg new file mode 100644 index 0000000..1502607 Binary files /dev/null and b/docs/assets/video/explainer-poster.jpg differ diff --git a/docs/data/metrics.json b/docs/data/metrics.json index e85315b..daf0920 100644 --- a/docs/data/metrics.json +++ b/docs/data/metrics.json @@ -4,5 +4,5 @@ "coverage_lines_total": 10638, "coverage_percent": 94.31, "coverage_python": "3.13", - "tests_collected": 1319 + "tests_collected": 1322 } diff --git a/docs/index.md b/docs/index.md index 14357d0..a6bb753 100644 --- a/docs/index.md +++ b/docs/index.md @@ -2,6 +2,13 @@ **Is the filter right about how wrong it is?** + + +*A captioned explainer built from one benchmark run: the same GNSS outage with the camera off and +on, the measured accuracy and calibration, and the decision that follows.* + ![Position error against the uncertainty the filter claims, through a GNSS outage, without and with vision](figures/hero.png) `contested-nav` is a 21-state error-state Kalman filter and an evaluation harness for GNSS-denied @@ -21,6 +28,14 @@ did not (see [Results](results.md)). The table above comes from a deterministic known-answer fixture. The recorded-data pages use a real IMU with GNSS simulated from the ground truth. No number here is a field measurement. +## How it works + +{% + include-markdown "../README.md" + start="" + end="" +%} + ## Where to start - [Getting started](getting-started.md): install, run the headline case in 19 seconds, reproduce every number. diff --git a/opencode.example.json b/opencode.example.json index 19e5fb7..6db0d9a 100644 --- a/opencode.example.json +++ b/opencode.example.json @@ -1,6 +1,9 @@ { "$schema": "https://opencode.ai/config.json", "model": "freellmapi/auto", + "disabled_providers": [ + "ollama" + ], "provider": { "freellmapi": { "npm": "@ai-sdk/openai-compatible", @@ -20,12 +23,6 @@ "name": "Auto Router (Fastest / Low Latency)" } } - }, - "local": { - "disabled": true - }, - "ollama": { - "disabled": true } } } diff --git a/tests/test_opencode_example.py b/tests/test_opencode_example.py new file mode 100644 index 0000000..7c8739d --- /dev/null +++ b/tests/test_opencode_example.py @@ -0,0 +1,32 @@ +"""The committed OpenCode template: shape checks that do not need OpenCode installed. + +OpenCode 1.19.0 drops unknown keys inside a ``provider`` entry without a warning, so a +``"disabled": true`` there leaves the provider switched on. ``disabled_providers`` is the +supported switch. +""" + +from __future__ import annotations + +import json +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent +CONFIG = json.loads((ROOT / "opencode.example.json").read_text(encoding="utf-8")) + + +def test_default_model_names_a_declared_provider_and_model() -> None: + provider, _, model = CONFIG["model"].partition("/") + assert model in CONFIG["provider"][provider]["models"] + + +def test_providers_are_disabled_with_the_supported_key() -> None: + for name, entry in CONFIG["provider"].items(): + assert "disabled" not in entry, f"{name}: use disabled_providers instead" + assert isinstance(CONFIG.get("disabled_providers", []), list) + + +def test_api_key_comes_from_the_environment() -> None: + for name, entry in CONFIG["provider"].items(): + key = entry.get("options", {}).get("apiKey") + if key is not None: + assert key.startswith("{env:") and key.endswith("}"), name