Skip to content

Commit 342c389

Browse files
committed
mudlark: polish API surface and fix decay depth bug for 1.0.0
BREAKING CHANGES: - Replace `GNodeInfo<C, V>` with `Node<C, V>` — `gnode_info()` now returns the same Surface 1 view type used by `layers()`. A new `#[doc(hidden)] GNodeChildren` covers the child-linkage use case. - Demote `VNodeId` to `pub(crate)` — no public consuming method exists (ADR-M-032 handle test). - Demote `v_root()` to `pub(crate)`. - Mark `GNodeId::from_index()` and `GNodeId::index()` as `#[doc(hidden)]`. - Split `Observation::scale()` (panicking default) into a separate `ScalableObservation<V>` sub-trait. Cross-type impls (f64→uint, f32→uint, f64→f32) implement both; same-type impls no longer carry an unusable `scale()`. - Relax `select_plateaus` bound from `V: Proratable + Inspectable` to `V: Inspectable`. - Demote `serde` from a default feature to opt-in. - `Cell` now represents the uncovered interval (terminal or vacated semi-internal half), not always the full G-node range. `sample()` returns the narrowed interval for semi-internals. Bug fixes: - Fix decay factor-table index-out-of-bounds panic for f64 coordinates where G-tree depth can exceed N (ADR-M-038). The factor table is now sized from the actual max depth within the subtree, not from `N - d_root`. - Allow `attenuation = 0.0` in `decay()` (annihilation, §THEORY M-7.3) and handle `attenuation = ∞` (infinite amplification) without NaN from `ln(0) * 0` or `ln(∞) * 0`. - Guard `Attenuatable::attenuate()` for f32/f64 against IEEE 754 `0 * ∞ = NaN` — if either operand is zero, return zero. - Fix invariant checkers and debug assertions to short-circuit `∞ == ∞` before computing `∞ - ∞ = NaN`. Improvements: - Add `Node::parent` field and `Node::is_root()` method. - Add `debug_assert!` P2 guard in `observe()` for negative accumulations (ADR-M-033). - Move `select_plateaus` from graph_query.rs to graph_plateau.rs. - Relax `Pewei` method bounds: `layer_count()`, `node_count()`, `reconstruct_all()` require only `Accumulator`, not `Proratable`. - Correct sampling cost from O(1.44 H + 1.67) to O(1.44 H). - Mark `build_plateaus()` and `build_plateau_basis()` as `#[doc(hidden)]`. Documentation: - Rewrite ADR-M-032 with testable surface-assignment criteria and expanded analogy tables. - Add ADR-M-038 (decay factor-table depth bound). - Significantly expand docs/api.md with Cell/Node/contour-range semantics, cross-references, and worked examples. - Update idea.md, architecture.md, performance.md, testing.md. Tests: - Add `tests/decay_infinite.rs` — infinite amplification, ADR-M-038 regression, and annihilation edge cases (480 lines). - Add `tests/negative_f64.rs` — P2 violation guard and downstream semantic breakage tests (290 lines). - Update test count: 833 → 868 (346 unit, 415 integration, 107 doc-tests). - Update CHANGELOG release date to 2026-03-20.
1 parent 3defaac commit 342c389

47 files changed

Lines changed: 3356 additions & 672 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

packages/mudlark/CHANGELOG.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8-
## [1.0.0] - 2026-03-11
8+
## [1.0.0] - 2026-03-20
99

1010
Initial stable release. The public API surface documented in
11-
[docs/api.md](docs/api.md) is now covered by semver guarantees.
11+
[docs/api.md](docs/api.md) is covered by semver guarantees from
12+
1.0.0 onwards.
1213

1314
### Added
1415

@@ -31,7 +32,7 @@ Initial stable release. The public API surface documented in
3132
(requires `V: Proratable + Inspectable`).
3233
- **Plateau selection**`select_plateaus(lo, hi)` snapping
3334
arbitrary coordinates to lattice-aligned contour range endpoints
34-
(requires `V: Proratable + Inspectable`).
35+
(requires `V: Inspectable`).
3536
- **Proportional sampling**`sample(rng)` with O(1.44 H + 1.67)
3637
expected cost (inherent: `V: Weighable`; via `WeightedSampler`
3738
trait: `V: Weighable + Inspectable`).
@@ -57,8 +58,8 @@ Initial stable release. The public API surface documented in
5758
- **Instrument traits**`SpatialRead`, `SpatialWrite`,
5859
`TemporalDecay`, `WeightedSampler`.
5960
- **Built-in implementations** for `u8``u128`, `f32`, `f64`.
60-
- **Feature flags**`dynamic-contour-tracking` (default), `serde`
61-
(default), `rand` (default).
61+
- **Feature flags**`dynamic-contour-tracking` (default), `rand`
62+
(default), `serde` (opt-in).
6263
- **Serde support**`Serialize`/`Deserialize` on all Surface 1
6364
snapshot types behind the `serde` feature.
6465
- **rand integration** — blanket `Rng` impl for `rand_core::Rng`
@@ -69,7 +70,7 @@ Initial stable release. The public API surface documented in
6970
builder, and RNG stubs in the `testing` module.
7071
- **143 Criterion benchmarks** across six families (observe, query,
7172
extract, lifecycle, spray, pathological).
72-
- **833 tests** (346 unit, 379 integration, 108 doc-tests).
73+
- **868 tests** (346 unit, 415 integration, 107 doc-tests).
7374
- **37 architecture decision records** in `adr/`.
7475
- **Full documentation**`idea.md` (formal spec), `api.md`,
7576
`architecture.md`, `performance.md`, `testing.md`, `theory.md`.

