The monorepo's IOReport energy backends now decline unless the CPU energy rail keeps advancing: on macOS 27 an unprivileged reader sees the CPU rails frozen or republished only in rare lumps, so a meter that trusted a present counter reported CPU work as GPU-only energy, or as zero, while tagging it a reading. The owner's decision is that a vendor energy counter is a real reading (the trust gap is provenance, not accuracy), so a LIVE reading is graded as a hardware reading, and every spec that grades vendor counters now carries the same rule: A reading from a vendor energy counter (RAPL, NVML, Apple IOReport, ...) MAY be graded as a hardware reading only while the counter is demonstrably live for the domain the figure claims: the implementation MUST have observed the counter covering that domain advance under load (for Apple IOReport: the CPU energy rail itself advancing in at least 5 sample intervals, in at least 4 of every 5, over at least 300 ms while the probing thread burns CPU). A counter that is present but not live - frozen, or republished in lumps - MUST NOT produce a hardware-graded figure; the implementation falls back to its estimator or reports the work unmetered, and SHOULD record why. A figure MUST NOT claim coverage of a domain its counter does not read; where the format has a coverage field, such a figure is Partial. Joule Code - spec/receipt.md §2 (REQUIRED): HwShunt now reads "an external shunt, or a vendor energy counter (RAPL, NVML, Apple SMC/IOReport) while that counter is live"; the rule above is added verbatim, and in this receipt the hardware grade is a present measured_uj and the HwShunt tag, so a counter that is not live supplies neither. The §1 comment and §5 say "live". - SPEC.md §6: the provenance definition now names IOReport and NVML beside RAPL/SMC, measured_uj comes only from an interface live for the domain it claims, and a paragraph states the MAY/MUST NOT, pointing to receipt.md §2 for the test. The §9 readiness row says "live" and "not live". - ROADMAP.md v0.2 "Measured energy": a vendor counter feeds the HwShunt path only while live; where it is present but not live (IOReport's CPU rails to an unprivileged reader on macOS 27) the receipt stays on the estimator. - examples/l3-jouleclaw/README.md: the claim that the receipt is "a real local measurement ... (HwShunt on RAPL/NVML hosts; Estimator on Apple Silicon until IOReport is wired)" was wrong both ways: run.rs wires only Apple Silicon's counter, and other hosts fall to the token Estimator. It now says so, that the counter's own tag is carried, and that HwShunt needs a live counter. - joule-code-receipt: the Provenance::HwShunt doc comment says the same. - CHANGELOG.md [Unreleased] ### Changed: the rule; no wire or vector change. Sense - SPEC.md §7 (REQUIRED): measured_uj comes only from a live interface; the rule is added verbatim. The Sandbox PowerMeter was described as "(real RAPL/IOReport)"; it is now described as grading a reading HwShunt only while its counter is live and falling back to its estimator otherwise, which is what origin/sandbox-meter/cpu-rail-liveness implements. §10's Sandbox edge says the same in brief. - sense-schema: the HwShunt, Energy and Energy::measured doc comments say a vendor counter is a hardware reading only while live. - Sense keeps no CHANGELOG, so none is added. Other specs that grade vendor counters - map/spec/receipt.md §2: the HwShunt row read "RAPL/SMC/shunt"; it now names a counter "while it is live", and the rule is added verbatim, noting that Receipt::seal cannot check liveness (the meter's duty, upstream). map/SPEC.md §6 states the MUST NOT and points there. CHANGELOG.md gains an [Unreleased] ### Changed entry; no wire or vector change. - arl/ARL-S.md, Telemetry: physical telemetry is what gates tiers above 0, and it said energy is "measured directly via RAPL ... NVML". The rule is added in ARL-S's own terms and lower-case keywords (ARL uses no RFC 2119 capitals): a counter that is not live does not produce physical telemetry, the energy it would have covered is reported unmetered, and the Supervisor must have observed the counter advance. No version bump. - smart-byte treatise §8.6: "Where RAPL or equivalent hardware energy counters are available, the measurement is read from the hardware" now requires the counter to be demonstrably live, and a frozen or lump- republished counter yields the same honest zero as an absent one. Text and doc comments only: no code path, wire format, or conformance vector changes. joule-code-rs (joule-code-receipt, joule-code-conformance) and sense-rs (sense-schema, sense-conformance) build and test clean, their rustdoc builds under -D warnings, map-rs (map-receipt, map-conformance) tests pass, `joule-code-conformance verify ../conformance/v1` passes 24/0/0, the no-vanity-metrics check passes, and every relative link in the changed Markdown resolves. Co-Authored-By: Claude Opus 5.5 <[email protected]>
157 lines
10 KiB
Markdown
157 lines
10 KiB
Markdown
# Changelog — Map
|
||
|
||
All notable changes to the Map standard and its reference implementation.
|
||
Format follows Keep a Changelog; the standard versions independently of the
|
||
reference crates (which share the `map-rs` workspace version).
|
||
|
||
## [Unreleased]
|
||
|
||
### Changed
|
||
|
||
- **SPEC §6 / `spec/receipt.md` §2 — a vendor counter is a hardware reading
|
||
only while it is demonstrably live.** A RAPL, NVML or Apple IOReport reading
|
||
MAY be graded `HwShunt` only while the counter has been observed advancing
|
||
under load for the domain the figure claims (for IOReport: the CPU energy rail
|
||
itself, in at least 5 sample intervals, at least 4 of every 5, over at least
|
||
300 ms of CPU burn); a present counter that is not live — frozen, or
|
||
republished in lumps — falls back to the estimator. No wire or
|
||
conformance-vector change.
|
||
|
||
## [0.1.0] — 2026-06-04
|
||
|
||
Initial draft standard — the protocol spine.
|
||
|
||
### Added
|
||
|
||
- **Specification.** `SPEC.md` (RFC 2119) defining the tile cargo, the 4D cell
|
||
key, canonical serialization + content addressing, the range-requestable
|
||
tileset + signed manifest/revision chain, the F0–F4 fidelity cascade, and the
|
||
JCR-1 receipt. Sub-specs under `spec/`.
|
||
- **`map-schema`** — `CellKey` (S2 + altitude band + time bucket + LOD),
|
||
`ContentId`, `Tile`, `Directory` (Hilbert-sorted + range planner), `Manifest`,
|
||
`Revision`, `verify_chain`, and the `Tier` enum.
|
||
- **`map-cascade`** — `resolve` over a `TileSource`, returning the highest tier
|
||
≤ the request with a guaranteed F1 fallback.
|
||
- **`map-receipt`** — `Receipt`, `Energy`, `Provenance`, JCR-1 sealing/verifying
|
||
(vendored, byte-compatible with the family verifiers).
|
||
- **`map-conformance`** — C1–C5 + C-neg harness (all checks real) and the
|
||
`gen-vectors` generator; published vectors in `conformance/v1/`.
|
||
- **Vocabulary.** `vocab/v1.ttl`, `v1.jsonld`, `v1.shapes.ttl`, `LEXICON.md`.
|
||
|
||
- **`map-index`** — pure-Rust S2 cell geometry (cell ids, projection, lat/lng,
|
||
points, edges, polygons, regions/caps/rects, the region coverer, metrics)
|
||
ported from `joule-s2`, plus a `bridge` to `map-schema`: `cell_key` from
|
||
coordinates, and `cover_region`/`cover_viewport` from a cap, rectangle, or
|
||
**polygon** to sorted `CellKey`s feeding the directory range planner. 75 tests,
|
||
zero clippy warnings.
|
||
- **Fixed (vs. upstream `joule-s2`)** the `polygon` module, which failed its own
|
||
tests upstream: spherical point-in-polygon now uses an orientation-robust
|
||
winding-angle test (was a degenerate ray); area uses the van Oosterom–Strackee
|
||
signed-area fan (was Girard's-formula complement, ≈4π−A); and `S2Polygon` now
|
||
implements `S2Region`, so the coverer accepts arbitrary boundaries / footprints
|
||
/ geofences.
|
||
- **`map-index::volume`** — the recommended 4D discretization scheme ported from
|
||
`joule-spatial3d`: logarithmic `AltBand`s (Mariana Trench → cislunar),
|
||
logarithmic `TimeTier`/`TimeBucket`s (1 s → 100 yr), `SpaceTimePoint`,
|
||
`SpatialKey`, and the `Spatial3DIndex`/`PositionRingBuffer` runtime utilities.
|
||
New bridge functions `alt_band`, `time_bucket`, and `cover_volume` map the
|
||
scheme into `map-schema` `CellKey`s and cover a horizontal region × altitude
|
||
range × time range. (The `chrono` convenience constructors were dropped to keep
|
||
the reference dependency-light.)
|
||
- **`map-tiles`** — tile payload codecs ported from `joule-tiles`: MVT vector,
|
||
raster (PNG/WebP), terrain quantized-mesh, and 3D Tiles, with no S2/geo or
|
||
routing dependency. A `tile` bridge wraps each as a content-addressed
|
||
`map-schema` `Tile` at its cascade tier — terrain → `F1`, raster → `F2`, MVT +
|
||
b3dm → `F3`, splat → `F4` — with stable `codec` tags.
|
||
- **`map-tiles` splat codec** (`map_tiles::splat`) — the `F4:Photoreal` cargo
|
||
codec the tier was missing: a compact SPZ-style container for 3D Gaussian splats
|
||
(positions fixed-point, scales log-quantized, rotation quaternion + colour as
|
||
bytes, gzip-compressed), with `encode_splats`/`decode_splats`. **Spherical
|
||
harmonics**: encodes degree-`n` view-dependent colour as `(n+1)²−1` quantized
|
||
RGB coefficients above the DC term (`sh_degree` in the header). Round-trip
|
||
(incl. degree-1 SH) + compression tests.
|
||
- **`map-tiles` PLY ingest** (`map_tiles::splat::read_ply`) — reads the de-facto
|
||
**INRIA `.ply`** 3DGS interchange format (ASCII + binary-little-endian),
|
||
inverting the trainer's storage conventions (log-scales → `exp`, logit-opacity
|
||
→ `sigmoid`, `(w,x,y,z)` → `(x,y,z,w)` normalized, DC `= 0.5 + C₀·f_dc`,
|
||
channel-major `f_rest` → per-coefficient SH) into `Splat`s that `encode_splats`
|
||
re-encodes into a conforming F4 tile. ASCII + binary + rejection tests.
|
||
- **`map-tiles` b3dm / glTF decode** (`map_tiles::tiles3d::read_b3dm`, `read_glb`)
|
||
— the reader half of the 3D Tiles codec: strips the b3dm header + feature/batch
|
||
tables and decodes the embedded binary glTF (GLB) into a triangle mesh, general
|
||
over accessors/bufferViews (f32 VEC3 positions; u8/u16/u32 indices; accessor +
|
||
bufferView byte offsets and strides; multi-primitive merge). Round-trips
|
||
`B3dmBuilder::encode`; encode/decode + u16-index + rejection tests.
|
||
- **`map-serve` crate** — a transport-agnostic tile-serving core (no HTTP-framework
|
||
dependency): `serve(store, steward, req)` parses a tile request, resolves it
|
||
through the F0–F4 cascade, and returns a content-addressed `Response` with a
|
||
strong `ETag`, `Cache-Control: immutable`, `If-None-Match` → `304`, `X-Map-Tier`
|
||
/ fell-back, and a JCR-1 energy-receipt header. Includes a `MemoryStore`
|
||
reference `TileServer`, plus a filesystem `DirStore` (content-addressed blobs on
|
||
disk + a reloadable JSON index; `std`-only). Pure + unit-tested with a runnable
|
||
doctest.
|
||
- **`map-serve-axum` crate** — an axum/tokio HTTP binding for the serving core:
|
||
`router(store, steward)` exposes `serve` as an axum `Router`, `run(addr, …)` is a
|
||
one-call server. The async stack is isolated here so `map-serve` stays
|
||
framework-free. Verified by `oneshot` tests and a live-socket demo over real HTTP.
|
||
- **`map-route` turn-by-turn + isochrones** — `directions::turn_by_turn` derives
|
||
human maneuvers (depart with cardinal heading, slight/normal/sharp turns,
|
||
U-turn, arrive) from route-segment bearing deltas, merging straight runs into
|
||
one step; `isochrone::isochrone` computes the budget-bounded reachable set from
|
||
a source by travel time or distance (Dijkstra expansion with pruning). Both
|
||
unit-tested.
|
||
- **`map-route` multi-modal + traffic-aware costs** — a `CostModel` trait and a
|
||
built-in `ModeProfile` (car/bicycle/foot) with per-mode road-class access
|
||
matrices, free-flow speeds, and a congestion multiplier; `cost::find_route_with`
|
||
is a generalized A* (admissible time heuristic) that skips inaccessible edges
|
||
and reports mode-specific durations. `RoadType` is now `Copy`. Unit-tested
|
||
(mode access, highway avoidance on foot, traffic slowdown).
|
||
- **`map-route` public-transit routing** (`transit`) — the Connection Scan
|
||
Algorithm over a GTFS-like `Timetable`: earliest arrival from timetabled
|
||
`Connection`s with walking-footpath transfers (manual or auto-generated within a
|
||
radius), reconstructing ride/walk `Leg`s. Times are integer seconds. Unit-tested
|
||
(line with wait, missed connection, faster-of-two, footpath transfer, auto-transfers).
|
||
- **`map-tiles` MVT decoder** — added the reader half (`mvt::decode_tile`,
|
||
`decode_geometry`, `unzigzag`): a minimal protobuf reader inverse to the
|
||
encoder, plus the geometry-command decoder back to absolute tile-local rings /
|
||
line-strings. A conforming reader must decode to render; the encoder shipped
|
||
without it. Encode→decode roundtrip tests cover geometry + properties + closed
|
||
polygons. (Not in upstream `joule-tiles`.)
|
||
- **Real-world robustness fix:** `decode_value` now handles MVT `sint_value`
|
||
(field 6, zigzag), decodes `int_value` (field 4) as a plain varint per spec
|
||
(was wrongly zigzag), and tolerates an empty/unknown value instead of aborting
|
||
the whole tile. Verified by decoding live OpenMapTiles tiles (OpenFreeMap) —
|
||
previously failed with `ProtobufError("empty value")`.
|
||
- **Fixed (vs. upstream `joule-tiles`)** `mvt::encode_polygon`, which clobbered
|
||
the `CMD_MOVE_TO` command word with a coordinate and reset the geometry cursor
|
||
per ring (malformed multi-ring polygons); added the missing regression test.
|
||
Backported to `joule-tiles`.
|
||
- **`map-orbital`** — TLE parsing, SGP4/SDP4 propagation, and ECI/ECEF/geodetic
|
||
transforms ported from `joule-orbital` (no `tracing` dependency; only the
|
||
doctest referenced it). A `bridge` maps a propagated satellite sub-point to a
|
||
`map-schema` `CellKey` — S2 cell at the sub-point, the altitude band for its
|
||
altitude (the ISS lands in the LEO band), and the time bucket for the instant —
|
||
so a live satellite is an `F3` address on the same 4D grid as every tile. Lint
|
||
cleanups only; the 38 SGP4/SDP4 known-answer tests confirm the numerics are
|
||
unchanged.
|
||
- **`map-route`** — local geospatial capabilities over a tileset, ported from
|
||
`joule-maps` (energy metering, tile cache, and the in-memory store dropped —
|
||
redundant with `map-receipt`/`map-tiles`/`map-schema`): WGS84 geometry,
|
||
haversine/bearing/midpoint, point-in-polygon, POI geocoding, Dijkstra + A*
|
||
routing on a weighted graph, and polygon geofencing. Added a **feed-agnostic
|
||
adapter** (`feed::{Entity, Feed}`): any source — flights, vessels, satellites,
|
||
CVEs, incidents, supply-chain nodes, enterprise signals — resolves to a
|
||
`CellKey`, so "swap the feeds and it visualizes anything with a location or a
|
||
time" is a property of the standard, not a per-feed rewrite. Tests cover the
|
||
ported geocode/route/geofence/geometry paths (the upstream tests did not come
|
||
with the extracted subset).
|
||
- **`map-schema`** — `TimeBucket.width_ms` widened `u32 → u64` so the temporal
|
||
axis can express coarse archival buckets (a year, a century); `u32` capped it
|
||
at ~49 days. CBOR encodes integers by magnitude, so existing content ids and
|
||
conformance vectors are byte-identical.
|
||
|
||
### Notes
|
||
|
||
- The remaining substrate crates (`map-index` 4D layer from `joule-spatial3d`,
|
||
`map-tiles`, `map-orbital`, `map-route`) and the demonstrator's `wgpu` renderer
|
||
are not yet in this release — see [`ROADMAP.md`](ROADMAP.md).
|