transform-tree 0.0.4__tar.gz → 0.0.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {transform_tree-0.0.4 → transform_tree-0.0.5}/Cargo.toml +6 -6
- {transform_tree-0.0.4 → transform_tree-0.0.5}/PKG-INFO +263 -48
- transform_tree-0.0.5/README.md +515 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree/Cargo.toml +11 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree/README.md +29 -3
- transform_tree-0.0.5/crates/tf_tree/examples/control_loop.rs +290 -0
- transform_tree-0.0.5/crates/tf_tree/examples/gen_domain_fixture.rs +131 -0
- transform_tree-0.0.5/crates/tf_tree/examples/two_processes.rs +241 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree/src/bin/rendezvous_child.rs +151 -2
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree/src/cache.rs +517 -24
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree/src/lib.rs +14 -3
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree/src/open.rs +418 -310
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree/src/tree.rs +1101 -104
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree/tests/batch.rs +39 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree/tests/behavior.rs +38 -0
- transform_tree-0.0.5/crates/tf_tree/tests/clock_offset.rs +447 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree/tests/frozen.rs +76 -0
- transform_tree-0.0.5/crates/tf_tree/tests/lookup.rs +333 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree/tests/rendezvous.rs +2095 -44
- transform_tree-0.0.5/crates/tf_tree/tests/wide_stamps.rs +218 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_arena/README.md +4 -0
- transform_tree-0.0.5/crates/tf_tree_bridge/Cargo.toml +68 -0
- transform_tree-0.0.5/crates/tf_tree_bridge/examples/offer_cost.rs +268 -0
- transform_tree-0.0.5/crates/tf_tree_bridge/src/authority.rs +549 -0
- transform_tree-0.0.5/crates/tf_tree_bridge/src/clock.rs +1333 -0
- transform_tree-0.0.5/crates/tf_tree_bridge/src/config.rs +1906 -0
- transform_tree-0.0.5/crates/tf_tree_bridge/src/discover.rs +568 -0
- transform_tree-0.0.5/crates/tf_tree_bridge/src/edgeindex.rs +430 -0
- transform_tree-0.0.5/crates/tf_tree_bridge/src/edgemap.rs +54 -0
- transform_tree-0.0.5/crates/tf_tree_bridge/src/ingest.rs +3407 -0
- transform_tree-0.0.5/crates/tf_tree_bridge/src/interner.rs +178 -0
- transform_tree-0.0.5/crates/tf_tree_bridge/src/lib.rs +380 -0
- transform_tree-0.0.5/crates/tf_tree_bridge/src/names.rs +306 -0
- transform_tree-0.0.5/crates/tf_tree_bridge/src/statics.rs +468 -0
- transform_tree-0.0.5/crates/tf_tree_bridge/src/stats.rs +200 -0
- transform_tree-0.0.5/crates/tf_tree_bridge/tests/steady_state_alloc.rs +420 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_core/Cargo.toml +47 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_core/README.md +4 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_core/src/arena_view.rs +94 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_core/src/buffer.rs +25 -0
- transform_tree-0.0.5/crates/tf_tree_core/src/crash.rs +167 -0
- transform_tree-0.0.5/crates/tf_tree_core/src/crash_tests.rs +702 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_core/src/edge.rs +101 -6
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_core/src/error.rs +266 -2
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_core/src/frame.rs +44 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_core/src/layout.rs +20 -10
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_core/src/lib.rs +71 -9
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_core/src/loom_tests.rs +103 -20
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_core/src/participant.rs +42 -4
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_core/src/plan.rs +784 -193
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_core/src/sample.rs +189 -14
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_core/src/tests.rs +1283 -36
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_core/src/topology.rs +29 -0
- transform_tree-0.0.5/crates/tf_tree_ingest/Cargo.toml +187 -0
- transform_tree-0.0.5/crates/tf_tree_ingest/examples/gen_zstd_conformance.rs +196 -0
- transform_tree-0.0.5/crates/tf_tree_ingest/src/cdr.rs +533 -0
- transform_tree-0.0.5/crates/tf_tree_ingest/src/decompress.rs +2268 -0
- transform_tree-0.0.5/crates/tf_tree_ingest/src/fixture.rs +1956 -0
- transform_tree-0.0.5/crates/tf_tree_ingest/src/ingest.rs +1410 -0
- transform_tree-0.0.5/crates/tf_tree_ingest/src/lib.rs +477 -0
- transform_tree-0.0.5/crates/tf_tree_ingest/src/report.rs +628 -0
- transform_tree-0.0.5/crates/tf_tree_ingest/src/source.rs +826 -0
- transform_tree-0.0.5/crates/tf_tree_ingest/src/spill.rs +761 -0
- transform_tree-0.0.5/crates/tf_tree_ingest/src/tft.rs +51 -0
- transform_tree-0.0.5/crates/tf_tree_ingest/testdata/ATTRIBUTION.md +103 -0
- transform_tree-0.0.5/crates/tf_tree_ingest/testdata/zstd_conformance.mcap +0 -0
- transform_tree-0.0.5/crates/tf_tree_ingest/tests/codec_free.rs +175 -0
- transform_tree-0.0.5/crates/tf_tree_ingest/tests/frozen_bag.rs +107 -0
- transform_tree-0.0.5/crates/tf_tree_ingest/tests/ingest.rs +2955 -0
- transform_tree-0.0.5/crates/tf_tree_ingest/tests/memory.rs +145 -0
- transform_tree-0.0.5/crates/tf_tree_ingest/tests/record_ceiling.rs +135 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_ipc/README.md +4 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_ipc/src/bin/ipc_child.rs +0 -1
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_ipc/src/client.rs +48 -1
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_ipc/src/error.rs +71 -5
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_ipc/src/identity.rs +158 -9
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_ipc/src/lib.rs +5 -2
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_ipc/src/lockfile.rs +145 -8
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_ipc/src/open.rs +305 -78
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_ipc/src/procstat.rs +153 -3
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_ipc/src/server.rs +44 -1
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_ipc/tests/multiprocess.rs +90 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_math/README.md +11 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_math/src/iso3.rs +36 -10
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_math/src/lib.rs +12 -2
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_py/Cargo.lock +232 -6
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_py/Cargo.toml +57 -4
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_py/src/errors.rs +93 -21
- transform_tree-0.0.5/crates/tf_tree_py/src/ingest.rs +187 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_py/src/lib.rs +66 -7
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_py/src/offline.rs +11 -8
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_py/src/tree.rs +1037 -66
- {transform_tree-0.0.4 → transform_tree-0.0.5}/pyproject.toml +1 -1
- {transform_tree-0.0.4 → transform_tree-0.0.5}/python/tf_tree/__init__.py +19 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/python/tf_tree/_core.pyi +288 -14
- transform_tree-0.0.4/README.md +0 -300
- transform_tree-0.0.4/crates/tf_tree/tests/lookup.rs +0 -177
- {transform_tree-0.0.4 → transform_tree-0.0.5}/LICENSE-APACHE +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/LICENSE-MIT +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/NOTICE +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree/LICENSE-APACHE +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree/LICENSE-MIT +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree/NOTICE +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree/src/cbor.rs +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree/src/frozen.rs +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree/src/unstable.rs +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree/tests/await_frames.rs +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree/tests/common/mod.rs +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree/tests/construction.rs +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree/tests/counters.rs +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree/tests/derivatives.rs +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree/tests/enumeration.rs +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree/tests/feature_gates.rs +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree/tests/math_reexports.rs +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree/tests/owned_writer.rs +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree/tests/plan_cache_identity.rs +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree/tests/tsan.rs +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_arena/Cargo.toml +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_arena/LICENSE-APACHE +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_arena/LICENSE-MIT +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_arena/NOTICE +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_arena/src/check.rs +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_arena/src/frozen.rs +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_arena/src/header.rs +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_arena/src/heap.rs +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_arena/src/layout.rs +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_arena/src/lib.rs +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_arena/src/mapped.rs +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_arena/tests/heap_alignment.rs +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_core/LICENSE-APACHE +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_core/LICENSE-MIT +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_core/NOTICE +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_core/src/bench_probe.rs +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_core/src/counters.rs +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_core/src/sync.rs +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_ipc/Cargo.toml +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_ipc/LICENSE-APACHE +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_ipc/LICENSE-MIT +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_ipc/NOTICE +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_ipc/src/fork.rs +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_ipc/src/ofd.rs +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_ipc/src/rendezvous.rs +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_ipc/src/runtime_dir.rs +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_ipc/src/wire.rs +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_math/Cargo.toml +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_math/LICENSE-APACHE +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_math/LICENSE-MIT +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_math/NOTICE +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_math/src/dualquat.rs +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_math/src/interp.rs +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_math/src/quat.rs +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_math/src/reference.rs +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_math/src/twist.rs +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_math/tests/proptests.rs +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/crates/tf_tree_math/tests/slerp_public.rs +0 -0
- {transform_tree-0.0.4 → transform_tree-0.0.5}/python/tf_tree/py.typed +0 -0
|
@@ -45,7 +45,7 @@ exclude = [
|
|
|
45
45
|
# an MSRV raise reaches nobody who did not opt into it by editing a manifest.
|
|
46
46
|
# The rule comes back into force at 0.1.0, where a minor slot exists again.
|
|
47
47
|
# `SUPPORT.md`'s MSRV section carries the same statement in its own voice.
|
|
48
|
-
version = "0.0.
|
|
48
|
+
version = "0.0.5"
|
|
49
49
|
edition = "2021"
|
|
50
50
|
# **1.87, and each step was forced by a dependency rather than chosen.**
|
|
51
51
|
#
|
|
@@ -122,8 +122,8 @@ unexpected_cfgs = { level = "warn", check-cfg = ['cfg(loom)'] }
|
|
|
122
122
|
[workspace.dependencies]
|
|
123
123
|
# Intra-workspace crates (version pinned so publishable crates carry a real
|
|
124
124
|
# requirement, not a path wildcard).
|
|
125
|
-
tf_tree_math = { path = "crates/tf_tree_math", version = "0.0.
|
|
126
|
-
tf_tree_arena = { path = "crates/tf_tree_arena", version = "0.0.
|
|
125
|
+
tf_tree_math = { path = "crates/tf_tree_math", version = "0.0.5" }
|
|
126
|
+
tf_tree_arena = { path = "crates/tf_tree_arena", version = "0.0.5" }
|
|
127
127
|
# Phase 2 shared memory (see docs/PHASE2.md §2). Linux-only, opt-in via the
|
|
128
128
|
# `shm` feature; `linux_raw` backend so there is no libc crate and no C build.
|
|
129
129
|
# Only `tf_tree_ipc`, and only for `fcntl(F_OFD_*)`: rustix has no OFD locking
|
|
@@ -138,9 +138,9 @@ rustix = { version = "1.1", default-features = false, features = ["mm", "fs", "s
|
|
|
138
138
|
# measurement — the "counters off" row was measuring the counters-on engine.
|
|
139
139
|
# Every crate that wants them re-enables them through its own `counters`
|
|
140
140
|
# feature, which is what makes the switch actually switch.
|
|
141
|
-
tf_tree_core = { path = "crates/tf_tree_core", version = "0.0.
|
|
142
|
-
tf_tree_ipc = { path = "crates/tf_tree_ipc", version = "0.0.
|
|
143
|
-
tf_tree = { path = "crates/tf_tree", version = "0.0.
|
|
141
|
+
tf_tree_core = { path = "crates/tf_tree_core", version = "0.0.5", default-features = false }
|
|
142
|
+
tf_tree_ipc = { path = "crates/tf_tree_ipc", version = "0.0.5" }
|
|
143
|
+
tf_tree = { path = "crates/tf_tree", version = "0.0.5", default-features = false }
|
|
144
144
|
# publish = false crates, wired by path only (no version requirement published).
|
|
145
145
|
# **`default-features = false` for the same load-bearing reason as `tf_tree`
|
|
146
146
|
# above, and it is easy to miss here.** `tf_tree_bench` has its own
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: transform_tree
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.5
|
|
4
4
|
Classifier: Development Status :: 3 - Alpha
|
|
5
5
|
Classifier: Programming Language :: Python :: 3.10
|
|
6
6
|
Classifier: Programming Language :: Python :: 3.13
|
|
@@ -20,11 +20,40 @@ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
|
20
20
|
|
|
21
21
|
# tf_tree
|
|
22
22
|
|
|
23
|
+
[](https://crates.io/crates/tf_tree)
|
|
24
|
+
[](https://docs.rs/tf_tree)
|
|
25
|
+
[](https://pypi.org/project/transform_tree/)
|
|
26
|
+
[](https://github.com/NoeFontana/tf_tree/actions/workflows/ci.yml)
|
|
27
|
+
[](#licence)
|
|
28
|
+
|
|
23
29
|
A transform tree engine: store time-stamped rigid-body transforms between named
|
|
24
30
|
coordinate frames and answer *"where was frame A relative to frame B at time
|
|
25
31
|
t?"* — fast enough to sit inside a control loop, with diagnostics good enough to
|
|
26
32
|
debug at 3 a.m.
|
|
27
33
|
|
|
34
|
+
**Jump to:** [Install](#install) · [Start with a bag you already
|
|
35
|
+
have](#start-where-you-change-nothing) · [First five
|
|
36
|
+
minutes](#first-five-minutes-with-no-data-at-all) · [Where it
|
|
37
|
+
fits](#where-it-fits-and-where-it-does-not) · [Status](#status) · [Is this a
|
|
38
|
+
`tf2` replacement?](#is-this-a-tf2-replacement)
|
|
39
|
+
|
|
40
|
+
## What you get
|
|
41
|
+
|
|
42
|
+
- **A compiled query.** `plan()` resolves the topology and folds static edges
|
|
43
|
+
once. `at()` then does *d* binary searches, *d* interpolations and *d−1*
|
|
44
|
+
compositions, and nothing else — no allocation, no hashing, no lock. Frames
|
|
45
|
+
are interned to integer ids at plan time, so no lookup hashes a string.
|
|
46
|
+
- **One tree, not one copy per process.** The arena holds no pointers — every
|
|
47
|
+
internal reference is an offset — so the same bytes map into every reader:
|
|
48
|
+
threads in one process, cooperating processes on a host, or sixteen dataloader
|
|
49
|
+
workers on a frozen `.tft` file. Consumers attach read-only by default, and
|
|
50
|
+
the MMU is what enforces it, not convention.
|
|
51
|
+
- **Failures you can act on.** Every error is a `Copy` identifier that names the
|
|
52
|
+
offending edge as data rather than as a formatted string, and `tf_tree doctor`
|
|
53
|
+
runs the `TFT001`–`TFT019` catalogue against a live arena, a frozen index, or
|
|
54
|
+
an MCAP recording you already have. Nineteen ids are reported; seventeen can
|
|
55
|
+
detect today, and the two that cannot say so rather than reporting a pass.
|
|
56
|
+
|
|
28
57
|
**Linux-first.** The single-process engine is portable Rust and much of it
|
|
29
58
|
compiles elsewhere; everything that maps memory — attaching to a live arena, the
|
|
30
59
|
frozen `.tft` backend, `tf_tree freeze` — is **Linux-only and behind a
|
|
@@ -32,19 +61,78 @@ default-off `shm` feature**. That sentence is here rather than in
|
|
|
32
61
|
[`SUPPORT.md`](./SUPPORT.md) alone because nobody should meet it as a build
|
|
33
62
|
error.
|
|
34
63
|
|
|
64
|
+
## Install
|
|
65
|
+
|
|
66
|
+
| What you want | How to get it | Where it comes from |
|
|
67
|
+
|---|---|---|
|
|
68
|
+
| The Rust engine | `cargo add tf_tree` | crates.io |
|
|
69
|
+
| The Python bindings | `pip install transform_tree`, then `import tf_tree` | PyPI |
|
|
70
|
+
| The `tf_tree` CLI | a prebuilt Linux binary from [the latest release](https://github.com/NoeFontana/tf_tree/releases/latest), or `cargo install --path crates/tf_tree_cli --features shm` from a clone | GitHub Releases (`x86_64`/`aarch64`, gnu and static musl); the CLI is `publish = false`, so not crates.io |
|
|
71
|
+
| C ABI, C++ header, ROS 2 bridge | `just c-abi-check`, `just cpp-check`, `just ros-build` | source |
|
|
72
|
+
|
|
73
|
+
Three notes on that table, each of which surprises somebody:
|
|
74
|
+
|
|
75
|
+
- **The distribution name is not the import name.** PyPI refuses `tf_tree` as
|
|
76
|
+
too close to the existing `tftree`, so the wheel is `transform_tree` and the
|
|
77
|
+
module stays `tf_tree` ([`0008`](./docs/decisions/0008-the-name-tf-tree.md)
|
|
78
|
+
records the measurement).
|
|
79
|
+
- **`cargo add tf_tree` gives you the portable engine.** Shared memory and the
|
|
80
|
+
frozen `.tft` reader need `--features shm`, on Linux.
|
|
81
|
+
- **`cargo install tf_tree` installs no command**, and does not fail either:
|
|
82
|
+
it exits 0 with a warning naming `--features shm`. Adding that flag *does*
|
|
83
|
+
install `tf_tree_rendezvous_child`, which is a test helper and not a tool —
|
|
84
|
+
[the crate's own page](./crates/tf_tree/README.md) has the whole story. The
|
|
85
|
+
CLI is a separate, unpublished crate: take the prebuilt binary from the
|
|
86
|
+
releases page, or build it from a checkout. **`publish = false` is about the
|
|
87
|
+
crates.io index, not about whether it is shipped** — three of its dependencies
|
|
88
|
+
are path-only, so it has no version to publish against.
|
|
89
|
+
|
|
90
|
+
**`0.0.x` promises nothing between releases.** Cargo treats every `0.0.x` as
|
|
91
|
+
incompatible with every other, so pin exactly and expect a later release to
|
|
92
|
+
break. That is the whole promise; see [`CHANGELOG.md`](./CHANGELOG.md).
|
|
93
|
+
|
|
35
94
|
## Start where you change nothing
|
|
36
95
|
|
|
37
96
|
Point it at a recording you already have. No node joins anyone's launch file, no
|
|
38
97
|
robot is redeployed, and `doctor --from-bag` needs no features at all:
|
|
39
98
|
|
|
40
99
|
```sh
|
|
41
|
-
|
|
42
|
-
|
|
100
|
+
# No clone and no Rust toolchain. The musl build is static: it needs no system
|
|
101
|
+
# library, so it runs on Ubuntu 20.04 and in a distroless container alike.
|
|
102
|
+
TAG=$(curl -fsSL https://api.github.com/repos/NoeFontana/tf_tree/releases/latest \
|
|
103
|
+
| grep -m1 '"tag_name"' | cut -d'"' -f4)
|
|
104
|
+
curl -fsSL "https://github.com/NoeFontana/tf_tree/releases/download/${TAG}/tf_tree-${TAG}-x86_64-unknown-linux-musl.tar.gz" | tar xz
|
|
105
|
+
tft=./tf_tree-${TAG}-x86_64-unknown-linux-musl/tf_tree # stay where your recording is
|
|
106
|
+
|
|
107
|
+
$tft doctor --from-bag drive.mcap # what is wrong with this /tf traffic
|
|
108
|
+
$tft freeze --from-bag drive.mcap -o drive.tft # keep the answer
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Both released builds carry `--features shm`, so the same binary also attaches to
|
|
112
|
+
a robot that is already running (`--attach`, `tf_tree top`, `tf_tree
|
|
113
|
+
participants`). The `-gnu` archive is the dynamically-linked build and needs
|
|
114
|
+
glibc 2.34 or newer (Ubuntu 22.04 / ROS 2 Humble and later); take it if you
|
|
115
|
+
would rather a glibc security fix reach this binary through your distribution
|
|
116
|
+
than wait for a new release. Otherwise take musl. From a clone the equivalent is
|
|
117
|
+
`cargo install --path crates/tf_tree_cli --features shm`.
|
|
118
|
+
|
|
119
|
+
The same two steps run from Python, with no CLI and no clone — which is what
|
|
120
|
+
`tf_tree_ingest` was made a library crate for
|
|
121
|
+
([`0046`](./docs/decisions/0046-the-consumer-the-crate-boundary-was-drawn-for.md)):
|
|
43
122
|
|
|
44
|
-
|
|
45
|
-
tf_tree
|
|
123
|
+
```python
|
|
124
|
+
import tf_tree
|
|
125
|
+
|
|
126
|
+
tree = tf_tree.ingest_bag("drive.mcap") # returns the ordinary Tree
|
|
127
|
+
tree.freeze("drive.tft") # records drive.mcap's BLAKE3 digest
|
|
128
|
+
print(tree.source["transforms"], tree.source["digest"][:16])
|
|
46
129
|
```
|
|
47
130
|
|
|
131
|
+
`tree.source` is how a `.tft` stays traceable to the recording it came from, and
|
|
132
|
+
`freeze` writes that digest for you — so there is no second `freeze_bag` call to
|
|
133
|
+
remember. Taking a publisher on the tree drops it, because from that point the
|
|
134
|
+
tree may hold samples the recording does not.
|
|
135
|
+
|
|
48
136
|
`drive.tft` is a **frozen transform index**, and it is the arena itself written
|
|
49
137
|
to disk. There are no pointers anywhere in the arena — every internal reference
|
|
50
138
|
is an offset — so opening one is an `mmap`, with no parsing, no deserialization
|
|
@@ -76,39 +164,28 @@ the ability to query at arbitrary times, or runs a ROS node to serve transforms
|
|
|
76
164
|
during training. This replaces all three and asks nobody to migrate anything,
|
|
77
165
|
which is also why it is the part that shipped first.
|
|
78
166
|
|
|
167
|
+
**Running it inside a loop instead?** `just control-loop` is the other half of
|
|
168
|
+
this page — `cargo run --release -p tf_tree --features shm --example control_loop`.
|
|
169
|
+
It is a 1 kHz controller against a 200 Hz estimate, showing the four things a
|
|
170
|
+
runtime consumer has to get right (compile the plan once, hoist the guard,
|
|
171
|
+
extrapolate on purpose and read how far it reached, treat a contended slot as
|
|
172
|
+
data) and printing the tail a deadline is set against. The offline path above
|
|
173
|
+
asks nobody to change their robot; this one is what happens when they do.
|
|
174
|
+
|
|
175
|
+
**Two processes, not two threads?** `just two-processes` — the capability this
|
|
176
|
+
page leads with, as something you can run. It spawns a publisher and a consumer
|
|
177
|
+
as separate processes and prints what each saw: the publisher declares the
|
|
178
|
+
topology (including a *static* sensor mount, which folds to one multiply and
|
|
179
|
+
needs no publisher ever), the consumer waits for it with `Open::await_open`
|
|
180
|
+
rather than depending on launch order, and then reads a transform and how far
|
|
181
|
+
past the newest sample it had to reach.
|
|
182
|
+
|
|
79
183
|
**Numbers belong where they can be reproduced**, not in this section.
|
|
80
184
|
`just bench-report` measures your host and writes
|
|
81
185
|
`report/{results.json,index.html}`; the standing figures and their caveats are in
|
|
82
186
|
[`docs/benchmarks/`](./docs/benchmarks/). *Benchmarks, and what they are worth*
|
|
83
187
|
below explains why a row there may legitimately read `UNAVAILABLE`.
|
|
84
188
|
|
|
85
|
-
## Status
|
|
86
|
-
|
|
87
|
-
**On crates.io from 0.0.1; on PyPI from 0.0.2.** The five engine crates are
|
|
88
|
-
published — `cargo add tf_tree`. The Python wheel starts at 0.0.2, because the
|
|
89
|
-
0.0.1 commit did not compile off Linux and no wheel for it exists or can; see
|
|
90
|
-
[`CHANGELOG.md`](./CHANGELOG.md). **`0.0.x` promises nothing between releases** —
|
|
91
|
-
cargo treats every one as incompatible with every other, so pin exactly.
|
|
92
|
-
|
|
93
|
-
| Phase | What it is | Status |
|
|
94
|
-
|---|---|---|
|
|
95
|
-
| 1 | Single-process engine: arena, seqlock buffers, plans, SE(3) math | **Implemented** |
|
|
96
|
-
| 2 | Shared memory: rendezvous, fd passing, claims as leases, reaping | **Implemented**, except the daemon/recorder surface (§9–§10) and §11.3's fault injection. §3.5's ownership migration has the protocol and not the trigger: kill the arena's owner and lookups keep being served, but no new process can join |
|
|
97
|
-
| 3 | Python bindings (PyO3, zero intermediate allocation) | **Implemented** |
|
|
98
|
-
| 4 | C ABI, C++ wrapper, ROS 2 ingest bridge, derivatives | **Done, except §5.9's affinity knobs and §6.3's replay rows.** `at_with_derivatives`, both headers, the header-only C++ wrapper with its CMake package, and **both halves of the ingest bridge** — the `rclcpp` package in `ros/tf_tree_ros` included. §7's benchmark gate is partial |
|
|
99
|
-
| 5 | Frozen `.tft` arena, bag ingestion, diagnostics, `tf_tree top` | **Mostly done.** `FORMAT_VERSION = 3`, the frozen arena (§2), the offline Python API (§4), the §5 counters and `tf_tree top` — terminal *and* `--web` (§7) — all landed. Ingestion is **MCAP only** (§3); the `TFT001`–`TFT019` catalogue reports all nineteen ids, of which sixteen can detect (§6). §8 is **deliberately not built**. §9's benchmark artifact and §10's release readiness are partial |
|
|
100
|
-
| 6–8 | Multi-host, `tf2` compatibility shim, replication | Not started; Phase 7 is gated by D21 and none of its four gates is met |
|
|
101
|
-
|
|
102
|
-
**The per-phase `§0.0` tables in [`docs/`](./docs/) are the source of truth**, not
|
|
103
|
-
this one — [`PHASE2.md`](./docs/PHASE2.md#00-implementation-status),
|
|
104
|
-
[`PHASE4.md`](./docs/PHASE4.md#00-implementation-status),
|
|
105
|
-
[`PHASE5.md`](./docs/PHASE5.md#00-implementation-status). If this table and one
|
|
106
|
-
of those disagree, the phase document is right and this is stale.
|
|
107
|
-
|
|
108
|
-
**CI runs again as of 2026-08-16**, after a gap since 2026-07-23 that ended
|
|
109
|
-
when this repository was made public. A green check is evidence once more — of
|
|
110
|
-
what the jobs cover. Gate locally with `just` first; CI is the second opinion.
|
|
111
|
-
|
|
112
189
|
## First five minutes, with no data at all
|
|
113
190
|
|
|
114
191
|
The block above assumes you have a recording. This one assumes only the clone:
|
|
@@ -134,7 +211,44 @@ so the printed translation is their interpolated midpoint, and `plan()` is the
|
|
|
134
211
|
object you keep — compiling the route once and evaluating it many times is the
|
|
135
212
|
whole shape of the fast path.
|
|
136
213
|
|
|
137
|
-
|
|
214
|
+
The same shape in Rust, which is where the engine actually lives. This block is
|
|
215
|
+
compiled by `cargo test --doc`, so it cannot drift away from the API:
|
|
216
|
+
|
|
217
|
+
```rust
|
|
218
|
+
use tf_tree::{Capacity, EdgeCfg, Iso3, Quat, Stamp, TreeBuilder, Vec3};
|
|
219
|
+
|
|
220
|
+
// Topology is declared up front: `build()` sizes one flat arena from exactly
|
|
221
|
+
// these edges, and nothing allocates after it returns.
|
|
222
|
+
let tree = TreeBuilder::new()
|
|
223
|
+
.static_edge("base_link", "lidar_top", &Iso3::IDENTITY) // (parent, child)
|
|
224
|
+
.dynamic_edge("odom", "base_link", EdgeCfg::new(Capacity::history(100.0, 10.0)))
|
|
225
|
+
.build()
|
|
226
|
+
.expect("layout");
|
|
227
|
+
|
|
228
|
+
let odom = tree.frame("odom").expect("declared");
|
|
229
|
+
let base_link = tree.frame("base_link").expect("declared");
|
|
230
|
+
let lidar_top = tree.frame("lidar_top").expect("declared");
|
|
231
|
+
|
|
232
|
+
// One writer per edge, enforced by the claim table rather than by convention.
|
|
233
|
+
// Note the order flips: the builder takes (parent, child), `claim` takes
|
|
234
|
+
// (child, parent). Both are annotated here because getting it wrong builds a
|
|
235
|
+
// silently inverted tree rather than failing.
|
|
236
|
+
let w = tree.claim(base_link, odom).expect("unclaimed"); // (child, parent)
|
|
237
|
+
let at_x = |x| Iso3::new(Quat::IDENTITY, Vec3::new(x, 0.0, 0.0));
|
|
238
|
+
w.push(1_000_000_000, &at_x(0.0)).expect("monotonic"); // integer nanoseconds
|
|
239
|
+
w.push(1_010_000_000, &at_x(1.0)).expect("monotonic");
|
|
240
|
+
|
|
241
|
+
// Compile the route once, evaluate it many times.
|
|
242
|
+
let plan = tree.plan(odom, lidar_top).expect("connected");
|
|
243
|
+
let g = tree.guard();
|
|
244
|
+
let t: Stamp = Stamp::from_nanos(1_005_000_000);
|
|
245
|
+
let pose = plan.at(&g, t).expect("in range");
|
|
246
|
+
assert!((pose.t.x - 0.5).abs() < 1e-12);
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
[`crates/tf_tree/README.md`](./crates/tf_tree/README.md) carries the annotated
|
|
250
|
+
version, including what a failed lookup prints. Two things surprise people about
|
|
251
|
+
both languages, and both are deliberate:
|
|
138
252
|
|
|
139
253
|
- **Stamps are integer nanoseconds.** There is no float-seconds overload. At a
|
|
140
254
|
2026 epoch the ULP of `float64` seconds is 238 ns, so every interval in a
|
|
@@ -144,11 +258,90 @@ Two things surprise people, both deliberate:
|
|
|
144
258
|
copy" here means no *intermediate* allocation — use `Plan.at_into` to supply
|
|
145
259
|
the destination.
|
|
146
260
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
261
|
+
`just` alone lists everything the repository can do.
|
|
262
|
+
|
|
263
|
+
## Where it fits, and where it does not
|
|
264
|
+
|
|
265
|
+
The fastest way to evaluate this is to find yourself on one of these two lists.
|
|
266
|
+
The second one is not a roadmap: most of its rows are decisions, recorded, that
|
|
267
|
+
will not be reversed.
|
|
268
|
+
|
|
269
|
+
**Reach for `tf_tree` when:**
|
|
270
|
+
|
|
271
|
+
- You look up transforms **inside a loop that has a deadline** — a controller, a
|
|
272
|
+
perception front end — and the per-lookup cost is a thing you have measured.
|
|
273
|
+
- **Many readers share one host**: threads in a process, or several processes.
|
|
274
|
+
One arena serves all of them, read-only by default, with no middleware between
|
|
275
|
+
the reader and the bytes.
|
|
276
|
+
- You train on recorded data and want **transforms in the dataloader** without a
|
|
277
|
+
ROS node in the training loop, or a pickle of precomputed poses that can no
|
|
278
|
+
longer answer at an arbitrary time.
|
|
279
|
+
- Your edges are **fast**, kilohertz-class, and float-seconds stamps have
|
|
280
|
+
already cost you resolution.
|
|
281
|
+
- You need to **debug** a transform tree — typed errors that name the edge, the
|
|
282
|
+
`TFT001`–`TFT019` catalogue, `tf_tree top`, and `doctor` against a bag with
|
|
283
|
+
nothing deployed.
|
|
284
|
+
|
|
285
|
+
**Look elsewhere when:**
|
|
286
|
+
|
|
287
|
+
| You need | Why not this | Where it is written down |
|
|
288
|
+
|---|---|---|
|
|
289
|
+
| A drop-in `tf2_ros::Buffer` | Phase 7, gated on operating evidence, not scheduled. What exists is a one-way ingest bridge | [`PHASE7.md`](./docs/PHASE7.md) §0.0 |
|
|
290
|
+
| Covariance or joint uncertainty | A tree cannot compose a correct one; composing marginals as independent is wrong in the optimistic direction. You need a factor graph | [`PROJECT.md`](./docs/PROJECT.md) §1, [`0009`](./docs/decisions/0009-descoping-phase-6.md) |
|
|
291
|
+
| Multi-parent frames, loop closure, copy-on-write branches | Multi-parent is the row above. Copy-on-write was cut for reasons of its own: it serves the use case D2 rejects *and* contradicts fixed capacity, one-writer-per-edge and append-only ids at once | [`0009`](./docs/decisions/0009-descoping-phase-6.md), [`PROJECT.md`](./docs/PROJECT.md) §5 D2 |
|
|
292
|
+
| Transforms across hosts | Phase 8. Not started | [`PROJECT.md`](./docs/PROJECT.md) §4 |
|
|
293
|
+
| Shared memory or `.tft` off Linux | The engine compiles; the mapping code does not exist elsewhere | [`SUPPORT.md`](./SUPPORT.md) |
|
|
294
|
+
| A viewer, or point-cloud deskewing | Deliberately absent, argument recorded. `at_adaptive` emits knots; the consumer transforms points where they already live | [`PHASE5.md`](./docs/PHASE5.md) §8, [`PROJECT.md`](./docs/PROJECT.md) §5 D8 |
|
|
295
|
+
| An API that will not move under you | `0.0.x`: every release may break every other | [`CHANGELOG.md`](./CHANGELOG.md) |
|
|
296
|
+
|
|
297
|
+
## Status
|
|
298
|
+
|
|
299
|
+
**On crates.io from 0.0.1; on PyPI from 0.0.2.** The five engine crates are
|
|
300
|
+
published — `cargo add tf_tree`. The Python wheel starts at 0.0.2 because the
|
|
301
|
+
0.0.1 commit did not compile off Linux, so no wheel for it exists or can; see
|
|
302
|
+
[`CHANGELOG.md`](./CHANGELOG.md).
|
|
303
|
+
|
|
304
|
+
| Phase | What it is | Status |
|
|
305
|
+
|---|---|---|
|
|
306
|
+
| 1 | Single-process engine: arena, seqlock buffers, plans, SE(3) math | **Implemented** |
|
|
307
|
+
| 2 | Shared memory: rendezvous, fd passing, claims as leases, reaping | **Implemented**, with gaps |
|
|
308
|
+
| 3 | Python bindings (PyO3, zero intermediate allocation) | **Implemented** |
|
|
309
|
+
| 4 | C ABI, C++ wrapper, ROS 2 ingest bridge, derivatives | **Implemented**, with gaps |
|
|
310
|
+
| 5 | Frozen `.tft` arena, bag ingestion, diagnostics, `tf_tree top` | **Mostly implemented** |
|
|
311
|
+
| 6–8 | Continuous-time interpolation, `tf2` shim, multi-host replication | Not started |
|
|
312
|
+
|
|
313
|
+
The gaps, named — because "with gaps" on its own is not a status:
|
|
314
|
+
|
|
315
|
+
- **Phase 2** — the daemon and recorder surface (§9–§10) are absent, and
|
|
316
|
+
§11.3's fault injection is a separate gap still being worked. §3.5's ownership
|
|
317
|
+
migration **landed on 2026-08-28**: kill the arena's owner and a surviving
|
|
318
|
+
read-write participant inherits the role, so new processes can join again. Its
|
|
319
|
+
trigger is caller-driven — `Tree::owner_lost()` is a non-blocking check a
|
|
320
|
+
survivor makes in its own loop, and nothing makes it for you, because there is
|
|
321
|
+
no daemon.
|
|
322
|
+
- **Phase 4** — everything except §5.9's affinity knobs and §6.3's replay rows.
|
|
323
|
+
`at_with_derivatives`, both headers, the header-only C++ wrapper with its
|
|
324
|
+
CMake package, and **both halves of the ingest bridge** — the `rclcpp` package
|
|
325
|
+
in `ros/tf_tree_ros` included. §7's benchmark gate is partial, and §1's exit
|
|
326
|
+
criterion is **operational, not a feature list**: it is open, and no amount of
|
|
327
|
+
code closes it.
|
|
328
|
+
- **Phase 5** — `FORMAT_VERSION = 3`, the frozen arena (§2), the offline Python
|
|
329
|
+
API (§4), the §5 counters and `tf_tree top` (terminal *and* `--web`, §7) have
|
|
330
|
+
all landed. Ingestion is **MCAP only** (§3). The `TFT001`–`TFT019` catalogue
|
|
331
|
+
reports all nineteen ids, of which seventeen can detect (§6). §8 is
|
|
332
|
+
**deliberately not built**. §9's benchmark artifact and §10's release
|
|
333
|
+
readiness are partial.
|
|
334
|
+
- **Phase 7** is gated by D21 and none of its four gates is met.
|
|
335
|
+
|
|
336
|
+
**The per-phase `§0.0` tables in [`docs/`](./docs/) are the source of truth**, not
|
|
337
|
+
this one — [`PHASE2.md`](./docs/PHASE2.md#00-implementation-status),
|
|
338
|
+
[`PHASE4.md`](./docs/PHASE4.md#00-implementation-status),
|
|
339
|
+
[`PHASE5.md`](./docs/PHASE5.md#00-implementation-status). If this table and one
|
|
340
|
+
of those disagree, the phase document is right and this is stale.
|
|
341
|
+
|
|
342
|
+
**CI runs again as of 2026-08-16**, after a gap since 2026-07-23 that ended
|
|
343
|
+
when this repository was made public. A green check is evidence once more — of
|
|
344
|
+
what the jobs cover. Gate locally with `just` first; CI is the second opinion.
|
|
152
345
|
|
|
153
346
|
## Is this a `tf2` replacement?
|
|
154
347
|
|
|
@@ -223,7 +416,7 @@ defend against a hazard they do not have (`docs/PHASE5.md` §4.3).
|
|
|
223
416
|
|
|
224
417
|
## Workspace
|
|
225
418
|
|
|
226
|
-
```
|
|
419
|
+
```text
|
|
227
420
|
crates/
|
|
228
421
|
├── tf_tree_math/ no_std SE(3)/SO(3) + dual quaternions; #![forbid(unsafe_code)]
|
|
229
422
|
├── tf_tree_arena/ no_std+alloc pointer-free arena + layout math
|
|
@@ -292,17 +485,39 @@ regenerates the baseline; that diff belongs in the commit that causes it.
|
|
|
292
485
|
Standing numbers and their caveats live in
|
|
293
486
|
[`docs/benchmarks/`](./docs/benchmarks/).
|
|
294
487
|
|
|
295
|
-
##
|
|
488
|
+
## Where to go next
|
|
489
|
+
|
|
490
|
+
**Using it.**
|
|
491
|
+
|
|
492
|
+
1. [`docs/API.md`](./docs/API.md) — the cross-cutting contract: six rules (§1)
|
|
493
|
+
every binding obeys, and the normative Rust, Python, C and C++ surfaces
|
|
494
|
+
(§2–§5). This is the reference.
|
|
495
|
+
2. [`docs/RUNBOOK.md`](./docs/RUNBOOK.md) — organised by **symptom**, because
|
|
496
|
+
that is what you have when a robot's transform tree misbehaves. Every row
|
|
497
|
+
names an error type and, where one exists, the `doctor` check that finds it.
|
|
498
|
+
3. [`docs/benchmarks/`](./docs/benchmarks/) — the standing measurements, each
|
|
499
|
+
row naming the command that produced it.
|
|
500
|
+
|
|
501
|
+
**Changing it.**
|
|
296
502
|
|
|
297
503
|
1. [`docs/PROJECT.md`](./docs/PROJECT.md) — overview, architecture, roadmap, and
|
|
298
|
-
the decision log D1–D22 in §5.
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
504
|
+
the decision log D1–D22 in §5. Read it before proposing anything: several
|
|
505
|
+
obvious-looking simplifications are excluded on purpose, and the reasons are
|
|
506
|
+
there.
|
|
507
|
+
2. The phase spec you are touching: [`PHASE1`](./docs/PHASE1.md) …
|
|
508
|
+
[`PHASE5`](./docs/PHASE5.md). `PHASE2`, `PHASE4`, `PHASE5` and `PHASE7` open
|
|
509
|
+
with a `§0.0` status table, and it outranks every other document including
|
|
510
|
+
this one. `PHASE1` has none because Phase 1 is implemented whole, and
|
|
511
|
+
`PHASE3` has none because it records deviations inline, in the section each
|
|
512
|
+
belongs to.
|
|
513
|
+
3. [`docs/API.md`](./docs/API.md) again, and **before** writing any public
|
|
514
|
+
surface — §1's six rules generate every binding, and §7 is the checklist a
|
|
515
|
+
new surface passes. It authorizes nothing on its own: its §6 delta table
|
|
516
|
+
names the phase or decision record each row lands in.
|
|
517
|
+
4. [`docs/decisions/`](./docs/decisions/) — the records for what the phase specs
|
|
518
|
+
do not cover, and where a change of that kind starts.
|
|
519
|
+
5. [`CONTRIBUTING.md`](./CONTRIBUTING.md) — the local gates, and the order to
|
|
520
|
+
run them in.
|
|
306
521
|
|
|
307
522
|
## Contributing and support
|
|
308
523
|
|