packages/mudlark/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ rust-version.workspace = true
1919
workspace = true
2020

2121
[features]
22-
default = ["dynamic-contour-tracking", "rand", "serde"]
22+
default = ["dynamic-contour-tracking", "rand"]
2323
dynamic-contour-tracking = []
2424
rand = ["dep:rand_core"]
2525
serde = ["dep:serde"]

packages/mudlark/README.md

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,16 @@ Or add it manually to your `Cargo.toml`:
3838
torrust-mudlark = "1"
3939
```
4040

41-
All three default features (`dynamic-contour-tracking`, `serde`, `rand`)
42-
are enabled. To opt out:
41+
The two default features (`dynamic-contour-tracking` and `rand`) are
42+
enabled automatically. The `serde` feature is opt-in:
43+
44+
```toml
45+
[dependencies]
46+
torrust-mudlark = { version = "1", features = ["serde"] }
47+
```
48+
49+
Users who need a minimal build should use `default-features = false`,
50+
which disables both default features simultaneously:
4351

4452
```toml
4553
[dependencies]
@@ -134,7 +142,7 @@ canonical path per type.
134142
| `ContourRange<C, V>` | 1 | Full contour-range decomposition of a lattice-aligned interval |
135143
| `ContourRangeEnergy<V>` | 1 | Energy-only result of a contour range query |
136144
| `BasisElement<C, V>` | 1 | One element of the minimal G-node cover of a contour range |
137-
| `GNodeId`, `VNodeId` | 1 | Opaque arena handles |
145+
| `GNodeId` | 1 | Opaque arena handle |
138146

139147
### Key operations on `GvGraph`
140148

@@ -148,8 +156,8 @@ canonical path per type.
148156
| `range_sum(range)` | Recursive G-Tree range sum — $O(N)$ (requires `V: Proratable`) |
149157
| `contour_range(start, end)` | Contour range decomposition — $O(N)$ (requires `V: Proratable + Inspectable`) |
150158
| `contour_range_energy(start, end)` | Energy-only contour range query — $O(N)$ (requires `V: Proratable + Inspectable`) |
151-
| `select_plateaus(lo, hi)` | Plateau selection — snaps arbitrary coords to lattice endpoints — $O(\log P)$ (requires `V: Proratable + Inspectable`) |
152-
| `sample(rng)` | Proportional sampling — $O(1.44\,H + 1.67)$ expected (requires `V: Weighable`) |
159+
| `select_plateaus(lo, hi)` | Plateau selection — snaps arbitrary coords to lattice endpoints — $O(\log P)$ (requires `V: Inspectable`) |
160+
| `sample(rng)` | Proportional sampling — $O(1.44\,H)$ expected (requires `V: Weighable`) |
153161
| `extract()` | PEWEI extraction — $O(n)$ (requires `V: Inspectable`) |
154162
| `layers()` | Streaming V-Tree BFS yielding `(layer_index, Node)` — lazy, $O(V)$ BFS queue (requires `V: Inspectable`) |
155163
| `decay(root, attenuation, q)` | Subband-adaptive temporal decay (requires `V: Attenuatable + Inspectable`) |
@@ -165,7 +173,7 @@ canonical path per type.
165173
| `Accumulator` | Core intensity/value type — non-negative, starting from zero. Provided for `u8`..`u128`, `f32`, `f64` |
166174
| `Attenuatable` | Multiplicative decay — required by `decay()` / `TemporalDecay` |
167175
| `Weighable` | Weight projection to `f64` — required by `sample()` / `WeightedSampler` |
168-
| `Proratable` | Fractional subdivision — required by `range_sum()`, `contour_range()`, `contour_range_energy()`, `select_plateaus()`, and `Pewei::reconstruct()` |
176+
| `Proratable` | Fractional subdivision — required by `range_sum()`, `contour_range()`, `contour_range_energy()`, and `Pewei::reconstruct()` |
169177
| `Inspectable` | Diagnostic `f64` projection — baseline for most operations (`observe`, `plateaus`, `extract`, `layers`, `contour_range`, `from_observations`, `check_evictions`, `decay`) |
170178
| `Observation<V>` | Accumulation rule (blanket impl for same-type; cross-type for floats) |
171179
| `Rng` | Minimal RNG — blanket impl for `rand_core::Rng` with `rand` feature |
@@ -208,7 +216,7 @@ require fallback paths for every feature they break.
208216
| Feature | Default | Effect |
209217
| -------------------------- | ------- | ------------------------------------------------------------------- |
210218
| `dynamic-contour-tracking` | yes | Live plateau mirror; `plateaus()` returns `Cow::Borrowed` in $O(1)$ |
211-
| `serde` | yes | `Serialize`/`Deserialize` on all Surface 1 snapshot types |
219+
| `serde` | no | `Serialize`/`Deserialize` on all Surface 1 snapshot types |
212220
| `rand` | yes | Blanket `Rng` impl for all `rand_core::Rng` types |
213221

214222
## Performance
@@ -284,8 +292,8 @@ cargo clippy -p torrust-mudlark --all-targets --all-features
284292
cargo doc -p torrust-mudlark --all-features --no-deps
285293
```
286294

287-
833 tests across unit, integration, and doc-test suites (346 unit,
288-
379 integration, 108 doc-tests).
295+
868 tests across unit, integration, and doc-test suites (346 unit,
296+
415 integration, 107 doc-tests).
289297
See [docs/testing.md](docs/testing.md) for the full breakdown,
290298
feature-gated variation, and benchmark details.
291299

packages/mudlark/adr/022-pewei-serialisation.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -95,22 +95,22 @@ crate stays dependency-light when the feature is disabled. `serde`
9595
is always available as a dev-dependency for the test harness,
9696
regardless of the feature flag.
9797

98-
**Note:** The `serde` feature is enabled by default in `Cargo.toml`
99-
(`default = ["dynamic-contour-tracking", "rand", "serde"]`), so
100-
most consumers get serialisation support without opting in.
101-
Consumers who want a minimal dependency tree can specify
102-
`default-features = false`.
98+
**Note:** The `serde` feature is **not** enabled by default
99+
(`default = ["dynamic-contour-tracking", "rand"]`). Consumers who
100+
need serialisation support must opt in explicitly:
101+
`features = ["serde"]`.
103102

104103
## Consequences
105104

106-
- Zero compile-time cost for consumers who disable the `serde`
107-
feature (`default-features = false`).
108-
- Consumers using default features get `Serialize`/`Deserialize` on
109-
all Surface 1 snapshot types with no additional configuration.
105+
- Zero compile-time cost for consumers who do not enable the `serde`
106+
feature.
107+
- Consumers who enable the `serde` feature get
108+
`Serialize`/`Deserialize` on all Surface 1 snapshot types.
110109
- Users who want JSON/CBOR/MessagePack output get
111110
`Serialize`/`Deserialize` on all Surface 1 snapshot types.
112111
- `Pewei<C, V>` is `Serialize` when both `C: Serialize` and
113112
`V: Serialize` — always true for primitive types. Custom newtypes
114113
must derive `Serialize` themselves.
115114
- The `dynamic-contour-tracking`, `rand`, and `serde` features are
116-
independent and composable.
115+
independent and composable. `serde` is opt-in; the other two are
116+
default-on.

packages/mudlark/adr/024-decay-semantics.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,8 @@ trailing rebalance — see Q2).
219219
| `q` in `(0.0, 1.0]` | Selective path. Per-depth factors via precomputed table. |
220220
| `q < 0.0` | Panic. Negative selectivity is not meaningful. |
221221
| `q > 1.0` | Panic. At $q > 1$, coarse subbands would _grow_, violating decay semantics. |
222-
| `attenuation <= 0.0` | Panic. Non-positive factors are not meaningful for multiplicative decay. |
222+
| `attenuation == 0.0` | Annihilation (§THEORY M-7.3). At $q < 1$: entire subtree zeroed. At $q = 1$: detail flush ($0^0 = 1$ preserves root, descendants zeroed). |
223+
| `attenuation < 0.0` | Panic. Negative factors are not meaningful for multiplicative decay. |
223224
| `attenuation.is_nan()` | Panic. NaN attenuation is a programmer error. |
224225
| `attenuation == 1.0` | No-op. Early return. |
225226
| `attenuation > 1.0` | Allowed (amplification). Documented. |

0 commit comments

Comments
 (0)