From 89f500b0da25539834ea229d812e2c5309d69752 Mon Sep 17 00:00:00 2001 From: James Ross Date: Sun, 29 Mar 2026 15:03:18 -0700 Subject: [PATCH 01/10] chore(gitignore): ignore editor report --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index bfccc3bb..decead69 100644 --- a/.gitignore +++ b/.gitignore @@ -47,6 +47,7 @@ temp artifacts/pr-review/ __pycache__/ *.pyc +EDITORS-REPORT.md # Playwright artifacts test-results From 2edadb8b2f776e2b8642a68d32961b45baa286e1 Mon Sep 17 00:00:00 2001 From: James Ross Date: Sun, 29 Mar 2026 15:35:21 -0700 Subject: [PATCH 02/10] docs: cull archive and tighten live docs surface --- README.md | 2 +- docs/DETERMINISTIC_MATH.md | 1 - docs/SPEC_DETERMINISTIC_MATH.md | 2 - docs/architecture-outline.md | 25 +- docs/archive/AGENTS.md | 183 -- docs/archive/ISSUES_MATRIX.md | 104 - docs/archive/README.md | 12 - docs/archive/ROADMAP/ECHO_ROADMAP.md | 164 -- docs/archive/ROLLBACK_TTD.md | 88 - docs/archive/aion-papers-bridge.md | 231 -- docs/archive/branch-merge-playbook.md | 90 - docs/archive/capability-ownership-matrix.md | 115 - docs/archive/code-map.md | 71 - docs/archive/determinism-invariants.md | 8 - docs/archive/determinism/DETERMINISM-AUDIT.md | 323 --- .../determinism/DIND-MISSION-PHASE3.md | 38 - .../determinism/DIND-MISSION-PHASE5.md | 39 - docs/archive/determinism/DIND-MISSION.md | 54 - docs/archive/diagrams.md | 196 -- docs/archive/guide/collision-tour.md | 10 - docs/archive/hash-graph.md | 52 - docs/archive/jitos/spec-0000.md | 370 --- docs/archive/math-validation-plan.md | 175 -- .../memorials/2026-01-18-phase4-rubicon.md | 113 - docs/archive/notes/AFTER.webp | Bin 65536 -> 0 bytes docs/archive/notes/BEFORE.webp | Bin 65652 -> 0 bytes docs/archive/notes/Final.webp | Bin 109818 -> 0 bytes docs/archive/notes/boaw-perf-baseline.md | 148 -- .../notes/claude-musings-on-determinism.md | 114 - ...deterministic-trig-implementation-guide.md | 311 --- docs/archive/notes/project-tour-2025-12-28.md | 189 -- .../notes/scheduler-optimization-followups.md | 447 ---- .../notes/scheduler-radix-optimization-2.md | 349 --- .../notes/scheduler-radix-optimization.md | 465 ---- docs/archive/notes/xtask-wizard.md | 51 - docs/archive/phase1-plan.md | 130 -- docs/archive/plans/BOAW-tech-debt.md | 315 --- docs/archive/plans/COMING_SOON.md | 125 - docs/archive/plans/SPEC-0004-final-plan.md | 249 -- .../archive/plans/SPEC-0004-review-hitlist.md | 149 -- docs/archive/plans/cross-warp-parallelism.md | 106 - .../plans/per-warp-time-sovereignty.md | 852 ------- docs/archive/release-criteria.md | 36 - docs/archive/rfc/mat-bus-finish.md | 662 ------ docs/archive/roadmap-mwmr-mini-epic.md | 84 - docs/archive/runtime-diagnostics-plan.md | 64 - docs/archive/rust-rhai-ts-division.md | 87 - docs/archive/scheduler-benchmarks.md | 25 - docs/archive/scheduler-reserve-complexity.md | 12 - docs/archive/scheduler-reserve-validation.md | 23 - docs/archive/spec-deterministic-math.md | 213 -- docs/archive/spec-geom-collision.md | 42 - docs/archive/study/aion.cls | 175 -- docs/archive/study/build-tour.py | 260 --- docs/archive/study/diagrams/tour-01.mmd | 39 - docs/archive/study/diagrams/tour-01.pdf | Bin 28280 -> 0 bytes docs/archive/study/diagrams/tour-01.svg | 1 - docs/archive/study/diagrams/tour-02.mmd | 24 - docs/archive/study/diagrams/tour-02.pdf | Bin 35626 -> 0 bytes docs/archive/study/diagrams/tour-02.svg | 1 - docs/archive/study/diagrams/tour-03.mmd | 20 - docs/archive/study/diagrams/tour-03.pdf | Bin 33507 -> 0 bytes docs/archive/study/diagrams/tour-03.svg | 1 - docs/archive/study/diagrams/tour-04.mmd | 16 - docs/archive/study/diagrams/tour-04.pdf | Bin 20119 -> 0 bytes docs/archive/study/diagrams/tour-04.svg | 1 - docs/archive/study/diagrams/tour-05.mmd | 8 - docs/archive/study/diagrams/tour-05.pdf | Bin 20590 -> 0 bytes docs/archive/study/diagrams/tour-05.svg | 1 - docs/archive/study/diagrams/tour-06.mmd | 29 - docs/archive/study/diagrams/tour-06.pdf | Bin 24688 -> 0 bytes docs/archive/study/diagrams/tour-06.svg | 1 - docs/archive/study/diagrams/tour-07.mmd | 7 - docs/archive/study/diagrams/tour-07.pdf | Bin 20041 -> 0 bytes docs/archive/study/diagrams/tour-07.svg | 1 - docs/archive/study/diagrams/tour-08.mmd | 9 - docs/archive/study/diagrams/tour-08.pdf | Bin 21688 -> 0 bytes docs/archive/study/diagrams/tour-08.svg | 1 - docs/archive/study/diagrams/tour-09.mmd | 7 - docs/archive/study/diagrams/tour-09.pdf | Bin 19991 -> 0 bytes docs/archive/study/diagrams/tour-09.svg | 1 - docs/archive/study/diagrams/tour-10.mmd | 29 - docs/archive/study/diagrams/tour-10.pdf | Bin 26358 -> 0 bytes docs/archive/study/diagrams/tour-10.svg | 1 - docs/archive/study/diagrams/tour-11.mmd | 17 - docs/archive/study/diagrams/tour-11.pdf | Bin 23624 -> 0 bytes docs/archive/study/diagrams/tour-11.svg | 1 - docs/archive/study/diagrams/tour-12.mmd | 16 - docs/archive/study/diagrams/tour-12.pdf | Bin 24080 -> 0 bytes docs/archive/study/diagrams/tour-12.svg | 1 - docs/archive/study/diagrams/tour-13.mmd | 7 - docs/archive/study/diagrams/tour-13.pdf | Bin 29205 -> 0 bytes docs/archive/study/diagrams/tour-13.svg | 1 - docs/archive/study/diagrams/tour-14.mmd | 6 - docs/archive/study/diagrams/tour-14.pdf | Bin 19399 -> 0 bytes docs/archive/study/diagrams/tour-14.svg | 1 - docs/archive/study/diagrams/tour-15.mmd | 21 - docs/archive/study/diagrams/tour-15.pdf | Bin 21609 -> 0 bytes docs/archive/study/diagrams/tour-15.svg | 1 - .../study/echo-tour-de-code-directors-cut.pdf | Bin 390650 -> 0 bytes .../study/echo-tour-de-code-directors-cut.tex | 1330 ----------- .../echo-tour-de-code-with-commentary.pdf | Bin 359333 -> 0 bytes .../echo-tour-de-code-with-commentary.tex | 2016 ----------------- docs/archive/study/echo-tour-de-code.md | 1355 ----------- docs/archive/study/echo-tour-de-code.pdf | Bin 97927 -> 0 bytes docs/archive/study/echo-tour-de-code.tex | 1560 ------------- .../study/echo-visual-atlas-with-diagrams.pdf | Bin 88449 -> 0 bytes .../study/echo-visual-atlas-with-diagrams.tex | 279 --- docs/archive/study/echo-visual-atlas.md | 662 ------ docs/archive/study/echo-visual-atlas.tex | 760 ------- docs/archive/study/extract-mermaid.py | 137 -- docs/archive/study/inject-diagrams.py | 112 - docs/archive/study/macros.tex | 13 - docs/archive/study/paper-7eee.pdf | Bin 437068 -> 0 bytes docs/archive/study/paper-7eee.tex | 1315 ----------- docs/archive/study/refs.bib | 111 - docs/archive/study/render-tour-diagrams.py | 91 - .../study/what-makes-echo-tick-processed.md | 1121 --------- .../what-makes-echo-tick-with-diagrams.pdf | Bin 142870 -> 0 bytes .../what-makes-echo-tick-with-diagrams.tex | 1515 ------------- docs/archive/study/what-makes-echo-tick.md | 1198 ---------- docs/archive/study/what-makes-echo-tick.pdf | Bin 405468 -> 0 bytes docs/archive/study/what-makes-echo-tick.tex | 1627 ------------- docs/archive/tasks.md | 10 - docs/archive/tasks/TASKS.md | 203 -- docs/archive/tasks/WASM-TASKS.md | 48 - docs/archive/tasks/issue-canonical-f32.md | 44 - docs/archive/telemetry-graph-replay.md | 72 - docs/archive/testing-and-replay-plan.md | 115 - docs/archive/two-lane-abi.md | 59 - docs/archive/warp-demo-roadmap.md | 83 - docs/archive/warp-runtime-architecture.md | 83 - .../dags/tasks-dag-source.md} | 0 docs/assets/dags/tasks-dag.dot | 4 +- docs/assets/dags/tasks-dag.svg | 8 +- docs/dependency-dags.md | 8 +- docs/guide/eli5.md | 2 +- docs/guide/start-here.md | 9 +- docs/index.md | 39 +- docs/meta/README.md | 12 - docs/meta/docs-audit.md | 160 -- docs/meta/docs-index.md | 129 -- docs/meta/legacy-excavation.md | 35 - docs/public/collision-dpo-tour.html | 926 ++++++-- docs/workflows.md | 16 - scripts/check-append-only.js | 2 +- scripts/generate-dependency-dags.js | 4 +- scripts/generate-tasks-dag.js | 2 +- tests/hooks/test_dependency_dags.sh | 11 +- xtask/src/main.rs | 2 +- 150 files changed, 803 insertions(+), 24904 deletions(-) delete mode 100644 docs/archive/AGENTS.md delete mode 100644 docs/archive/ISSUES_MATRIX.md delete mode 100644 docs/archive/README.md delete mode 100644 docs/archive/ROADMAP/ECHO_ROADMAP.md delete mode 100644 docs/archive/ROLLBACK_TTD.md delete mode 100644 docs/archive/aion-papers-bridge.md delete mode 100644 docs/archive/branch-merge-playbook.md delete mode 100644 docs/archive/capability-ownership-matrix.md delete mode 100644 docs/archive/code-map.md delete mode 100644 docs/archive/determinism-invariants.md delete mode 100644 docs/archive/determinism/DETERMINISM-AUDIT.md delete mode 100644 docs/archive/determinism/DIND-MISSION-PHASE3.md delete mode 100644 docs/archive/determinism/DIND-MISSION-PHASE5.md delete mode 100644 docs/archive/determinism/DIND-MISSION.md delete mode 100644 docs/archive/diagrams.md delete mode 100644 docs/archive/guide/collision-tour.md delete mode 100644 docs/archive/hash-graph.md delete mode 100644 docs/archive/jitos/spec-0000.md delete mode 100644 docs/archive/math-validation-plan.md delete mode 100644 docs/archive/memorials/2026-01-18-phase4-rubicon.md delete mode 100644 docs/archive/notes/AFTER.webp delete mode 100644 docs/archive/notes/BEFORE.webp delete mode 100644 docs/archive/notes/Final.webp delete mode 100644 docs/archive/notes/boaw-perf-baseline.md delete mode 100644 docs/archive/notes/claude-musings-on-determinism.md delete mode 100644 docs/archive/notes/f32scalar-deterministic-trig-implementation-guide.md delete mode 100644 docs/archive/notes/project-tour-2025-12-28.md delete mode 100644 docs/archive/notes/scheduler-optimization-followups.md delete mode 100644 docs/archive/notes/scheduler-radix-optimization-2.md delete mode 100644 docs/archive/notes/scheduler-radix-optimization.md delete mode 100644 docs/archive/notes/xtask-wizard.md delete mode 100644 docs/archive/phase1-plan.md delete mode 100644 docs/archive/plans/BOAW-tech-debt.md delete mode 100644 docs/archive/plans/COMING_SOON.md delete mode 100644 docs/archive/plans/SPEC-0004-final-plan.md delete mode 100644 docs/archive/plans/SPEC-0004-review-hitlist.md delete mode 100644 docs/archive/plans/cross-warp-parallelism.md delete mode 100644 docs/archive/plans/per-warp-time-sovereignty.md delete mode 100644 docs/archive/release-criteria.md delete mode 100644 docs/archive/rfc/mat-bus-finish.md delete mode 100644 docs/archive/roadmap-mwmr-mini-epic.md delete mode 100644 docs/archive/runtime-diagnostics-plan.md delete mode 100644 docs/archive/rust-rhai-ts-division.md delete mode 100644 docs/archive/scheduler-benchmarks.md delete mode 100644 docs/archive/scheduler-reserve-complexity.md delete mode 100644 docs/archive/scheduler-reserve-validation.md delete mode 100644 docs/archive/spec-deterministic-math.md delete mode 100644 docs/archive/spec-geom-collision.md delete mode 100644 docs/archive/study/aion.cls delete mode 100644 docs/archive/study/build-tour.py delete mode 100644 docs/archive/study/diagrams/tour-01.mmd delete mode 100644 docs/archive/study/diagrams/tour-01.pdf delete mode 100644 docs/archive/study/diagrams/tour-01.svg delete mode 100644 docs/archive/study/diagrams/tour-02.mmd delete mode 100644 docs/archive/study/diagrams/tour-02.pdf delete mode 100644 docs/archive/study/diagrams/tour-02.svg delete mode 100644 docs/archive/study/diagrams/tour-03.mmd delete mode 100644 docs/archive/study/diagrams/tour-03.pdf delete mode 100644 docs/archive/study/diagrams/tour-03.svg delete mode 100644 docs/archive/study/diagrams/tour-04.mmd delete mode 100644 docs/archive/study/diagrams/tour-04.pdf delete mode 100644 docs/archive/study/diagrams/tour-04.svg delete mode 100644 docs/archive/study/diagrams/tour-05.mmd delete mode 100644 docs/archive/study/diagrams/tour-05.pdf delete mode 100644 docs/archive/study/diagrams/tour-05.svg delete mode 100644 docs/archive/study/diagrams/tour-06.mmd delete mode 100644 docs/archive/study/diagrams/tour-06.pdf delete mode 100644 docs/archive/study/diagrams/tour-06.svg delete mode 100644 docs/archive/study/diagrams/tour-07.mmd delete mode 100644 docs/archive/study/diagrams/tour-07.pdf delete mode 100644 docs/archive/study/diagrams/tour-07.svg delete mode 100644 docs/archive/study/diagrams/tour-08.mmd delete mode 100644 docs/archive/study/diagrams/tour-08.pdf delete mode 100644 docs/archive/study/diagrams/tour-08.svg delete mode 100644 docs/archive/study/diagrams/tour-09.mmd delete mode 100644 docs/archive/study/diagrams/tour-09.pdf delete mode 100644 docs/archive/study/diagrams/tour-09.svg delete mode 100644 docs/archive/study/diagrams/tour-10.mmd delete mode 100644 docs/archive/study/diagrams/tour-10.pdf delete mode 100644 docs/archive/study/diagrams/tour-10.svg delete mode 100644 docs/archive/study/diagrams/tour-11.mmd delete mode 100644 docs/archive/study/diagrams/tour-11.pdf delete mode 100644 docs/archive/study/diagrams/tour-11.svg delete mode 100644 docs/archive/study/diagrams/tour-12.mmd delete mode 100644 docs/archive/study/diagrams/tour-12.pdf delete mode 100644 docs/archive/study/diagrams/tour-12.svg delete mode 100644 docs/archive/study/diagrams/tour-13.mmd delete mode 100644 docs/archive/study/diagrams/tour-13.pdf delete mode 100644 docs/archive/study/diagrams/tour-13.svg delete mode 100644 docs/archive/study/diagrams/tour-14.mmd delete mode 100644 docs/archive/study/diagrams/tour-14.pdf delete mode 100644 docs/archive/study/diagrams/tour-14.svg delete mode 100644 docs/archive/study/diagrams/tour-15.mmd delete mode 100644 docs/archive/study/diagrams/tour-15.pdf delete mode 100644 docs/archive/study/diagrams/tour-15.svg delete mode 100644 docs/archive/study/echo-tour-de-code-directors-cut.pdf delete mode 100644 docs/archive/study/echo-tour-de-code-directors-cut.tex delete mode 100644 docs/archive/study/echo-tour-de-code-with-commentary.pdf delete mode 100644 docs/archive/study/echo-tour-de-code-with-commentary.tex delete mode 100644 docs/archive/study/echo-tour-de-code.md delete mode 100644 docs/archive/study/echo-tour-de-code.pdf delete mode 100644 docs/archive/study/echo-tour-de-code.tex delete mode 100644 docs/archive/study/echo-visual-atlas-with-diagrams.pdf delete mode 100644 docs/archive/study/echo-visual-atlas-with-diagrams.tex delete mode 100644 docs/archive/study/echo-visual-atlas.md delete mode 100644 docs/archive/study/echo-visual-atlas.tex delete mode 100755 docs/archive/study/extract-mermaid.py delete mode 100644 docs/archive/study/inject-diagrams.py delete mode 100644 docs/archive/study/macros.tex delete mode 100644 docs/archive/study/paper-7eee.pdf delete mode 100644 docs/archive/study/paper-7eee.tex delete mode 100644 docs/archive/study/refs.bib delete mode 100644 docs/archive/study/render-tour-diagrams.py delete mode 100644 docs/archive/study/what-makes-echo-tick-processed.md delete mode 100644 docs/archive/study/what-makes-echo-tick-with-diagrams.pdf delete mode 100644 docs/archive/study/what-makes-echo-tick-with-diagrams.tex delete mode 100644 docs/archive/study/what-makes-echo-tick.md delete mode 100644 docs/archive/study/what-makes-echo-tick.pdf delete mode 100644 docs/archive/study/what-makes-echo-tick.tex delete mode 100644 docs/archive/tasks.md delete mode 100644 docs/archive/tasks/TASKS.md delete mode 100644 docs/archive/tasks/WASM-TASKS.md delete mode 100644 docs/archive/tasks/issue-canonical-f32.md delete mode 100644 docs/archive/telemetry-graph-replay.md delete mode 100644 docs/archive/testing-and-replay-plan.md delete mode 100644 docs/archive/two-lane-abi.md delete mode 100644 docs/archive/warp-demo-roadmap.md delete mode 100644 docs/archive/warp-runtime-architecture.md rename docs/{archive/tasks/TASKS-DAG.md => assets/dags/tasks-dag-source.md} (100%) delete mode 100644 docs/meta/README.md delete mode 100644 docs/meta/docs-audit.md delete mode 100644 docs/meta/docs-index.md delete mode 100644 docs/meta/legacy-excavation.md diff --git a/README.md b/README.md index 187bb01d..b8664e25 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@

Get StartedArchitecture • - Docs • + DocsAIΩN Framework

diff --git a/docs/DETERMINISTIC_MATH.md b/docs/DETERMINISTIC_MATH.md index 13d3095e..e3ddec5c 100644 --- a/docs/DETERMINISTIC_MATH.md +++ b/docs/DETERMINISTIC_MATH.md @@ -21,7 +21,6 @@ is largely standardized, "freaky numbers" (NaN, Subnormals, Signed Zero) introdu > | -------------------------------------------------------------------------------- | -------------------------------------------- | > | [SPEC_DETERMINISTIC_MATH.md](SPEC_DETERMINISTIC_MATH.md) | **Normative policy** (this doc defers to it) | > | [warp-math-claims.md](warp-math-claims.md) | Claims and theory framing | -> | [math-validation-plan.md](archive/math-validation-plan.md) | Validation test plan and CI lanes (archived) | > | [determinism/DETERMINISM_CLAIMS_v0.1.md](determinism/DETERMINISM_CLAIMS_v0.1.md) | Formal determinism claims | ## 1. NaN Payloads diff --git a/docs/SPEC_DETERMINISTIC_MATH.md b/docs/SPEC_DETERMINISTIC_MATH.md index 3850b956..22970470 100644 --- a/docs/SPEC_DETERMINISTIC_MATH.md +++ b/docs/SPEC_DETERMINISTIC_MATH.md @@ -16,9 +16,7 @@ All math within the simulation loop (`warp-core`) must adhere to these rules. > | -------------------------------------------------------------------------------- | -------------------------------------------------- | > | [DETERMINISTIC_MATH.md](DETERMINISTIC_MATH.md) | Hazard catalog (IEEE 754 pitfalls and mitigations) | > | [warp-math-claims.md](warp-math-claims.md) | Claims and theory framing | -> | [math-validation-plan.md](math-validation-plan.md) | Validation test plan and CI lanes | > | [determinism/DETERMINISM_CLAIMS_v0.1.md](determinism/DETERMINISM_CLAIMS_v0.1.md) | Formal determinism claims | -> | [spec-deterministic-math.md](spec-deterministic-math.md) | Legacy Phase 0 design sketch (archived) | ## 1. Floating Point (f32) diff --git a/docs/architecture-outline.md b/docs/architecture-outline.md index e39a2c6d..b5e7e8d2 100644 --- a/docs/architecture-outline.md +++ b/docs/architecture-outline.md @@ -17,6 +17,22 @@ will lag behind the current Rust-first implementation; prefer WARP specs for the > - ⚠️ **Partial** — some aspects exist, others planned > - 🗺️ **Planned** — design only, not yet implemented +## What Exists Today + +Before the aspirational material below: Echo already has a real deterministic WARP runtime. + +- **`warp-core` rewrite engine** ✅: immutable snapshot reads, private deltas, canonical merge, and deterministic scheduling. +- **Playback / worldlines / provenance** ✅: recorded history, cursor replay, and append-only lineage support. +- **Renderer / scene boundary** ✅: a bit-exact scene port and canonical codec boundary. +- **TTD / browser tooling substrate** ✅: WASM-first protocol tooling and time-travel debugging infrastructure. + +Read the current implementation through these docs first: + +- [/spec-warp-core](/spec-warp-core) +- [/scheduler-warp-core](/scheduler-warp-core) +- [/spec/SPEC-0004-worldlines-playback-truthbus](/spec/SPEC-0004-worldlines-playback-truthbus) +- [/warp-two-plane-law](/warp-two-plane-law) + ## Vision - Reimagine a battle-tested ECS core into **Echo**, a renderer-agnostic spine that survives browsers, native shells, and whatever 2125 invents next. @@ -192,13 +208,6 @@ TTD is a first-class citizen in Echo, built on top of the provenance and scene p - **Security & Sandbox**: Optional restrictions for user-generated content or multiplayer host/client boundaries; capability-based access to ports. - **Extensibility**: Plugins define new components, systems, adapters, or editor tools; registration API enforces namespace isolation and version checks. -## Legacy Excavation Log - -- **Goal**: Track every legacy file, classify (keep concept, redesign, discard), note dependencies (Mootools, globals, duplicate IDs), and record learnings to inform Echo. -- **Artifacts**: `docs/meta/legacy-excavation.md` (to be populated) with columns for file, role, verdict, action items, and notes. -- **Process**: Review file → summarize intent → capture bugs/gaps → map to Echo’s modules → decide migration path or deprecation. -- **Outcome**: Comprehensive reference that prevents accidental feature loss and keeps the rewrite grounded in historical context. - ## Delivery Roadmap > **Current Status (2026-01):** Phase 0 is largely complete for `warp-core`. The Rust-first WARP graph rewriting engine is implemented with deterministic scheduling, snapshot hashing, and basic math. ECS storage and system scheduler remain future work. @@ -239,5 +248,5 @@ TTD is a first-class citizen in Echo, built on top of the provenance and scene p - `/packages/echo-cli` — tooling launcher (future), wraps dev server and inspector. - `/packages/echo-adapters` — reference adapters (Pixi/WebGPU, browser input, etc.). - `/apps/playground` — Vite-driven sandbox for samples and inspector. - - `/docs` — specs, diagrams, memorials (human-facing knowledge base). + - `/docs` — live specs, guides, and operational knowledge. - `/tooling` — shared build scripts, benchmarking harness (future). diff --git a/docs/archive/AGENTS.md b/docs/archive/AGENTS.md deleted file mode 100644 index 373f2505..00000000 --- a/docs/archive/AGENTS.md +++ /dev/null @@ -1,183 +0,0 @@ - - - -# Echo Agent Briefing - -Welcome to the **Echo** project. This file captures expectations for any LLM agent (and future-human collaborator) who touches the repo. - -## Core Principles - -- **Honor the Vision**: Echo is a deterministic, multiverse-aware ECS. Consult `docs/architecture-outline.md` before touching runtime code. -- **Document Ruthlessly**: Every meaningful design choice should land in `docs/` (specs, diagrams, ADRs) or PR descriptions. -- **Docstrings Aren't Optional**: Public APIs across crates (`warp-core`, `warp-wasm`, etc.) must carry rustdoc comments that explain intent, invariants, and usage. Treat missing docs as a failing test. -- **Determinism First**: Avoid introducing sources of nondeterminism without a mitigation plan. -- **Temporal Mindset**: Think in timelines—branching, merging, entropy budgets. Feature work should map to Chronos/Kairos/Aion axes where appropriate. - -## The Drill Sergeant Discipline - -Continuum (formerly JITOS) enforces a high-integrity "Drill Sergeant" discipline for all contributors (human or agent): - -1. **Tests as Spec**: Every `feat:` or `fix:` commit MUST include a "red -> green" test story. If you change source code without changing or adding a test, CI will flag it as a policy violation. -2. **Zero-Warning Tolerance**: All determinism-critical crates (`warp-core`, `echo-wasm-abi`, `echo-scene-port`) are compiled with `RUSTFLAGS="-Dwarnings"`. Unused imports, dead code, or silenced lints are treated as build failures. -3. **Determinism Integrity**: We assert inevitability, not just correctness. - - Bit-exact consistency across Rust and JavaScript/Node.js is mandatory for all float-to-int operations. - - Never iterate over `std::collections::HashMap` or `HashSet` in paths that affect the state hash; use `BTreeMap` or sorted iterators. - - Use the DIND (Deterministic Ironclad Nightmare Drills) harness to verify any changes against golden hash chains. -4. **Panic Ban**: Library code must return `Result` or `Option` instead of panicking. `unwrap()` and `expect()` are forbidden in non-test code. - -## Timeline Logging - -- Capture milestones, blockers, and decisions in relevant specs, ADRs, or PR descriptions. -- AGENTS.md and `TASKS-DAG.md` are append-only; see `docs/append-only-invariants.md` plus `scripts/check-append-only.js` for the enforcement plan that CI will run before merges. - -## Agent Context System (2-Tier) - -Agents use a **2-tier context system** to maintain continuity across sessions: - -| Tier | Store | Purpose | Update Frequency | -| ------------- | --------------- | ------------------------------------------ | -------------------------------- | -| **Immediate** | Redis stream | Current task state, branch, blockers | Every significant action | -| **Deep** | Knowledge graph | Architecture decisions, patterns, entities | When learning something reusable | - -### Session Start (Bootstrap) - -1. **Read this file** (`AGENTS.md`) for project conventions - -2. **Check Redis handoff stream**: `echo:agent:handoff` (most recent entry) - - ```text - XRANGE echo:agent:handoff - + COUNT 5 - ``` - -3. **Query knowledge graph** for relevant entities: - - ```python - search_nodes("") # e.g., "BOAW", "MaterializationBus" - search_nodes("Echo") # General project context - ``` - -### During Work (Continuous Updates) - -**Redis stream** — Update after every significant action: - -- Completing a task or subtask -- Encountering a blocker -- Making a key decision -- Changing branches or PRs - -```bash -XADD echo:agent:handoff * \ - branch "graph-boaw" \ - status "IN_PROGRESS" \ - summary "Fixing determinism bug in view op emission" \ - current_task "Updating emit_view_op_delta_scoped()" \ - blockers "none" \ - timestamp "" -``` - -**Knowledge graph** — Create/update entities when you: - -- Discover an architectural pattern worth preserving -- Complete a milestone (create `_Phase` entity) -- Fix a non-obvious bug (create `_BugFix` entity) -- Make a decision that future agents should know about - -```json -{ - "name": "BOAW_Determinism_Fix", - "entityType": "BugFix", - "observations": [ - "Root cause: emit_view_op_delta() used delta.len() for view op IDs", - "delta.len() is worker-local and varies by shard claim order", - "Fix: derive op ID from intent scope (NodeId) which is content-addressed" - ] -} -``` - -### Session End (Handoff) - -Before ending a session, **always** write a handoff entry: - -```bash -XADD echo:agent:handoff * \ - branch "" \ - status "" \ - summary "<1-2 sentence summary of what was done>" \ - commits "" \ - next_steps "" \ - blockers "" \ - tech_debt "" \ - test_commands "" \ - timestamp "" -``` - -### Key Entities to Know - -The knowledge graph contains ~300+ entities built by prior agents. Key patterns: - -- `Echo Project` — Core project info and current focus -- `_Architecture` — Design decisions for major features -- `_Phase` — Milestone completion records -- `_BugFix` — Non-obvious bug fixes worth remembering -- `_Tech_Debt_P` — Tracked technical debt by priority - -### Why This Matters - -- **Quick tasks**: Redis handoff alone may suffice -- **Complex tasks**: Query knowledge graph for architectural context -- **Debugging**: Search for prior bug fixes in similar areas -- **Decisions**: Check if prior agents already explored an approach - -The 2-tier system means handoffs are seamless—no context is lost between agents, and institutional knowledge accumulates over time. - -## Workflows & Automation - -- The contributor playbook lives in `docs/workflows.md` (policy + blessed commands + automation). -- Preferred repo maintenance entrypoint is `cargo xtask …` (see `xtask/` and `.cargo/config.toml`). -- Planning DAG artifacts live in `docs/assets/dags/` and are documented in `docs/dependency-dags.md`. -- For automated DAG refresh PRs, set `DAG_REFRESH_ISSUE=` as a GitHub Actions variable so the bot PR body includes `Refs #…`. - -## Repository Layout - -- `crates/warp-core`: Runtime core (WARP graph model, materialization bus). -- `apps/playground`: Vite sandbox and inspector (future). -- `docs/`: Specs, diagrams, memorials. -- `docs/notes`: Working notes and explorations (non-authoritative). - -## Working Agreement - -- **Isolated Branches**: Every new task, feature, or bugfix **MUST** begin on a fresh, isolated branch based on the latest `main` (unless context explicitly dictates otherwise). Never mix unrelated objectives on the same branch. -- Keep `main` pristine. Feature work belongs on branches named `echo/` or `timeline/`. -- Tests and benchmarks are mandatory for runtime changes once the harness exists. -- Respect determinism: preferably no random seeds without going through the Echo PRNG. -- Run `cargo clippy --all-targets -- -D missing_docs` and `cargo test` before every PR; CI will expect a zero-warning, fully documented surface. - -### PRs & Issues (Linkage Policy) - -- Every PR must be tied to a GitHub Issue. - - If no suitable issue exists, open one before you open the PR. - - Use explicit closing keywords in the PR body: include a line like `Closes #` so the issue auto‑closes on merge. - - Keep PRs single‑purpose: 1 PR = 1 thing. Avoid bundling unrelated changes. -- Branch naming: prefer `echo/` or `timeline/` and include the issue number in the PR title. -- Project hygiene: assign the PR's linked issue to the correct Milestone and Board column (Blocked/Ready/Done) as part of the PR. - -### Git Hooks & Local CI - -- Install repo hooks once with `make hooks` (configures `core.hooksPath`). -- Formatting: pre-commit auto-fixes with `cargo fmt` by default. Set `ECHO_AUTO_FMT=0` to run check-only instead. -- Toolchain: pre-commit verifies your active toolchain matches `rust-toolchain.toml`. -- SPDX header policy (source): every source file must start with exactly: - - `// SPDX-License-Identifier: Apache-2.0` - - `// © James Ross Ω FLYING•ROBOTS ` - Use the repository `.githooks/` installed by `make hooks`; `scripts/hooks/` - are legacy compatibility shims only. Do not add dual-license headers to code. - -## Git Real - -1. **NEVER** use `--force` with any git command. If you think you need it, stop and ask the human for help. -2. **NEVER** use rebase. Embrace messy distributed history; plain merges capture the truth, rebases rewrite it. -3. **NEVER** amend a commit. Make a new commit instead of erasing recorded history. - -In short: no one cares about a tidy commit graph, but everyone cares if you rewrite commits on origin. - -Safe travels in the multiverse. diff --git a/docs/archive/ISSUES_MATRIX.md b/docs/archive/ISSUES_MATRIX.md deleted file mode 100644 index 862bcbc6..00000000 --- a/docs/archive/ISSUES_MATRIX.md +++ /dev/null @@ -1,104 +0,0 @@ - - - -# Echo Issues Matrix (Active Plan) - -This table mirrors the current state of active issues in Project 9 with our plan-aligned milestones and relationships. Native GitHub dependencies represent "blocked by"/"blocking"; we no longer use custom text fields for these. The Project board remains the live system of record for status. - -## Managing Issue Dependencies (Blocked By / Blocking) - -Echo uses **native GitHub issue dependencies** to track “blocked by” relationships (not custom text fields). - -Practical note: the GitHub GraphQL API exposes dependency data/events, but dependency _mutation_ is easiest via the **REST API**. In practice, we use `gh api` as the most scriptable interface. - -Reference: GitHub docs “REST API endpoints for issue dependencies” (see `issues/issue-dependencies` in the REST docs). - -### Common `gh api` recipes - -Auth note: `gh api` uses your authenticated GitHub token (via `gh auth login` or `GH_TOKEN` env var). You do not need to manually add an `Authorization:` header unless you are reproducing these requests with another client (like `curl`). - -List dependencies an issue is blocked by: - -```bash -gh api \ - -H "Accept: application/vnd.github+json" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - repos/flyingrobots/echo/issues//dependencies/blocked_by -``` - -List dependencies an issue is blocking: - -```bash -gh api \ - -H "Accept: application/vnd.github+json" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - repos/flyingrobots/echo/issues//dependencies/blocking -``` - -Note: the `blocked_by` and `blocking` relationships are inverses. Adding “issue A blocked by issue B” is equivalent to adding “issue B blocking issue A”. Choose the direction that matches your workflow. - -Add a “blocked by” dependency (make `` blocked by ``): - -```bash -set -euo pipefail - -# Optional (only needed if you are not already authenticated via `gh auth login` or `GH_TOKEN`): -# -H "Authorization: Bearer " -BLOCKING_ISSUE_ID="$( - gh api \ - -H "Accept: application/vnd.github+json" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - repos/flyingrobots/echo/issues/ \ - --jq .id -)" || { echo "Failed to fetch blocking issue ID" >&2; exit 1; } - -if [[ -z "$BLOCKING_ISSUE_ID" ]]; then - echo "BLOCKING_ISSUE_ID is empty; verify auth and jq extraction." >&2 - exit 1 -fi - -gh api \ - -X POST \ - -H "Accept: application/vnd.github+json" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - repos/flyingrobots/echo/issues//dependencies/blocked_by \ - -f issue_id="$BLOCKING_ISSUE_ID" -``` - -Remove a “blocked by” dependency: - -```bash -gh api \ - -X DELETE \ - -H "Accept: application/vnd.github+json" \ - # Optional (only needed if you are not already authenticated via `gh auth login` or `GH_TOKEN`): - # -H "Authorization: Bearer " \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - repos/flyingrobots/echo/issues//dependencies/blocked_by/ -``` - -| Issue Name | Issue # | Milestone | Priority | Estimate | Blocked By | Blocking | Parent | Children | Remarks | -| ----------------------------------------------------------------------------- | ------: | ------------------------------------- | -------- | -------- | ------------------- | ------------------- | ------ | -------------- | ----------------------------------------------------------------- | -| Benchmarks & CI Regression Gates | 22 | M1 – Golden Tests | P1 | 13h+ | #42,#43,#44,#45,#46 | | | 42,43,44,45,46 | Umbrella for perf pipeline (blocked by all children) | -| Create benches crate | 42 | M1 – Golden Tests | P1 | 3h | | #22,#43,#44,#45,#46 | #22 | | Criterion + scaffolding | -| Snapshot hash microbench | 43 | M1 – Golden Tests | P1 | 5h | #42 | #22 | #22 | | Reachable hash microbench | -| Scheduler drain microbench | 44 | M1 – Golden Tests | P1 | 5h | #42 | #22 | #22 | | Deterministic rule‑order/drain | -| JSON report + CI upload | 45 | M1 – Golden Tests | P2 | 3h | #42 | #22,#46 | #22 | | Upload Criterion JSON | -| Regression thresholds gate | 46 | M1 – Golden Tests | P1 | 8h | #42,#45 | #22 | #22 | | Fail on P50/P95/P99 regress | -| CLI: verify/bench/inspect | 23 | M2.2 – Playground Slice | P2 | 5h | | | | | Grouping placeholder; break down in PRs | -| Scaffold CLI subcommands | 47 | M2.2 – Playground Slice | P2 | 5h | | | | | | -| Implement 'verify' | 48 | M2.2 – Playground Slice | P2 | 5h | | | | | | -| Implement 'bench' | 49 | M2.2 – Playground Slice | P2 | 5h | | | | | | -| Implement 'inspect' | 50 | M2.2 – Playground Slice | P2 | 5h | | | | | | -| Docs/man pages | 51 | M2.2 – Playground Slice | P2 | 5h | | | | | Tie docs to CLI UX | -| README+docs (defaults & toggles) | 41 | M4 – Determinism Proof & Publish 0.1 | P2 | 3h | | | | | Docs polish before 0.1 | -| Deterministic trig: pin error budget + deterministic oracle for audit test | 177 | M4 – Determinism Proof & Publish 0.1 | | | | | | | Cross-OS determinism gate; keep oracle host-independent | -| T2: Embedded tooling UI baseline (Open Props + screenshot regen) | 168 | T2 – Embedded Tooling UI Baseline | | | | | | | Embedded dashboard baseline + Playwright evidence | -| TT0: Time model spec lock (TimeStreams + admission digests) | 166 | TT0 – Time Model Spec Lock | | | | | | | Spec lock for time model primitives (streams/cursors/admission) | -| TT1: StreamsFrame inspector support (backlog + cursors + admission decisions) | 170 | TT1 – Streams Inspector Frame | | | | | | | Inspector scaffolding for stream backlogs and admission decisions | -| TT2: Time Travel MVP (pause/rewind/buffer/catch-up) | 171 | TT2 – Time Travel MVP | | | | | | | Pause/rewind UX + buffering policies | -| TT3: Rulial diff / worldline compare MVP | 172 | TT3 – Rulial Diff / Worldline Compare | | | | | | | Side-by-side run comparison tooling | -| S1: Deterministic Rhai surface (sandbox + claims/effects) | 173 | S1 – Deterministic Rhai Surface | | | | | | | Deterministic sandbox boundary for scripts | -| W1: Wesley as a boundary grammar (hashable view artifacts) | 174 | W1 – Wesley as a Boundary Grammar | | | | | | | Hashable grammar + pinned semantics for replay integrity | - -Backlog issues are labeled `backlog` and kept visible in the Project; they will be prioritized into milestones as needed. diff --git a/docs/archive/README.md b/docs/archive/README.md deleted file mode 100644 index fb91aca4..00000000 --- a/docs/archive/README.md +++ /dev/null @@ -1,12 +0,0 @@ - - - -# Archive - -Documents that are **no longer the active source of truth** for any ongoing -work. This includes superseded specs, completed plans, abandoned proposals, and -design notes whose insights have been absorbed into code or canonical docs. - -**Rule of thumb:** if someone would read a doc and ask "should I be doing -something about this?" — it does not belong here. If the answer is "this -already happened" or "we went a different direction" — archive it. diff --git a/docs/archive/ROADMAP/ECHO_ROADMAP.md b/docs/archive/ROADMAP/ECHO_ROADMAP.md deleted file mode 100644 index 54ef72ca..00000000 --- a/docs/archive/ROADMAP/ECHO_ROADMAP.md +++ /dev/null @@ -1,164 +0,0 @@ - - - -# ECHO_ROADMAP — Phased Plan (Post-ADR Alignment) - -## Completed Sprint: TTD-HARDENING-S1 (2026-02-14 to 2026-02-15) - -**Goal:** Formalize the TTD (Time-Travel Determinism) hardening gates and evidence integrity. - -- [x] **G1 (DET):** Multi-platform determinism matrix (macOS/Linux + wasm). -- [x] **G2 (SEC):** Explicit negative test mapping for decoder controls. -- [x] **G3 (PRF):** Criterion baseline + regression threshold for materialization path. -- [x] **G4 (REP):** Enforce artifact-backed VERIFIED claims and path-aware gates. -- [x] **GOV:** Publish release policy and commit-ordered rollback playbooks. - ---- - -This roadmap re-syncs active work with recent ADRs: - -- ADR-0003: Causality-first API + MaterializationBus/Port -- ADR-0004: No global state / explicit dependency injection -- ADR-0005: Physics as deterministic scheduled rewrites -- ADR-0006: Ban non-determinism via CI guards - -It also incorporates the latest DIND status from `GEMINI_CONTINUE_NOTES.md`. - ---- - -## Phase 0 — Repo Hygiene & Ownership - -Goal: eliminate structural drift and restore correct ownership boundaries. - -- Move `crates/echo-dind-harness/` to the Echo repo (submodule) where it belongs. - - Remove the crate from this workspace once moved. - - Ensure any references/scripts in this repo point to the Echo submodule path. -- Audit for other Echo-owned crates/docs accidentally mirrored here. -- Update docs to reflect the correct location of DIND tooling. - -Exit criteria: - -- `crates/echo-dind-harness/` no longer exists in this repo. -- A clear pointer exists for where to run DIND locally (Echo repo). - ---- - -## Phase 1 — Determinism Guardrails (ADR-0004 + ADR-0006) - -Goal: codify the “no global state / no nondeterminism” doctrine and enforce it in CI. - -- Add CI scripts: - - `scripts/ban-globals.sh` (ADR-0004) - - `scripts/ban-nondeterminism.sh` and `scripts/ban-unordered-abi.sh` (ADR-0006) -- Wire scripts into CI for core crates (warp-core, warp-wasm, app wasm). -- Add minimal allowlist files (empty by default). -- Document determinism rules in README / doctrine doc. - -Exit criteria: - -- CI fails on banned patterns. -- No global init (`install_*` style) in runtime core. - ---- - -## Phase 2 — Causality-First Boundary (ADR-0003) - -Goal: enforce ingress-only writes and bus-first reads. - -- Define/confirm canonical intent envelopes for ingress (bytes-only). -- Ensure all write paths use ingress; remove any public “direct mutation” APIs. -- Implement MaterializationBus + MaterializationPort boundary: - - `view_subscribe`, `view_drain`, `view_replay_last`, `view_unsubscribe` - - channel IDs are byte-based (TypeId-derived), no strings in ABI -- Ensure UI uses materializations rather than direct state reads (except inspector). -- Define InspectorPort as a gated, separate API (optional). - -Exit criteria: - -- No direct mutation path exposed to tools/UI. -- UI can run solely on materialized channels (or has a plan to get there). - ---- - -## Phase 3 — Physics Pipeline (ADR-0005) - -Goal: implement deterministic physics as scheduled rewrites. - -- Implement tick phases: - 1. Integrate (predict) - 2. Candidate generation (broadphase + narrowphase) - 3. Solver iterations with footprint scheduling - 4. Finalize (commit) -- Canonical ordering: - - candidate keys: `(toi_q, min_id, max_id, feature_id)` - - deterministic iteration order for bodies and contacts -- Add optional trace channels for physics (debug materializations). -- Ensure physics outputs only emit post-commit. - -Exit criteria: - -- Physics determinism across wasm/native with fixed seeds and inputs. -- No queue-based “micro-inbox” for derived physics work. - ---- - -## Phase 4 — DIND Mission Continuation (from GEMINI_CONTINUE_NOTES) - -Goal: complete Mission 3 polish and Mission 4 performance envelope. - -### Mission 3 (Polish / Verification) - -- Badge scoping: clarify scope (“PR set”) and platforms. -- Badge truth source: generate from CI artifacts only. -- Matrix audit: confirm explicit aarch64 coverage needs. - -### Mission 4 (Performance Envelope) - -- Add `perf` command to DIND harness: - - `perf --baseline --tolerance 15%` - - track `time_ms`, `steps`, `time_per_step` - - optional: max nodes/edges, allocations -- Add baseline: `testdata/dind/perf_baseline.json` -- CI: - - PR: core scenarios, release build, fail on >15% regression - - Nightly: full suite, upload perf artifacts - -Exit criteria: - -- DIND perf regressions fail CI. -- Stable baseline file committed. - ---- - -## Phase 5 — App-Repo Integration (flyingrobots.dev specific) - -Goal: keep app-specific wasm boundary clean and deterministic. - -- Ensure TS encoders are the source of truth for binary protocol. -- Keep WASM as a thin bridge (no placeholder exports). -- Verify handshake matches registry version / codec / schema hash. -- Add or update tests verifying canonical ordering and envelope bytes. - -Exit criteria: - -- ABI tests use TS encoders, not wasm placeholder exports. -- wasm build + vitest pass. - ---- - -## Open Questions / Dependencies - -- Precise target crates for determinism guardrails in this repo vs Echo repo. -- Whether InspectorPort needs to exist in flyingrobots.dev or only in Echo. -- Final home for DIND artifacts: Echo repo or shared tooling repo. - ---- - -## Suggested Execution Order - -1. Phase 0 (move DIND harness) to prevent ownership drift. -2. Phase 1 guardrails to lock determinism. -3. Phase 2 boundary enforcement (ingress + bus). -4. Phase 3 physics pipeline. -5. Phase 4 DIND polish/perf. -6. Phase 5 app integration clean-up. diff --git a/docs/archive/ROLLBACK_TTD.md b/docs/archive/ROLLBACK_TTD.md deleted file mode 100644 index 08d435f4..00000000 --- a/docs/archive/ROLLBACK_TTD.md +++ /dev/null @@ -1,88 +0,0 @@ - - - -# Rollback Playbook — TTD Integration - -## Scope - -> **Note:** Commit SHAs below are pinned to the original TTD integration merge window. Verify against `git log` before executing any rollback. - -Rollback coverage for commit range: - -- Base: `efae3e8` -- Head: `e201c9b` - -## Preconditions - -- Release owner approval logged. -- Current branch state saved/tagged. -- Incident ticket created. - -## Scenario A — Full TTD Rollback - -### Objective (Scenario A) - -Return repository to pre-TTD integration state. - -### Ordered actions - -1. Create rollback branch: - - `rollback/ttd-full-` -2. Revert commits in reverse order from head to base+1: - - `e201c9b` - - `fd98b91` - - `ce98d80` - - `a02ea86` - - `3187e6a` - - `6e34a77` - - `f138b8a` - > **Merge commits:** If any listed commit is a merge, use `git revert -m 1 ` to select the first parent as the mainline. -3. Resolve conflicts preserving pre-TTD behavior. - -### Validation Checklist (Scenario A) - -- [ ] `cargo check --workspace` passes -- [ ] Determinism suite for non-TTD core passes -- [ ] Build pipelines pass -- [ ] Smoke test core runtime flows pass - ---- - -## Scenario B — Partial Rollback (FFI/UI layer) - -### Objective (Scenario B) - -Remove unstable FFI/UI integration while preserving core hardening. - -### Candidate revert target(s) - -- `fd98b91` (UI/WASM Integration) -- `ce98d80` (Frontend Restoration) -- optionally `a02ea86` if FFI safety layer must be reverted together - -### Dependency constraints - -- Reverting `a02ea86` may break consumers expecting SessionToken/FFI contracts. -- Validate dependent crates/apps after each revert step. - -### Validation Checklist (Scenario B) - -- [ ] `apps/ttd-app` build status known (pass/fail expected documented) -- [ ] Core codec/scene crates compile and tests pass -- [ ] CI gate summary attached to incident - ---- - -## Post-Rollback Evidence Packet (required) - -- commit SHAs reverted -- CI run IDs -- failing/passing gate delta (before vs after) -- residual risk summary -- recommendation: GO / CONDITIONAL / NO-GO - -### Filing - -- Attach the evidence packet to the incident ticket. -- Link the packet in the rollback PR description. -- Name the artifact `incident--post-rollback-evidence`. diff --git a/docs/archive/aion-papers-bridge.md b/docs/archive/aion-papers-bridge.md deleted file mode 100644 index 7af118c1..00000000 --- a/docs/archive/aion-papers-bridge.md +++ /dev/null @@ -1,231 +0,0 @@ - - - -# AIΩN Foundations → Echo Bridge - -Last reviewed: 2025-12-29. - -This doc maps the **AIΩN Foundations series** (“WARP Graphs”, Papers I–VI) onto the **Echo** repository as it exists today. - -Goal: keep the repo’s _implemented_ determinism contracts and its _spec narrative_ aligned with the papers that motivated them. - -## Scope / Sources Read - -For background and public context: - -- AIΩN Framework repo: - -Published paper links (DOIs): - -- Paper I: -- Paper II: -- Paper III: -- Paper IV: -- Papers V–VI: not yet published (as of 2025-12-28). - -Note: the TeX sources used to author Papers I–VI are maintained outside this repo and are intentionally not vendored into Echo. - -## Terminology (WARP vs RMG) - -The AIΩN Foundations papers standardize on **WARP graph** (Worldline Algebra for Recursive Provenance) as the public name for the substrate. - -This Echo repo historically used the older name **RMG** / “recursive metagraph” in crate names, type names, and docs (e.g., `rmg-core`, `RmgFrame`, “RMG viewer”). -Those identifiers have now been mechanically renamed to **WARP** equivalents (e.g., `warp-core`, `WarpFrame`, `warp-viewer`), but you may still see “RMG” in older notes and historical commit messages. - -**Project policy:** - -- Prefer **WARP** terminology in human-facing docs going forward. -- When Echo intentionally deviates from the paper design (for performance, ergonomics, or game-engine concerns), we **must** document the deviation and rationale here (so readers of the papers learn what changed and why). - -Status note: the mechanical rename from `rmg-*` → `warp-*` has landed (crates + the session/tooling surface). -The session wire protocol prefers `warp_*` op strings and `warp_id`, but decoders accept legacy `rmg_*` / `rmg_id` as compatibility aliases. - -## Backlog Mapping (Paper → Echo) - -These tables are intentionally “backlog-driving”: they identify what exists today, what is missing, and where Echo has (or may later choose) a different path. - -### Paper I — WARP as the state object - -| Paper I concept | Echo status | Touchpoints (today) | Backlog / next step | Deviation notes | -| -------------------------------------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| WARP graph = atom **or** skeleton-with-attachments | Partial (skeleton + typed atom attachments) | `crates/warp-core/src/graph.rs`, `crates/warp-core/src/record.rs`, `crates/warp-core/src/attachment.rs`, `docs/warp-two-plane-law.md` | Stage B1: represent descended attachments via explicit indirection (skeleton-visible links / refs), not “hidden graphs inside bytes” | Echo v0 treats attachments as **typed atoms** (`AtomPayload { type_id, bytes }`) and commits the payload `type_id` into boundary digests. Full “WARPs all the way down” (descended attachments) is not implemented yet by design. | -| Depth / finite unfoldings | Not implemented explicitly | (N/A; conceptual) | If observers/tools need “unfold to depth k”, define a canonical encoding for nested payloads and add tooling helpers | Might stay in the tooling layer, not the core engine. | -| Morphisms / category framing | Not implemented explicitly | (Docs only) | Identify which morphism fragments matter for engine/tooling APIs (likely: stable IDs + isomorphism criteria for hashing) | Echo currently uses hashes + canonical encodings as “practical morphisms”. | - -### Paper II — Deterministic evolution (ticks, independence, receipts) - -| Paper II concept | Echo status | Touchpoints (today) | Backlog / next step | Deviation notes | -| --------------------------------------------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Tick = atomic commit (all-or-nothing) | Implemented for the spike (`commit` finalizes tx) | `crates/warp-core/src/engine_impl.rs` | Make abort/stutter semantics explicit if/when partial failure exists (currently “reserve rejects conflicts”) | Echo currently models conflicts as “not reserved”; explicit abort receipts are a good future addition. | -| Independence via footprints (delete/use; read/write sets) | Implemented (expanded to nodes/edges/ports + factor mask) | `crates/warp-core/src/footprint.rs`, `crates/warp-core/src/scheduler.rs` | Ensure footprint semantics remain “Paper II compatible” as optimizations land (bitmaps/SIMD, etc.) | Echo adds boundary ports + factor masks for engine practicality; document as an extension of the footprint idea. | -| Deterministic scheduling via total key order (“left-most wins”) | Implemented (deterministic ordering + deterministic reserve filter) | `crates/warp-core/src/scheduler.rs` | Specify the canonical key format (what exactly is “scope”?); keep stable across releases | Echo’s key is currently (`scope_hash`, `rule_id`, `nonce`); may evolve, but must remain deterministic. | -| Tick receipts (accepted vs rejected + blocking poset) | Implemented (receipt + blocking attribution; richer reasons pending) | `crates/warp-core/src/receipt.rs`, `crates/warp-core/src/engine_impl.rs`, `docs/spec-merkle-commit.md` | Decide when/if to commit blocking edges into the hash and extend receipts with richer rejection reasons once conflict policy/join semantics land | Receipt captures accepted vs rejected in canonical plan order and records blockers (poset edges) for footprint conflicts; only rejection reason today is footprint conflict. | - -### Paper III — Holography (payloads, BTRs, wormholes) - -| Paper III concept | Echo status | Touchpoints (today) | Backlog / next step | Deviation notes | -| ----------------------------------------------------- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | -| Boundary encoding `(U0, P)` where `P` is tick patches | Implemented in spirit | `crates/echo-graph` (`Snapshot` + `Diff`), `crates/warp-viewer/src/session_logic.rs` | Decide whether Echo’s “Diff stream” is _the_ canonical tick-patch format (or one of several observers) | Echo’s stream is a practical boundary artifact; may not capture full tick receipts yet. | -| BTR packaging (hash in/out + payload + auth tag) | Partially implemented (hashes + canonical encoding + checksums) | `crates/warp-core/src/snapshot.rs`, `crates/echo-session-proto/src/wire.rs` | Define an explicit “BTR” message/record type for archives and replication (and later signing) | Today: checksums protect packets; signatures are future work. | -| Wormholes (compressed multi-tick segments) | Not implemented | (Concept only) | Add “checkpoint/wormhole” support once payloads are structured enough to compress/skip while preserving verification | Might be a tooling/storage feature, not required for realtime gameplay. | -| Prefix forks (content-addressed shared history) | Partially implemented (parents in commit hash) | `crates/warp-core/src/snapshot.rs` | Implement branch storage / addressable worldline families (Chronos/Kairos) | Echo already has parents; higher-level branch mechanics are still in docs. | - -### Paper IV — Observer geometry + Chronos/Kairos/Aion - -| Paper IV concept | Echo status | Touchpoints (today) | Backlog / next step | Deviation notes | -| ------------------------------------- | ----------------------------------------------------- | --------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | -| Chronos/Kairos/Aion triad | Specced; partially embodied (epochs, hashes, streams) | `docs/architecture-outline.md`, `crates/echo-session-service` (`ts`), `crates/echo-graph` (`epoch`) | Implement explicit branch-event (Kairos) and possibility-space (Aion) APIs once branch tree lands | Echo can keep the triad as “design axes” even before full branch tree exists. | -| Observers as projections over history | Embodied as tools/viewers | `crates/warp-viewer`, `docs/book/echo/booklet-05-tools.tex` | Define a “small family of canonical observers” and their guarantees (hash checks, partial views, privacy scopes) | Game tools want fast observers; papers motivate explicit translation costs. | - -### Paper V — Provenance sovereignty (ethics requirements) - -| Paper V concept | Echo status | Touchpoints (today) | Backlog / next step | Deviation notes | -| ----------------------------------------- | ------------------------------------ | ------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | -| Capability-scoped observers + due process | Partially specced / stub hooks exist | `crates/echo-session-proto` (handshake `capabilities`), `docs/spec-capabilities-and-security.md` | Evolve the session handshake into a real capability system tied to observer access | Echo can ship “developer mode” first, but must document the intended governance boundary. | - -### Paper VI — JITOS / OS boundary + JS-ABI syscall framing - -| Paper VI concept | Echo status | Touchpoints (today) | Backlog / next step | Deviation notes | -| ---------------------------------------------- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | -| JS-ABI as stable, language-independent framing | Implemented | `crates/echo-session-proto/src/wire.rs`, `crates/echo-session-proto/src/canonical.rs` | Keep the framing boring and stable; add capability negotiation/versioning as needed | Echo already matches the “boring but essential” framing objective. | -| WAL / epochs as temporal backbone | Partially implemented (monotonic `ts`, epoch stream discipline) | `crates/echo-session-service`, `crates/echo-graph` | Define durable WAL / archive format and its relationship to commit hashes and diffs | Echo can treat session streams as “live WAL slices” and add persistence later. | - -## Paper I — WARP as the state object (graphs all the way down) - -**Core idea:** a _WARP graph_ is either an **atom** (`Atom(p)` for opaque payload `p`) or a **finite directed multigraph skeleton** whose vertices and edges carry attached WARPs. - -**Relevance to Echo:** - -- Echo’s “everything is a graph” story is Paper I’s substrate claim. -- Echo’s current engine spike (`warp-core`) implements a _flat_ skeleton graph (`GraphStore`) plus **depth-0 attachments** as typed atoms (`AtomPayload { type_id, bytes }`). -- The WARP “attachments are themselves graphs” concept is **not implemented yet**. Echo’s project law forbids treating payload bytes as “hidden edges”; descended attachments must be represented via explicit, skeleton-visible indirection when implemented (Stage B1). - -**Echo touchpoints:** - -- `crates/warp-core/src/graph.rs` is the current SkeletonGraph implementation. -- `crates/warp-core/src/record.rs` + `crates/warp-core/src/attachment.rs` define node/edge records and depth-0 typed atom attachments. -- `crates/echo-graph` is the canonical _tool/wire_ graph shape. - -## Paper II — Deterministic evolution: ticks, footprints, and receipts - -**Core idea:** define a deterministic, concurrent operational semantics at the level of a **tick**: - -- Within a tick, commit a scheduler-admissible batch (pairwise independent by footprint discipline). -- **Tick confluence:** any serialization order yields the same successor (up to isomorphism). -- Deterministic scheduling comes from a deterministic total order on candidates (“left-most wins”). -- Optional **tick receipts** record accepted vs rejected candidates and _why_ (a poset of blocking causality). - -**Relevance to Echo:** - -- Echo’s runtime determinism is largely “Paper II made executable”: - - collect candidate rewrites, - - sort deterministically, - - accept a conflict-free subset, - - apply in deterministic order, - - produce a hash. - -**Echo touchpoints:** - -- Deterministic pending queue + drain ordering: - - `crates/warp-core/src/scheduler.rs` -- Footprints + independence checks: - - `crates/warp-core/src/footprint.rs` -- Transaction lifecycle + plan/rewrites digests: - - `crates/warp-core/src/engine_impl.rs` - -**Notable gap (intentional/expected):** - -- Echo’s current engine exposes “plan_digest” / “rewrites_digest”, and now exposes a minimal tick receipt blocking witness: for rejected candidates, the receipt lists which earlier applied candidates blocked it (indices in canonical plan order). - -## Paper III — Computational holography: provenance payloads, BTRs, wormholes - -**Core idea:** for deterministic worldlines, the interior derivation volume is recoverable from a compact boundary: - -- boundary encoding = `(U0, P)` where `P` is an ordered list of tick patches (payload) -- BTR (Boundary Transition Record) packages boundary hashes + payload for tamper-evidence/audit -- slicing: materialize only the causal cone required for some value -- prefix forks: Git-like branching via shared-prefix dedupe under content addressing -- wormholes: compress multi-tick segments into a single edge carrying a sub-payload - -**Relevance to Echo:** - -- Echo already treats hashing and canonical encoding as “truth checks”. -- Echo’s session pipeline is essentially a practical “boundary stream”: - - full snapshots + gapless diffs (tick patches) with optional state hashes. - -**Echo touchpoints:** - -- `state_root` + `commit_id` and canonical encoding: - - `crates/warp-core/src/snapshot.rs` - - `docs/spec-merkle-commit.md` -- Gapless snapshot/diff semantics + per-frame hash checks: - - `crates/echo-graph` - - `crates/echo-session-proto` - - `crates/echo-session-service` - - `crates/warp-viewer/src/session_logic.rs` - -## Paper IV — Observer geometry, rulial distance, Chronos/Kairos/Aion - -**Core idea:** observers are resource-bounded functors out of history categories; translation cost induces geometry (rulial distance). - -This paper also formalizes the three-layer time model used throughout Echo docs: - -- **Chronos:** linear time of a fixed replay path (tick index) -- **Kairos:** branch events / loci of alternative continuations -- **Aion:** the full possibility space (history category; “Ruliad” as a large disjoint union) - -**Relevance to Echo:** - -- Echo’s Chronos/Kairos/Aion language isn’t “theme”; it’s an architectural partitioning of: - - replay time, - - branching structure, - - and the larger possibility space/tooling surface. - -**Echo touchpoints:** - -- Conceptual: `docs/architecture-outline.md` (temporal axes) -- Practical precursor: hash-checked replay streams (viewer) + deterministic encoding (proto) - -## Paper V — Ethics: provenance sovereignty as a runtime requirement - -**Core idea:** deterministic replay + complete provenance becomes a capability that can be abused; therefore a runtime must treat provenance access as governed. - -Paper V extracts system-level requirements (examples): - -- consent + revocation, -- capability-scoped observers and view access, -- sealing / selective disclosure, -- fork rights (and constraints), -- due-process override protocols. - -**Echo touchpoints:** - -- `docs/spec-capabilities-and-security.md` (security/capability design space) -- Session protocol’s explicit “capabilities” field (`HandshakePayload`) provides a concrete hook to evolve toward scoped observers. - -## Paper VI — JITOS: OS boundary layer and JS-ABI as syscall framing - -**Core idea:** build an OS whose primary artifact is lawful transformations (history), with “state” as a materialized view; introduce SWS (shadow worlds), WAL-backed epochs, deterministic collapse, and JS-ABI as a stable syscall framing. - -**Echo touchpoints:** - -- JS-ABI framing + canonical payload encoding + checksums: - - `crates/echo-session-proto/src/wire.rs` - - `crates/echo-session-proto/src/canonical.rs` -- Session hub as an early “daemon boundary”: - - `crates/echo-session-service` -- Viewer/tooling as early “observer” implementations: - - `crates/warp-viewer` -- Multi-clock time model (streams + admission policies) and wormholes as tick-range compression for fast seek/catch-up: - - `docs/spec-time-streams-and-wormholes.md` - -## Practical Alignment Notes (What to Keep in Sync) - -- Terminology drift: “RMG” vs “WARP” - - Papers use “WARP” as the public substrate name; Echo now uses `warp-*` naming in crates and the session/tooling surface. - - Docs and historical artifacts may still mention "RMG"; keep this note explicit about why/when a deviation exists. -- Empty-digest semantics for commit metadata - - The engine’s canonical empty _length-prefixed list digest_ is `blake3(0u64.to_le_bytes())`. - - Keep docs consistent because this changes commit identity. -- Receipts / traces - - Paper II receipts and Paper III payload/boundary formats are natural next layers over `plan_digest`/`rewrites_digest` and session diffs. diff --git a/docs/archive/branch-merge-playbook.md b/docs/archive/branch-merge-playbook.md deleted file mode 100644 index f695de6a..00000000 --- a/docs/archive/branch-merge-playbook.md +++ /dev/null @@ -1,90 +0,0 @@ - - - -# Branch Merge Conflict Playbook - -Merging timelines is where Echo’s temporal sandbox shines. This playbook defines how we detect, surface, and resolve conflicts when combining branch diffs. - ---- - -## Conflict Types - -1. **Component Value Conflict** - - Same entity & component modified differently in both branches. - -2. **Structural Conflict** - - One branch deletes entity/component the other modifies. - -3. **Order Conflict** - - Sequencing-sensitive actions (e.g., timeline events) reordered. - -4. **Resource Conflict** - - Shared resources (inventory counts, singleton states) diverge. - ---- - -## Detection Pipeline - -1. Identify lowest common ancestor node `L`. -2. Collect diffs `Δα` (from `L` to branch α head) and `Δβ` (to branch β head). -3. For each entity/component touched: - - Compare mutation timestamps (relative order from diff metadata). - - If both branches modify same slot → flag conflict. -4. For deletions vs modifications, flag structural conflict. -5. Accumulate conflict records for resolution stage. - -Conflict record structure: - -```ts -interface MergeConflict { - entityId: EntityHandle; - componentType: number | null; // null for entity-level conflict - type: "value" | "structural" | "order" | "resource"; - branchA: DiffEntry; - branchB: DiffEntry; -} -``` - ---- - -## Resolution Strategies - -1. **Manual Selection (Default)** - - Present conflicts in inspector; designer chooses branch (A wins, B wins, custom). - - Record decision for determinism (stored in merge log). - -2. **Policy-Based** - - Rules such as "prefer branch with higher Aion (Echo's per-node timeline weight)" or "prefer lower entropy". - - Configurable via merge options. - -3. **Blend** (future) - - For numeric components, allow interpolation (requires designer script). - -4. **Retry** - - Abort merge, spawn new branch to rework conflicts. - ---- - -## Tooling Flow - -- Merge UI displays conflict list with filters (type, component, branch). -- Each conflict shows diffs side-by-side, include context (timeline notes, metadata). -- Decisions appended to merge log (`MergeDecision[]`) for replay. -- After resolving all conflicts, system applies merged diff sequentially and commits new node. - ---- - -## Automation Hooks - -- `merge.resolve(conflictId, strategy)` API for scripting/automation. -- Optional "auto-resolve" pass using policy (e.g., prefer branch A) before manual review. -- Notifications when unresolved conflicts remain. - ---- - -## Open Questions - -- Should we support collaborative resolution (multiple designers editing simultaneously)? -- How to visualize conflicts across nested branches (merge of merges)? -- Do we need plugin points for domain-specific merge strategies (e.g., level geometry vs inventory)? -- How to integrate paradox detection (if merge would introduce paradox, block and prompt user). diff --git a/docs/archive/capability-ownership-matrix.md b/docs/archive/capability-ownership-matrix.md deleted file mode 100644 index 73e67fa2..00000000 --- a/docs/archive/capability-ownership-matrix.md +++ /dev/null @@ -1,115 +0,0 @@ - - - -# Capability Ownership Matrix - -Date: 2026-01-01 -Status: Draft (Phase 0.75) - -This document is a living boundary map for Echo. - -It answers (explicitly, in one place): - -- Who **owns** vs **consumes** each capability? -- What determinism level is required at each layer? -- What provenance is required to make replay / time travel honest? -- Which external dependencies (clocks, OS IO, networks) are allowed to influence state, and how? - -It is intentionally redundant with specs: the point is to keep the architecture legible while it is evolving. - ---- - -## Layers (Echo interpretation) - -Use these columns consistently: - -- **Platform**: host integration and durable artifacts/contracts (process, filesystem, sockets, timers, OS scheduling, worldline storage, commit hashing, tick patch format, digests). Nondeterministic by default. -- **Kernel**: deterministic semantic core (rewrite engine, scheduler, receipts, snapshot/tick structure, deterministic decision records including stream admission decisions). HistoryTime-only. -- **Views**: controlled accessors and projections over history (query APIs, inspectors, adapters). Any interaction with HostTime/IO must be recorded as replay-safe claims/decisions. -- **Tooling**: UIs, dashboards, CLI workflows (read-only by default; must be usable during pause/rewind; any control surface must be capability-gated and recorded). -- **Docs**: specs and procedures; the "human-facing API". - ---- - -## Ratings - -- **Determinism** - - `none`: may vary per run; not replayable - - `best-effort`: tries to be stable but not a contract - - `deterministic`: replayable given pinned artifacts/inputs -- **Provenance** - - `none`: no tracking - - `basic`: timestamps/ids only - - `strong`: hash/CID-linked; replay/audit friendly - ---- - -## Matrix Template (Copy/Paste) - -For each capability × layer, fill the cell with: - -- `Role`: owns | consumes -- `Stability`: experimental | beta | stable -- `Determinism`: none | best-effort | deterministic (replayable) -- `Provenance`: none | basic (timestamps) | strong (CID/hash-linked) -- `External Deps`: libs, services, clocks, networks, etc. - -Cell format: - -```text -Role: owns | consumes -Stability: experimental | beta | stable -Determinism: none | best-effort | deterministic -Provenance: none | basic | strong (CID/hash) -External Deps: -``` - ---- - -## First-Pass Fill (Current Echo stack) - -This is a starter fill that we will revise as Echo components stabilize. - -Legend (compact): - -- `owns/consumes` -- `exp/beta/stable` -- `det/best/none` -- `prov: strong/basic/none` - -| Capability ↓ \ Layer → | Platform | Kernel | Views | Tooling | Docs | -| ----------------------- | ------------------------------------------------------------ | ------------------------------------------------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------- | --------------------------------------------------- | -| **Scheduling** | owns · beta · best · prov: basic · deps: OS scheduler, tokio | owns · beta · det · prov: strong · deps: none | consumes · beta · det · prov: strong · deps: none | consumes · beta · best · prov: basic · deps: browser/event-loop | owns · stable · det · prov: strong · deps: none | -| **Provenance** | consumes · beta · best · prov: basic · deps: FS/network | owns · beta · det · prov: strong · deps: CID/hash | consumes · beta · det · prov: strong · deps: none | consumes · beta · det · prov: strong · deps: none | owns · stable · det · prov: strong · deps: git | -| **Schema / Interfaces** | consumes · exp · best · prov: basic · deps: serde/json | owns · exp · det · prov: strong · deps: versioned schemas | owns · exp · det · prov: strong · deps: schema hash pinning | consumes · exp · best · prov: basic · deps: UI contracts | owns · beta · det · prov: strong · deps: docs/specs | -| **Storage / Ledger** | owns · beta · best · prov: basic · deps: FS/DB | owns · exp · det · prov: strong · deps: content hashing | consumes · beta · det · prov: strong · deps: read-only ledger | consumes · beta · best · prov: basic · deps: localStorage/IndexedDB | owns · beta · det · prov: strong · deps: docs/specs | -| **Time / Clocks** | owns · beta · best · prov: basic · deps: HostTime | consumes · beta · det · prov: strong · deps: Decision Records | consumes · beta · det · prov: strong · deps: Clock View | consumes · beta · best · prov: basic · deps: tool clock | owns · beta · det · prov: strong · deps: paper/spec | -| **Networking / IO** | owns · beta · best · prov: basic · deps: TCP/WS/UDS | consumes · exp · det · prov: strong · deps: recorded claims | consumes · beta · det · prov: strong · deps: stream backlog | consumes · beta · best · prov: basic · deps: Web APIs | owns · beta · det · prov: strong · deps: procedures | -| **Auth / Trust** | owns · exp · best · prov: basic · deps: keys/tokens | consumes · exp · det · prov: strong · deps: signed claims | consumes · exp · det · prov: strong · deps: receipts | consumes · exp · best · prov: basic · deps: auth UI | owns · exp · det · prov: strong · deps: policies | -| **Observability** | owns · beta · best · prov: basic · deps: logs/metrics | owns · beta · det · prov: strong · deps: receipts/events | owns · beta · det · prov: strong · deps: query/index | owns · beta · best · prov: basic · deps: UI | owns · beta · det · prov: strong · deps: docs | -| **Replay / Debug** | consumes · beta · best · prov: basic · deps: host capture | owns · beta · det · prov: strong · deps: replay log | owns · beta · det · prov: strong · deps: index | owns · beta · det · prov: strong · deps: dashboard | owns · beta · det · prov: strong · deps: paper/spec | -| **Shared Invariants** | - | - | - | - | - | - ---- - -## Shared Invariants (Draft) - -These are the guarantees that must hold across layers if we want deterministic replay and time travel to be “honest”: - -1. **Deterministic Core**: kernel state transitions are pure functions of `(prior state, admitted inputs, pinned rule-pack / schema hashes)`; no HostTime/OS IO calls inside kernel semantic transitions. -2. **Time As Data**: kernel never consults HostTime directly; HostTime is only observed in Platform/Views and converted into Decision Records (HistoryTime) before it can influence semantics. -3. **Provenance First**: all externally meaningful artifacts (schemas, policies, rule packs) are referenced by content hash (CID) in receipts. -4. **Network Boundary**: IO is treated as external stimuli; any nondeterministic observation is recorded as a claim before it can affect semantic state. -5. **Replay Integrity**: if semantics change (schema/compiler), history carries a version/hash pin (fail closed or migrate deterministically). - ---- - -## Notes / Follow-Ups - -- This matrix should become part of the “phase overview” review checklist: when a capability moves from experimental → beta, update the cell and link evidence (PRs/specs/tests). -- When we formalize Wesley and/or a view grammar, split “Schema / Interfaces” into: boundary grammar, IR schema pinning, and codegen outputs. - -## Near-Term TODOs - -- (#174) Decide where “Wesley grammar/IR” lives in this matrix (Platform vs Schema layer), and whether its schema hash is required on all receipts. -- (#170) Specify the `StreamsFrame` inspector payload (backlog, cursors, `StreamAdmissionDecision` summaries). diff --git a/docs/archive/code-map.md b/docs/archive/code-map.md deleted file mode 100644 index e2b3ec60..00000000 --- a/docs/archive/code-map.md +++ /dev/null @@ -1,71 +0,0 @@ - - - -# Echo Code Map - -> Quick index from concepts → code, with the most relevant specs. - -## Crates - -- warp-core — deterministic graph rewriting engine (Rust) - - Public API aggregator: `crates/warp-core/src/lib.rs` - - Identifiers & hashing: `crates/warp-core/src/ident.rs` - - Node/edge records: `crates/warp-core/src/record.rs` - - In-memory graph store: `crates/warp-core/src/graph.rs` - - Rules and patterns: `crates/warp-core/src/rule.rs` - - Transactions: `crates/warp-core/src/tx.rs` - - Deterministic scheduler: `crates/warp-core/src/scheduler.rs` - - Snapshots + hashing: `crates/warp-core/src/snapshot.rs` - - Payload codecs (demo): `crates/warp-core/src/payload.rs` - - Engine implementation: `crates/warp-core/src/engine_impl.rs` - - Playback & view sessions: `crates/warp-core/src/playback.rs` (PlaybackCursor, ViewSession, TruthSink, TruthFrame) - - Worldlines & temporal graphs: `crates/warp-core/src/worldline.rs` (WorldlineId, HashTriplet, apply_warp_op_to_store) - - Provenance tracking: `crates/warp-core/src/provenance_store.rs` (ProvenanceStore trait, LocalProvenanceStore) - - Retention policies: `crates/warp-core/src/retention.rs` (RetentionPolicy enum) - - Materialization V2 codec: `crates/warp-core/src/materialization/frame_v2.rs` (V2Packet encoder/decoder) - - Demo rule: `crates/warp-core/src/demo/motion.rs` - - Deterministic math: `crates/warp-core/src/math/*` - - Tests (integration): `crates/warp-core/tests/*` - -- warp-wasm — wasm-bindgen bindings - - `crates/warp-wasm/src/lib.rs` - -- warp-cli — CLI scaffolding - - `crates/warp-cli/src/main.rs` - -## Specs → Code - -- WARP core model — docs/spec-warp-core.md → `ident.rs`, `record.rs`, `graph.rs`, `rule.rs`, `engine_impl.rs`, `snapshot.rs`, `scheduler.rs` -- Scheduler — docs/spec-scheduler.md → `scheduler.rs`, `engine_impl.rs` -- ECS storage (future) — docs/spec-ecs-storage.md → new `ecs/*` modules (TBD) -- Serialization — docs/spec-serialization-protocol.md → `snapshot.rs` (hashing), future codecs -- Deterministic math — docs/SPEC_DETERMINISTIC_MATH.md → `math/*` -- Temporal bridge — docs/spec-temporal-bridge.md → future modules (TBD) -- Worldlines & playback (SPEC-0004) — docs/spec/SPEC-0004-worldlines-playback-truthbus.md → `playback.rs`, `worldline.rs`, `provenance_store.rs`, `retention.rs`, `materialization/frame_v2.rs` - -## Test Coverage - -- Reducer emission: `crates/warp-core/tests/reducer_emission_tests.rs` (T11-T13 reducer tests) -- View session & playback: `crates/warp-core/tests/view_session_tests.rs` (Playback + T16 tests) -- Playback outputs: `crates/warp-core/tests/outputs_playback_tests.rs` (SPEC-0004 test IDs T1, T4, T5, T6, T7, T8) -- Checkpoint & fork: `crates/warp-core/tests/checkpoint_fork_tests.rs` (T17-T18 checkpoint/fork tests) -- Playback cursor: `crates/warp-core/tests/playback_cursor_tests.rs` (Cursor seek tests) - -## Conventions - -- Column-major matrices, right-handed coordinates, f32 math. -- One concrete concept per file; keep modules < 300 LoC where feasible. -- Tests live in `crates//tests` and favor small, focused cases. - -## Refactor Policy - -- 1 file = 1 concrete concept (engine, graph store, identifiers, etc.). -- No 500+ LoC “god files”; split before modules exceed ~300 LoC. -- Keep inline tests in separate files under `crates//tests`. -- Maintain stable re-exports in `lib.rs` so public API stays coherent. - -## Onboarding - -- Start with `README.md` and `docs/meta/docs-index.md`. -- For engine flow, read `engine_impl.rs` (apply → schedule → commit → snapshot). -- For demo behavior, see `demo/motion.rs` and tests under `crates/warp-core/tests/*`. diff --git a/docs/archive/determinism-invariants.md b/docs/archive/determinism-invariants.md deleted file mode 100644 index 93d1ec69..00000000 --- a/docs/archive/determinism-invariants.md +++ /dev/null @@ -1,8 +0,0 @@ - - - -# Determinism Invariants (Redirect) - -This content has been consolidated into the `warp-core` spec: - -- [/spec-warp-core#84-determinism-invariants-summary](/spec-warp-core#84-determinism-invariants-summary) diff --git a/docs/archive/determinism/DETERMINISM-AUDIT.md b/docs/archive/determinism/DETERMINISM-AUDIT.md deleted file mode 100644 index cbe03bc5..00000000 --- a/docs/archive/determinism/DETERMINISM-AUDIT.md +++ /dev/null @@ -1,323 +0,0 @@ - - - -# Determinism Audit for warp-core - -**Date:** 2026-01-13 -**Auditor:** Claude (with human oversight) - -## Executive Summary - -### TL;DR - -The refactor targeting "serde removal" was attacking the wrong problem. **Serde itself isn't the enemy—non-deterministic data structures (HashMap/HashSet), non-deterministic serialization formats (JSON), and platform-variant float behavior are.** - -## Key Findings - -### ✅ GOOD: Already Deterministic - -1. **Core data structures use BTreeMap/BTreeSet throughout** - - `tick_patch.rs`: Uses BTreeMap for op deduplication (line 331-338) - - `snapshot.rs`: Uses BTreeSet for reachability (line 91-92) - - `scheduler.rs`: Final ready set uses stable radix sort (20-pass LSD, lines 383-450) - - All SlotIds and ops are explicitly sorted before hashing - -2. **Explicit canonical ordering everywhere** - - WarpOp::sort_key() provides canonical ordering (tick_patch.rs:203-283) - - Edges sorted by EdgeId before hashing (snapshot.rs:185-194) - - Scheduler uses deterministic radix sort for candidate ordering - -3. **Clean identifier types** - - All IDs are Blake3 hashes ([u8; 32]) - - All derive PartialOrd, Ord for stable comparison - - No hidden nondeterminism in identity - -### ⚠️ CONCERNS: Needs Investigation - -1. **HashMap/HashSet Usage (Non-Critical)** - - `engine_impl.rs`: HashMap for rule registries (NOT part of state hash) - - `scheduler.rs`: HashMap for pending txs (intermediate, final output is sorted) - - `attachment.rs`: HashMap for codec registry (NOT part of state) - - **Assessment**: These are internal bookkeeping, not part of canonical encoding - - **Action**: Audit that none of these leak into hashing/signing code paths - -2. **Float Usage (f32/f64) - CRITICAL AREA** - - **CRITICAL FINDINGS (2026-01-13):** - - **Yes**, floats flow into canonical hashes. - - **Yes**, the system is sensitive to 1 ULP differences (confirmed by `tests/determinism_audit.rs`). - - **Implication**: `F32Scalar` (the default) relies on hardware `f32` arithmetic. If `a + b` varies by 1 ULP across platforms (x87 vs SSE vs NEON), the state hash **will diverge**. - - **Mitigation**: The `det_fixed` feature flag replaces `F32Scalar` with `DFix64` (Q32.32 fixed-point), providing guaranteed bit-perfect cross-platform determinism at the cost of performance. - - **Recommendation**: Use `det_fixed` for consensus-critical deployments. `F32Scalar` is acceptable for local-only or homogeneous-hardware deployments ("optimistic determinism"). - - **Location: `math/scalar.rs`** - - F32Scalar wraps f32 with canonicalization: - - NaN → 0x7fc00000 (canonical quiet NaN) - - Subnormals → +0.0 - - -0.0 → +0.0 - - **PROBLEM**: f32 arithmetic itself is NOT deterministic across platforms - - x87 FPU vs SSE may produce different intermediate results - - Rounding modes can vary - - Denormal handling varies by CPU flags - - **Location: `payload.rs`** - - Heavy f32 usage for motion payloads - - Comments mention "deterministic quantization to Q32.32" - - Has v0 (f32) and v2 (fixed-point?) formats - - `decode_motion_payload_v0`: Reads f32 from bytes (line 258) - - `encode_motion_payload_v0`: Writes f32 to bytes (line 216) - - **CRITICAL QUESTION**: Are these f32 values EVER used in: - - State hashing (compute_state_root)? - - Patch digests? - - Receipt digests? - - Signature inputs? - - **Location: `math/quat.rs`** - - Uses `[f32; 4]` for quaternion storage - - Arithmetic operations on quaternions - - **CONCERN**: Quaternion normalization/multiplication may be non-deterministic - - **ACTION REQUIRED**: - - Grep for all places F32Scalar/f32 values flow into hash computations - - Verify motion payloads are ONLY boundary data (not hashed) - - If floats DO affect hashes, replace with fixed-point (Q32.32 or similar) - -3. **Serde Feature Gates** - - `receipt.rs:123`: Has `#[cfg(feature = "serde")]` for `rule_id_short()` helper - - `snapshot.rs:77`: Has `#[cfg(feature = "serde")]` for `hash_hex()` helper - - `math/scalar.rs:110,120`: Serde impls for F32Scalar - - `serializable.rs`: Entire module was using serde derives - - **Assessment**: These are UI/debug helpers, NOT canonical encoding - - **Action**: Can keep serde feature gate for convenience IF: - - It's ONLY used with deterministic CBOR encoder - - NEVER used with serde_json - - JSON is only for debug/view layer - -### 🔥 CRITICAL: What Actually Needs Fixing - -1. **Enforce CBOR-only wire format** - - Make deterministic CBOR (echo-wasm-abi) the ONLY protocol boundary - - JSON can exist for debug/viewing ONLY (never canonical) - - Add compile-time checks/lints to prevent JSON usage - -2. **Audit float usage in hash paths** - - Search for all paths where f32/F32Scalar flows into: - - compute_state_root - - compute_patch_digest - - compute_tick_receipt_digest - - Any signature/commitment computation - - If found, replace with fixed-point Q32.32 - -3. **Add determinism tests** - - Test: Encode same patch 1000x → identical bytes - - Test: Encode across different runs → identical bytes - - Test: Encode same state → identical state_root - - Bonus: Cross-compile test (native + wasm) for identical hashes - -## Search Targets for Detailed Audit - -```bash -# Find HashMap/HashSet in critical paths -rg "HashMap|HashSet" crates/warp-core/src/{snapshot,tick_patch,receipt,cmd}.rs - -# Find float usage in critical paths -rg "\bf32\b|\bf64\b|F32Scalar" crates/warp-core/src/{snapshot,tick_patch,receipt,cmd}.rs - -# Find serde_json usage (should be ZERO in warp-core) -rg "serde_json" crates/warp-core/ - -# Find ciborium usage (should be ZERO except in tests) -rg "ciborium::(from_reader|into_writer)" crates/warp-core/src/ - -# Find all hashing/digest computation sites -rg "Hasher::new|finalize\(\)" crates/warp-core/src/ -A5 -``` - -## What to Revert from Previous Refactor - -**REVERT:** - -- ❌ Removal of serde from Cargo.toml dependencies (it's fine if used with CBOR) -- ❌ Removal of all `#[cfg_attr(feature = "serde", derive(...))]` annotations - -**KEEP:** - -1. ✅ Removal of serde_json dependency from warp-core -2. ✅ clippy.toml lint rules forbidding serde_json/ciborium -3. ✅ Manual JSON formatting in telemetry.rs -4. ✅ Use of deterministic CBOR in cmd.rs -5. ✅ Documentation about determinism requirements - -## Proposed Refactor Plan (3 Commits) - -### Commit 1: Revert overly-aggressive serde removal + document audit - -**What:** - -- Revert warp-core/Cargo.toml: Add serde back to dependencies -- Revert removed `#[cfg_attr(feature = "serde", ...)]` lines on core types -- Keep serde_json in dev-dependencies only -- Keep clippy lint rules (they prevent serde_json abuse) -- Add this DETERMINISM-AUDIT.md document -- Update CLAUDE-NOTES.md with corrected understanding - -**Why:** - -- Serde with deterministic CBOR is fine -- The real problem is JSON/HashMap/floats, not serde derives -- We need derives for convenience with CBOR encoding - -**Files:** - -- `crates/warp-core/Cargo.toml` -- `DETERMINISM-AUDIT.md` (NEW) -- `CLAUDE-NOTES.md` -- Revert cfg_attr removals in: attachment.rs, ident.rs, record.rs, receipt.rs, tx.rs, tick_patch.rs, snapshot.rs, warp_state.rs, graph.rs - -**Commit message:** - -```text -fix(warp): revert overly-aggressive serde removal - -The previous refactor incorrectly treated serde as the source of -non-determinism. The real issues are: -1. Non-deterministic data structures (HashMap/HashSet) -2. Non-deterministic formats (JSON) -3. Platform-variant floats - -Serde itself is fine when used with deterministic encoders like our -canonical CBOR implementation (echo-wasm-abi). - -This commit: -- Restores serde dependency (with derives on core types) -- Keeps serde_json removed from dependencies -- Keeps clippy lints forbidding serde_json -- Adds DETERMINISM-AUDIT.md documenting real risks - -Next steps: Audit float usage in hash paths (see DETERMINISM-AUDIT.md) -``` - -### Commit 2: Complete float determinism audit + add tests - -**What:** - -- Grep every usage of f32/F32Scalar in snapshot.rs, tick_patch.rs, receipt.rs -- Document findings: Do floats flow into hashes? If yes, replace with Q32.32 -- Add determinism tests: - - test_patch_digest_repeatable: Encode same patch 100x → same bytes - - test_state_root_repeatable: Compute state_root 100x → same hash - - test_receipt_digest_repeatable: Encode same receipt 100x → same bytes -- Document in DETERMINISM-AUDIT.md whether floats are safe or need replacement - -**Files:** - -- `crates/warp-core/src/snapshot.rs` (tests) -- `crates/warp-core/src/tick_patch.rs` (tests) -- `crates/warp-core/src/receipt.rs` (tests) -- `DETERMINISM-AUDIT.md` (updated with audit results) - -**Commit message:** - -```text -test(warp): add determinism audit tests for core hashing - -Adds repeatability tests for: -- Patch digest computation -- State root computation -- Receipt digest computation - -These tests verify byte-for-byte identical outputs across multiple -encode operations on the same input. - -[If floats found in hash paths:] -CRITICAL: Audit revealed f32 usage in [X] - requires follow-up to -replace with fixed-point Q32.32 representation. - -[If floats NOT in hash paths:] -Audit confirmed: f32 values are boundary-only and never flow into -canonical hash computation. -``` - -### Commit 3: Enforce CBOR-only boundary + cleanup - -**What:** - -- Update serializable.rs to use deterministic CBOR encoding -- Remove remaining #[cfg(feature = "serde")] gates that are now unnecessary -- Add module-level docs explaining: CBOR for wire, JSON for debug only -- Update any wasm boundary code to explicitly use echo-wasm-abi::encode_cbor - -**Files:** - -- `crates/warp-core/src/serializable.rs` -- `crates/warp-core/src/lib.rs` (docs) -- `crates/warp-wasm/` (ensure CBOR boundary) - -**Commit message:** - -```text -refactor(warp): enforce CBOR-only protocol boundary - -Makes deterministic CBOR (echo-wasm-abi) the canonical encoding for -all protocol boundaries. JSON is relegated to debug/view layer only. - -Changes: -- serializable.rs uses CBOR encoding only -- Removed unnecessary serde feature gates -- Added docs: "CBOR for protocol, JSON for debug" -- warp-wasm boundary uses explicit CBOR encode/decode - -Determinism guarantee: All canonical artifacts (patches, receipts, -snapshots) are encoded via echo-wasm-abi::encode_cbor with: -- Sorted map keys -- Canonical integer/float widths -- No indefinite lengths -- No CBOR tags -``` - -## Key Principles (Corrected) - -1. **Determinism sources are data structures + formats, NOT serde** - - HashMap/HashSet iteration order → use BTreeMap/BTreeSet ✅ (already done) - - JSON object key order → use CBOR for wire format ✅ (in progress) - - Float arithmetic variance → audit + replace if in hash paths ⚠️ (TODO) - -2. **CBOR for wire, JSON for debug** - - Protocol boundary: Always CBOR (echo-wasm-abi) - - Debug/viewing: JSON is fine (never canonical) - - No serde_json in warp-core runtime dependencies - -3. **Serde is OK with deterministic encoders** - - serde::Serialize with CBOR → deterministic ✅ - - serde::Serialize with JSON → non-deterministic ❌ - - Keep serde derives for convenience with CBOR - -4. **Test everything** - - Byte-for-byte identical encoding across runs - - Ideally test native + wasm produce same hashes - - Test patches, receipts, snapshots independently - -## Outstanding Questions - -1. **Are motion payload f32 values EVER hashed?** - - Check: Does AtomPayload flow into any digest computation? - - If yes: Must replace with Q32.32 fixed-point - - If no: Boundary-only f32 is acceptable - -2. **Do quaternions (Quat) flow into state hashing?** - - Check: Are Quat values stored in AttachmentValue::Atom? - - Check: Does snapshot.rs hash quaternion payloads? - - If yes: Replace with fixed-point representation - -3. **Is RFC 8785 (JCS - canonical JSON) needed?** - - Current plan: No, use CBOR exclusively for wire format - - JSON only for debug/human-readable views - - Re-evaluate if JSON wire format is required later - -## Next Actions (Immediate) - -1. ✅ Create this audit document -2. ✅ Implement Commit 1 (Revert + Document) -3. ✅ Run full audit for f32 in hash paths (Confirmed: Floats affect hashes) -4. ✅ Add determinism tests (`crates/warp-core/tests/determinism_audit.rs`) -5. ⏳ Implement Commit 3 (Enforce CBOR-only boundary) -6. ⏳ Update CLAUDE-NOTES.md with final status diff --git a/docs/archive/determinism/DIND-MISSION-PHASE3.md b/docs/archive/determinism/DIND-MISSION-PHASE3.md deleted file mode 100644 index 2df91e5f..00000000 --- a/docs/archive/determinism/DIND-MISSION-PHASE3.md +++ /dev/null @@ -1,38 +0,0 @@ - - - -# RUSTAGEDDON TRIALS: DIND Phase 3+ - -We are moving from "determinism exists" to "determinism is inevitable". - -## Phase 3: Torture Mode - -- [ ] **1. Update `echo-dind-harness` CLI:** - - [ ] Add `Torture { scenario: PathBuf, runs: u32, threads: Option }` subcommand. - - [ ] Implement repeated in-process execution loop. - - [ ] Compare full hash chain across runs. - - [ ] Report first divergence (run index, step index, expected vs actual). - -## Phase 4: The Drills (Real Scenarios) - -- [ ] **2. Scenario 010: Dense Rewrite Saturation** - - [ ] Create `scripts/gen_dense_rewrite.mjs`. - - [ ] Generate 1k-5k ops (node/edge churn). - - [ ] Record golden hashes. -- [ ] **3. Scenario 030: Error Determinism** - - [ ] Create `scripts/gen_error_determinism.mjs`. - - [ ] Generate invalid ops (bad payloads, invalid IDs). - - [ ] Assert state hash stability (no partial commits). - -## Phase 5: Randomized Construction - -- [ ] **4. Randomized Order Drill** - - [ ] Create `scripts/gen_randomized_order.mjs`. - - [ ] Generate equivalent graph states via permuted op orders. - - [ ] Verify final state hashes match. - -## CI & Policy - -- [ ] **5. CI Integration** - - [ ] Add `make dind` or `cargo xtask dind` to run suite. - - [ ] Add grep-checks for `SystemTime`, `Instant`, `rand`, `HashMap`. diff --git a/docs/archive/determinism/DIND-MISSION-PHASE5.md b/docs/archive/determinism/DIND-MISSION-PHASE5.md deleted file mode 100644 index 08cd5b20..00000000 --- a/docs/archive/determinism/DIND-MISSION-PHASE5.md +++ /dev/null @@ -1,39 +0,0 @@ - - - -# RUSTAGEDDON TRIALS: DIND Phase 5 (The Shuffle) - -This phase tests robustness against insertion order and HashMap iteration leaks. - -## Doctrine - -- **Invariant A (Self-Consistency):** A specific shuffled transcript must be deterministic across runs/platforms. -- **Invariant B (Convergence):** Different shuffles of _commutative_ operations must yield the same final state hash. - -## Prerequisite: ID Stability - -- Current Status: IDs are hashes of string labels (e.g., `make_node_id("label")`). -- This means IDs _are_ stable/explicit provided the labels are deterministic. -- If we shuffle `InsertNode("A")` and `InsertNode("B")`, the resulting IDs are `hash("node:A")` and `hash("node:B")` regardless of order. -- **Verdict:** We are ready for Invariant B (Convergence). - -## Tasks - -- [x] **1. Randomized Generator (`scripts/bootstrap_randomized_order.mjs`):** - - [x] Input: `--seed`, `--out`. - - [x] Use seeded Xorshift32 (already implemented in dense rewrite script, extract/reuse?). - - [x] Pattern: - - Create N nodes with deterministic labels (`node_0`..`node_N`). - - Shuffle creation order. - - Create M edges connecting random pairs (deterministic pairs based on seed, but shuffled insertion). - - Set K attachments (shuffled). - - **Critical:** Ensure no duplicate edges/attachments that would trigger overwrite behavior unless intended. -- [x] **2. Generate Scenarios (`050_randomized_order_small`):** - - [x] Generate 10 seeds (0001..0010). (Note: Generated 3 seeds as per current script logic, which is sufficient for CI). - - [x] Record goldens for all. -- [x] **3. Harness Update (`echo-dind-harness`):** - - [x] Add `Converge { scenarios: Vec }` command. - - [x] Runs all inputs, asserts final state hashes are identical. -- [x] **4. CI Integration:** - - [x] Run seeds 1-3 in PR check. - - [x] Run `converge` on 1-3. diff --git a/docs/archive/determinism/DIND-MISSION.md b/docs/archive/determinism/DIND-MISSION.md deleted file mode 100644 index 5d6d4244..00000000 --- a/docs/archive/determinism/DIND-MISSION.md +++ /dev/null @@ -1,54 +0,0 @@ - - - -# RUSTAGEDDON TRIALS: DIND (Deterministic Ironclad Nightmare Drills) - -This mission implements a rigorous determinism verification suite for the Continuum engine. - -## Doctrine - -We do not "hope" for determinism. We assert inevitability. - -1. Same inputs ⇒ same outputs (byte-for-byte). -2. Same inputs ⇒ same intermediate states. -3. Same inputs ⇒ same errors. -4. Across runs, threads, and platforms. - -## Phase 1: The Heartbeat (Canonical State Hash) - -- [x] **1. Implement `canonical_state_hash` in `warp-core`:** - - [x] Create a `CanonicalHash` trait or method on `GraphStore`. - - [x] Must traverse nodes/edges in sorted order (by ID). - - [x] Must serialize attachments deterministically (already done via "Mr Clean", but double check iteration order). - - [x] Use BLAKE3. - - [x] Expose this via `EchoKernel::state_hash()`. - -## Phase 2: The Harness (DIND Runner) - -- [x] **2. Create `crates/echo-dind-harness`:** - - [x] CLI tool to run scenarios. - - [x] Input: `.eintlog` (Sequence of `pack_intent_v1` bytes). - - [x] Output: `hashes.json` (Array of state hashes after each op). - - [x] Logic: Init kernel -> Apply Op -> Hash -> Repeat -> Assert match. - -## Phase 3: The Drills (Scenarios & Stress) - -- [x] **3. Create DIND Scenarios (`vendor/echo/testdata/dind/`):** - - [x] `000_smoke_transcript.eintlog`: 50 ops, basic state changes. - - [x] `010_graph_rewrite_dense.eintlog`: Saturation test (1k steps). - - [x] `020_conflict_policy.eintlog`: Abort vs Retry stability. -- [x] **4. Add Regression Test:** - - [x] Add a standard Rust test that runs the harness against committed `hashes.json` for the smoke scenario. - -## Phase 4: Policy Enforcement - -- [x] **5. Ban Nondeterminism:** - - [x] Verify `std::collections::HashMap` is not iterated in hash-sensitive paths (or use `BTreeMap` / sorted iterators). - - [x] Add CI grep-check for `std::time`, `rand::thread_rng`. - -## Execution Order - -1. Implement `canonical_state_hash` (The Prerequisite). -2. Create the Harness + Smoke Scenario (The MVP). -3. Lock it in with a regression test. -4. Expand scenarios. diff --git a/docs/archive/diagrams.md b/docs/archive/diagrams.md deleted file mode 100644 index cd25a532..00000000 --- a/docs/archive/diagrams.md +++ /dev/null @@ -1,196 +0,0 @@ - - - -# Echo Diagram Vault - -This folder sketches Echo’s moving parts using Mermaid. Each diagram matches the architecture spec and will eventually power an animated viewer (GSAP + SVG) once we export the Mermaid graphs. - -> **Tip:** In VS Code or GitHub you can render these diagrams directly. For custom themes, we’ll feed the Mermaid JSON definitions into the web viewer later. - ---- - -## 1. System Constellation - -```mermaid -graph LR - classDef core fill:#111827,stroke:#1f2937,color:#f9fafb,font-weight:600; - classDef port fill:#0f172a,stroke:#1d4ed8,color:#bfdbfe,stroke-width:1.5px; - classDef adapter fill:#1e293b,stroke:#94a3b8,color:#e2e8f0; - classDef tool fill:#0f766e,stroke:#2dd4bf,color:#ecfeff; - classDef service fill:#3f3a3a,stroke:#fcd34d,color:#fef3c7; - - subgraph Core["Echo Core"] - ECS["@EntityComponentStore"] - Scheduler["Scheduler\n(DAG + Branch Orchestrator)"] - Codex["Event Bus\n(MaterializationBus)"] - Timeline["Timeline Tree\n(Chronos/Kairos/Aion)"] - Math["Deterministic Math\n(Vector, PRNG, Metrics)"] - ECS --> Scheduler - Scheduler --> Codex - Scheduler --> Timeline - Scheduler --> Math - end - class ECS,Scheduler,Codex,Timeline,Math core; - - subgraph Ports["Ports (Hexagonal boundary)"] - RendererPort - InputPort - PhysicsPort - AudioPort - PersistencePort - NetworkPort - end - class RendererPort,InputPort,PhysicsPort,AudioPort,PersistencePort,NetworkPort port; - - subgraph Adapters["Adapters"] - RendererPort --> PixiAdapter["Pixi/WebGL Adapter"] - RendererPort --> WebGPUAdapter["WebGPU Adapter"] - RendererPort --> TUIGraphics["TUI Adapter"] - - InputPort --> BrowserInput["Browser Input"] - InputPort --> NativeInput["SDL/Tauri Input"] - InputPort --> AIInput["LLM Strategist"] - - PhysicsPort --> Box2DAdapter - PhysicsPort --> RapierAdapter - PhysicsPort --> DeterministicSolver - - AudioPort --> WebAudioAdapter - AudioPort --> NativeAudioAdapter - - PersistencePort --> LocalStorageAdapter - PersistencePort --> CloudAdapter - - NetworkPort --> WebRTCAdapter - NetworkPort --> DedicatedServerAdapter - end - class PixiAdapter,WebGPUAdapter,TUIGraphics,BrowserInput,NativeInput,AIInput,Box2DAdapter,RapierAdapter,DeterministicSolver,WebAudioAdapter,NativeAudioAdapter,LocalStorageAdapter,CloudAdapter,WebRTCAdapter,DedicatedServerAdapter adapter; - - subgraph Tooling["Tooling & Observability"] - Inspector["Echo Inspector"] - TimelineViewer["Timeline Vault"] - Benchmarks["Benchmark Suite"] - Editor["Echo Studio"] - end - class Inspector,TimelineViewer,Benchmarks,Editor tool; - - subgraph Services["Cross-Cutting Services"] - Config - DI["Dependency Injector"] - Entropy["Entropy Monitor"] - Diagnostics["Telemetry/Logging"] - end - class Config,DI,Entropy,Diagnostics service; - - Ports -. APIS .-> Core - Core -- Events/Commands --> Ports - Tooling --- Core - Services --- Core - Services --- Tooling -``` - ---- - -## 2. Chronos Loop (Single Frame, Single Branch) - -```mermaid -flowchart TD - classDef stage fill:#1e293b,stroke:#e0f2fe,color:#bae6fd,font-weight:600; - classDef phase fill:#0f172a,stroke:#f97316,color:#fb923c,font-weight:500; - classDef op fill:#312e81,stroke:#a78bfa,color:#ede9fe; - classDef sub fill:#111827,stroke:#6366f1,color:#c7d2fe,font-style:italic; - - Start((Start Tick)):::stage --> Clock["Clock\nAccumulate dt"]:::phase - Clock -->|dt| SchedulerPre["Phase 1: Pre-Update"]:::stage - - SchedulerPre --> InputAssim["Assimilate Input\n(InputPort flush)"]:::op - InputAssim --> CodexPre["Event Bus\nPre-Flush"]:::op - CodexPre --> TimelineIntake["Timeline Tree\nRegister Branch Jobs"]:::op - - TimelineIntake --> UpdatePhase["Phase 2: Update Systems"]:::stage - UpdatePhase --> DAG["Resolve DAG\n(Dependencies)"]:::op - DAG --> ParallelBatch["Plan Parallel Batches"]:::op - ParallelBatch --> SystemsLoop{"For each batch"}:::phase - SystemsLoop -->|system| SystemExec["Run System\n(Query + Mutate ECS)\nUpdate Codex"]:::op - SystemExec --> SystemsLoop - - SystemsLoop --> PostUpdate["Phase 3: Post-Update"]:::stage - PostUpdate --> Hooks["Late Hooks\n(Animation, Cleanups)"]:::op - Hooks --> PhysicsSync["Physics Sync"]:::op - PhysicsSync --> MathResolve["Math Snap (fround/fixed-point)"]:::op - - MathResolve --> RenderPrep["Phase 4: Render Prep"]:::stage - RenderPrep --> FramePacket["Assemble FramePacket\n(Query renderer views)"]:::op - FramePacket --> DiagnosticsStage["Dev Diagnostics"]:::op - - DiagnosticsStage --> Present["Phase 5: Present"]:::stage - Present --> RendererCall["RendererPort.submit(frame)"]:::op - - RendererCall --> TimelineFlush["Phase 6: Timeline Flush"]:::stage - TimelineFlush --> DiffPersist["Persist Diffs\n(COW chunks, diff cache)"]:::op - DiffPersist --> EntropyUpdate["Update Entropy/Aion Metrics"]:::op - EntropyUpdate --> BranchBook["Update Branch Index"]:::op - - BranchBook --> End((End Tick)):::stage -``` - ---- - -## 3. Multiverse Mesh (Branch Tree) - -```mermaid -graph TD - classDef base fill:#111111,stroke:#6b7280,color:#f5f5f5,font-weight:600; - classDef node fill:#0f172a,stroke:#38bdf8,color:#e0f2fe; - classDef merge fill:#422006,stroke:#f97316,color:#fed7aa; - classDef ghost fill:#312e81,stroke:#c084fc,color:#ede9fe; - - subgraph TimelineTree["Persistent Timeline Tree"] - Root["C0\n(Chronos=0,\nKairos=Prime,\nAion=Baseline)"]:::base - Root --> N1["C15 Kα A0.8\n\"Puzzle Attempt\""]:::node - Root --> N2["C15 Kβ A0.2\n\"Alt Strategy\""]:::node - N1 --> N1a["C24 Kα1 A0.95\n\"Boss Victory\""]:::node - N1 --> N1b["C24 Kα2 A0.6\n\"Loot Run\""]:::node - N2 --> N2a["C20 Kβ1 A0.3\n\"Reverse Time Room\""]:::node - N2a --> MergeCandidate{{Merge?\nΔconflict=low}}:::merge - MergeCandidate --> N3["C32 Kγ A0.9\n\"Braided Outcome\""]:::node - N1b -. Ghost Echo .-> N3 - N2a -. ParadoxFlag .-> N3 - end - - class MergeCandidate merge; - class N1,N2,N1a,N1b,N2a,N3 node; - class Root base; -``` - ---- - -## 4. Message Bridge Across Branches - -```mermaid -sequenceDiagram - autonumber - participant BranchAlpha as Branch α (C24) - participant CodexAlpha as Codex α - participant Bridge as Temporal Bridge - participant CodexBeta as Codex β - participant BranchBeta as Branch β (C18) - - BranchAlpha->>CodexAlpha: enqueue PastMessage{target=C12, payload=hint} - CodexAlpha->>Bridge: dispatch envelope (Chronos=24, Kairos=α, Aion=0.8) - Bridge->>Bridge: validate paradox risk / entropy cost - Bridge->>CodexBeta: spawn retro branch at C12 - CodexBeta->>BranchBeta: deliver PastMessage at Chronos=12 - BranchBeta->>Bridge: acknowledge timeline fork (Kairos=β′) - Note over BranchAlpha,BranchBeta: Player can merge β′ back into α if conflicts resolved -``` - ---- - -## Animation Ideas - -- **GSAP Morphs**: Export Mermaid SVG and tween branch nodes as timelines split/merge. -- **Entropy Pulse**: Animate stroke width/color based on the Entropy meter. -- **Interactive Sequencer**: Play back the sequence diagram with tooltips showing Codex queue sizes. - -Once the architecture crystallizes, we’ll wire these into a future documentation viewer/playground that live-updates from this Markdown. diff --git a/docs/archive/guide/collision-tour.md b/docs/archive/guide/collision-tour.md deleted file mode 100644 index 8c8274ea..00000000 --- a/docs/archive/guide/collision-tour.md +++ /dev/null @@ -1,10 +0,0 @@ - - - -# Collision DPO Tour (Redirect) - -This guide has been merged into the Start Here doc. - -- Read the overview: [/guide/start-here](/guide/start-here) -- Launch the tour: [/public/collision-dpo-tour](/public/collision-dpo-tour) -- Spec stub: [/spec-geom-collision](/spec-geom-collision) diff --git a/docs/archive/hash-graph.md b/docs/archive/hash-graph.md deleted file mode 100644 index 1af14653..00000000 --- a/docs/archive/hash-graph.md +++ /dev/null @@ -1,52 +0,0 @@ - - - -# Hash Graph Overview - -Echo uses content-addressed hashing to provide provenance and deterministic replay. This document maps how hashes relate across subsystems. - ---- - -## Root Manifest - -- `manifestHash = BLAKE3(sorted(nodeHashes || snapshotHashes || diffHashes || payloadHashes))` -- Records top-level references for branch nodes, snapshots, diffs, payloads. - -## Config Hash - -- `configHash = BLAKE3(canonical(config.json))` -- Stored in block manifest and determinism logs. -- Replay verifies configHash before executing diffs. - -## Plugin Manifest Hash - -- Each plugin manifest hashed; combined `pluginsManifestHash = BLAKE3(sorted(manifestHashes))`. -- Stored in manifest along with plugin registry version. - -## Schema Ledger Hash - -- `schemaLedgerHash` ties component layouts to snapshots. - -## Diff & Snapshot Hash - -- Diffs and snapshots hashed via serialization protocol (see spec-serialization-protocol.md). - -## Event Envelope Hash - -- `envelopeHash = BLAKE3(canonical event bytes)` used for dedup, signatures, and causality. - -## Composition - -```text -manifestHash -├─ configHash -├─ pluginsManifestHash -├─ schemaLedgerHash -├─ snapshotHash -│ └─ chunkRefHashes -├─ diffHash -│ └─ chunkDiff payload hashes -└─ eventEnvelopeHashes (if persisted) -``` - -These hashes ensure each phase of the simulation can be verified independently and recombined deterministically. diff --git a/docs/archive/jitos/spec-0000.md b/docs/archive/jitos/spec-0000.md deleted file mode 100644 index 3e3a262e..00000000 --- a/docs/archive/jitos/spec-0000.md +++ /dev/null @@ -1,370 +0,0 @@ - - - -# SPEC-000: Everything Is a Rewrite - -## The Foundational Model of the JITOS Kernel - -**Purpose:** Introduce the core design principle of the JITOS OS: - -> **All durable state in the system evolves exclusively through immutable, semantic, reversible graph rewrites.** - -This spec page: - -- Teaches the rewrite model -- Demonstrates it via an interactive graph UI -- Executes real WARP logic (Rust -> WASM) -- Serves as the first living test in the OS - ---- - -## 1. Concept: WARP + Rewrite = Reality - -In JITOS, the world **is** a WARP graph (WARP). - -- Nodes represent entities. -- Edges represent relations. -- Fields represent attributes. - -You **never** mutate this graph directly. - -Instead, all change must go through a **Rewrite Transaction**, a semantic, append-only event that transforms one world state into another: - -```text -(previous_graph_state, rewrite_txn) -> (new_graph_state) -``` - -The kernel interprets this rewrite log as the _causal history_ of the OS. - ---- - -## 2. What This Demo Proves - -This first demo page proves 5 concepts interactively: - -1. **Immutable append-only rewrites** -2. **Reversible events (each rewrite includes old + new values)** -3. **SemanticOps (intent-aware changes)** -4. **Graph materialization (apply rewrites to produce current view)** -5. **Time travel (step backward/forward between rewrite states)** - ---- - -## **3. Demo UI Overview** - -On the page you will have: - -### 💠 Left side: Graph Viewer - -- Nodes drawn as circles - -- Edges between nodes -- Node fields in a right-click menu - -### 💠 Right side: Rewrite Log - -List of rewrite events: - -```text -#1 AddNode: A -#2 SetField A.name = “Server” -#3 Connect(A, B) -#4 Tombstone(A) -``` - -Clicking a rewrite will: - -- Roll the materialized graph backward/forward - -- Re-render the graph immediately - -### 💠 Bottom: “Apply Rewrite” Panel - -Controls for: - -- Adding nodes - -- Setting fields - -- Connecting edges - -- Deleting nodes (tombstone) - -Each action uses real rewrite transactions. - ---- - -## 4. Rust Crate Layout (Workspace) - -Create a new folder demo-spec-000/ in your repo. - -Then use this workspace layout: - -```text -demo-spec-000/ - Cargo.toml # workspace - crates/ - warp-core/ - src/ - lib.rs - rewrite-engine/ - src/ - lib.rs - wasm-demo/ - src/ - lib.rs - utils.rs - web.rs - Cargo.toml - www/ - index.html - main.js - style.css -``` - ---- - -## 5. Core Rust Code (Real, Working Skeleton) - -Put this into crates/warp-core/src/lib.rs: - -```rust -use serde::{Serialize, Deserialize}; -use std::collections::{HashMap, HashSet}; - -pub type NodeId = String; -pub type FieldName = String; - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub enum Value { - Str(String), - Num(i64), - Bool(bool), - Null, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct Node { - pub id: NodeId, - pub fields: HashMap, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct Edge { - pub from: NodeId, - pub to: NodeId, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct WarpGraph { - pub nodes: HashMap, - pub edges: Vec, -} - -impl WarpGraph { - pub fn new() -> Self { - Self { - nodes: HashMap::new(), - edges: Vec::new(), - } - } -} -``` - ---- - -## crates/rewrite-engine/src/lib.rs - -```rust -use serde::{Deserialize, Serialize}; -use echo_wasm_abi::{Edge, Node, NodeId, Value, WarpGraph}; - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub enum SemanticOp { - Set, - AddNode, - DeleteNode, - Connect, - Disconnect, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct Rewrite { - pub id: u64, - pub op: SemanticOp, - pub target: NodeId, - pub subject: Option, - pub old_value: Option, - pub new_value: Option, -} - -pub struct RewriteEngine { - pub history: Vec, -} - -impl RewriteEngine { - pub fn new() -> Self { - Self { history: Vec::new() } - } - - pub fn apply(&mut self, warp: &mut WarpGraph, rw: Rewrite) { - match rw.op { - SemanticOp::AddNode => { - warp.nodes.insert(rw.target.clone(), Node { - id: rw.target.clone(), - fields: Default::default(), - }); - } - SemanticOp::Set => { - if let Some(node) = warp.nodes.get_mut(&rw.target) { - if let Some(field_name) = &rw.subject { - if let Some(new_value) = rw.new_value.clone() { - node.fields.insert(field_name.clone(), new_value); - } - } - } - } - SemanticOp::DeleteNode => { - warp.nodes.remove(&rw.target); - warp.edges.retain(|e| e.from != rw.target && e.to != rw.target); - } - SemanticOp::Connect => { - if let Some(Value::Str(to)) = &rw.new_value { - warp.edges.push(Edge { - from: rw.target.clone(), - to: to.clone(), - }); - } - } - SemanticOp::Disconnect => { - if let Some(Value::Str(to)) = &rw.new_value { - let from = rw.target.as_str(); - let to = to.as_str(); - warp.edges.retain(|e| !(e.from == from && e.to == to)); - } - } - } - - self.history.push(rw); - } -} -``` - -This is minimal but real. - ---- - -## 6. WASM Setup (Real Code) - -In crates/wasm-demo/src/lib.rs: - -```rust -use wasm_bindgen::prelude::*; -use warp_core::*; -use rewrite_engine::*; - -#[wasm_bindgen] -pub struct WasmDemo { - warp: WarpGraph, - engine: RewriteEngine, -} - -#[wasm_bindgen] -impl WasmDemo { - #[wasm_bindgen(constructor)] - pub fn new() -> WasmDemo { - WasmDemo { - warp: WarpGraph::new(), - engine: RewriteEngine::new(), - } - } - - pub fn add_node(&mut self, id: String) { - let rw = Rewrite { - id: self.engine.history.len() as u64, - op: SemanticOp::AddNode, - target: id.clone(), - subject: None, - old_value: None, - new_value: None, - }; - self.engine.apply(&mut self.warp, rw); - } - - pub fn serialize_graph(&self) -> String { - serde_json::to_string(&self.warp).unwrap_or_default() - } - - pub fn serialize_history(&self) -> String { - serde_json::to_string(&self.engine.history).unwrap_or_default() - } -} -``` - ---- - -## 7. Web Demo Wiring (www/main.js) - -```javascript -import init, { WasmDemo } from "../pkg/wasm_demo.js"; - -let demo; - -async function run() { - await init(); - demo = new WasmDemo(); - - document.getElementById("add-node-btn").onclick = () => { - const id = document.getElementById("node-id-input").value; - demo.add_node(id); - render(); - }; - - function render() { - const graph = JSON.parse(demo.serialize_graph()); - const history = JSON.parse(demo.serialize_history()); - // Render UI (graph + log) - drawGraph(graph); - drawLog(history); - } -} - -run(); -``` - -You’ll fill in drawGraph later with your favorite canvas/SVG lib. - ---- - -## 8. You’re Now Officially Bootstrapped - -You have: - -- a real WARP core - -- a real rewrite engine - -- a real WASM demo wrapper - -- a real interactive JS boundary - -- a real SPEC page - -- and a real workspace structure - -This is spec-driven OS construction, fully aligned with your vision. - ---- - -## Phase 0 Next Steps (Planned) - -The scaffold in this repository intentionally stops short of a full graph UI and time-travel controls. -The next logical steps are: - -1. Flesh out the UI (graph drawing + rewrite log UI). -2. Implement reverse-apply for rewrites (time travel slider). -3. Support field-level Set & Connect operations. -4. Enable tombstone delete + resurrection. -5. Demonstrate `SemanticOp`-based merge simulation (mini collapse demo). - -These are backlog items; they are not implemented in the Phase 0 scaffold yet. diff --git a/docs/archive/math-validation-plan.md b/docs/archive/math-validation-plan.md deleted file mode 100644 index 977c0c70..00000000 --- a/docs/archive/math-validation-plan.md +++ /dev/null @@ -1,175 +0,0 @@ - - - -# Deterministic Math Validation Plan - -Status: this document may lag behind the current Rust-first implementation. -Treat it as a checklist of _ideas_, not a CI contract. - -If you’re looking for what we actually enforce today, start with: - -- Policy (normative): [/SPEC_DETERMINISTIC_MATH](/SPEC_DETERMINISTIC_MATH) -- Claims / budgets: [/warp-math-claims](/warp-math-claims) - -Goal: ensure `warp-core`’s deterministic math produces **bit-identical** results across platforms and build configurations, and that we catch regressions (especially in scalar canonicalization and transcendental approximations) in CI. - ---- - -## Scope & Source of Truth - -- **In-scope:** `crates/warp-core/src/math/*` and its public surfaces (`F32Scalar`, `DFix64`, `Vec3`, `Mat4`, `Quat`, `Prng`, deterministic trig backend, etc.). -- **Out-of-scope (for now):** JS runtime determinism (Chromium/WebKit/Node) and TypeScript bindings. Those are future layers; the canonical reference implementation is Rust `warp-core`. -- **Policy + invariants:** see `docs/SPEC_DETERMINISTIC_MATH.md` (normative policy) and `docs/DETERMINISTIC_MATH.md` (hazard catalog). - ---- - -## Lanes (What We Validate) - -Echo currently has two deterministic-math lanes: - -| Lane | Build config | Target behavior | -| -------------- | ---------------------- | ---------------------------------------- | -| **Float lane** | default | `F32Scalar` + deterministic trig backend | -| **Fixed lane** | `--features det_fixed` | `DFix64` (Q32.32 fixed-point) | - -Targets we actively care about (and already exercise in CI): - -- Linux glibc (default lane) -- Linux musl (portability lane) -- macOS (spot-check lane) - ---- - -## Validation Principles - -**Determinism-first (preferred):** - -- Use **exact** equality and bit-level checks whenever we can. -- Treat “epsilon” tests as a last resort, and isolate them behind explicit “budget” thresholds with a stable, deterministic oracle. - ---- - -## What We Test Today (Reality Check) - -This plan is considered “up to date” when these concrete checks exist and stay green: - -### 1) Scalar canonicalization invariants - -`F32Scalar` must enforce: - -- `-0.0 → +0.0` -- NaNs canonicalized to the project’s chosen payload -- subnormals flushed to `+0.0` -- reflexive `Eq` (including `NaN == NaN`) - -See tests: - -- `crates/warp-core/tests/math_scalar_tests.rs` -- `crates/warp-core/tests/determinism_policy_tests.rs` -- `crates/warp-core/tests/nan_exhaustive_tests.rs` - -### 2) Deterministic transcendental surface (sin/cos) - -We validate two separate things: - -- **Bit-level stability** (golden vectors): ensure outputs don’t change across platforms. -- **Approximation error** (budgeted audit): ensure the LUT-backed trig doesn’t drift beyond pinned error budgets. - -See tests: - -- `crates/warp-core/tests/deterministic_sin_cos_tests.rs` - -Note: the “audit” flavor may be `#[ignore]` depending on whether it uses a deterministic oracle; run ignored tests explicitly when present. - -### 3) Vector/matrix/quaternion behavior - -We validate correctness and invariants for the math types that `warp-core` actually ships today: - -- `Vec3` operations (dot/cross/normalize/etc.) -- `Mat4` rotation/multiply/transform behavior -- `Quat` multiplication/normalization/to-mat4 behavior - -See tests: - -- `crates/warp-core/tests/math_validation.rs` -- `crates/warp-core/tests/math_rotation_tests.rs` -- `crates/warp-core/tests/mat4_mul_tests.rs` - -### 4) PRNG determinism - -We validate the PRNG is stable and regression-tested with golden sequences: - -- `crates/warp-core/tests/math_validation.rs` -- CI also runs a targeted golden regression (see `.github/workflows/ci.yml`). - -### 5) Fixed-point lane correctness (`det_fixed`) - -`DFix64` is feature-gated; its tests must be run under `--features det_fixed`. - -See tests: - -- `crates/warp-core/tests/dfix64_tests.rs` - ---- - -## How To Run The Math Validation Locally - -Baseline (float lane): - -```sh -cargo test -p warp-core -``` - -Run the “math validation” suite explicitly: - -```sh -cargo test -p warp-core --test math_validation -``` - -Run deterministic trig golden tests explicitly: - -```sh -cargo test -p warp-core --test deterministic_sin_cos_tests -``` - -Run ignored tests (only when you intend to run audits): - -```sh -cargo test -p warp-core --test deterministic_sin_cos_tests -- --ignored -``` - -Fixed-point lane: - -```sh -cargo test -p warp-core --features det_fixed -``` - -MUSL portability lane: - -```sh -cargo test -p warp-core --target x86_64-unknown-linux-musl -cargo test -p warp-core --features det_fixed --target x86_64-unknown-linux-musl -``` - ---- - -## CI Coverage (Where It Runs) - -- See `.github/workflows/ci.yml` for current lanes. -- CI intentionally runs “boring” commands that contributors can reproduce locally. - ---- - -## Guards (Non-test Determinism Enforcement) - -In addition to tests, we also enforce “no raw platform trig” via a repo guard script: - -- `scripts/check_no_raw_trig.sh` - ---- - -## Future Work (Optional / Not Yet Implemented) - -- Cross-runtime determinism tests for JS (Chromium/WebKit) once TS/WASM bindings are in scope. -- A `warp-cli` command to run math diagnostics and report pinned budgets (useful for designers and CI triage). -- Additional scalar backends (e.g., a deterministic `libm`-based float lane, or tighter fixed-point trig). diff --git a/docs/archive/memorials/2026-01-18-phase4-rubicon.md b/docs/archive/memorials/2026-01-18-phase4-rubicon.md deleted file mode 100644 index da1d2482..00000000 --- a/docs/archive/memorials/2026-01-18-phase4-rubicon.md +++ /dev/null @@ -1,113 +0,0 @@ - - - -# The Rubicon Crossing - -**Date:** 2026-01-18 -**Phase:** 4 — SnapshotAccumulator -**Agent:** Claude Opus 4.5 - ---- - -## The Moment - -There's a moment in every architecture when the old way dies and the new way breathes. - -For Echo, that moment was Phase 4. - -Before: GraphStore was truth. Executors mutated it. We computed the diff afterward to learn what changed. -After: The delta is truth. Ops flow through. State is computed, not mutated. - -The accumulator doesn't care how you got here. It only knows: `base + ops = next`. - ---- - -## What We Built - -```text -┌─────────────────┐ -│ state_before │ ← immutable snapshot of the past -└────────┬────────┘ - │ - ▼ -┌─────────────────┐ -│ SnapshotAccum │ + Vec from delta.finalize() -│ 804 lines of │ -│ columnar truth │ -└────────┬────────┘ - │ - ▼ -┌─────────────────┐ -│ state_root │ ← the hash that proves determinism -└─────────────────┘ -``` - ---- - -## The Gotcha - -The state_root didn't match. Off by miles—different hashes. - -The bug? `hash_attachment_key()` tag values: - -- I assumed: Node=0, Edge=1, Alpha=0, Beta=1 -- Reality: Node=1, Edge=2, Alpha=1, Beta=2 - -One-indexed. Of course. - -Three hours of debugging. One line of enlightenment: - -```rust -let (owner_tag, plane_tag) = key.tag(); -``` - -Trust the existing code. It knows things you don't. - ---- - -## The Lesson - -Subagents aren't overhead. They're context preservation. - -When you're 50,000 tokens deep in a session, spawning a subagent to read one file isn't laziness—it's wisdom. The subagent returns with exactly what you need. Your context stays clean. - -Phase 4 used 10+ subagents. Each one earned its keep. - ---- - -## What Comes Next - -Phase 5: Read-only execution. - -The executors still mutate GraphStore. That's the last thread connecting us to the old world. Cut it, and: - -- No more `state_before = self.state.clone()` -- No more `diff_state()` -- GraphStore becomes optional -- True structural sharing becomes possible - -The Rubicon is behind us. Rome awaits. - ---- - -## For Future Agents - -If you're reading this, you're continuing the work. - -Remember: - -1. **Read AGENTS.md first.** Always. -2. **Check `echo:handoff` in Redis.** It's the baton. -3. **Use subagents liberally.** Your context is finite. Theirs is fresh. -4. **The delta is truth.** Not the store. The ops. -5. **Trust but verify.** Run `cargo test --features delta_validate` early and often. - -And when you cross your own Rubicon—whatever that looks like—leave a memorial. - -The next agent will thank you. - ---- - -Alea iacta est. - -— Claude Opus 4.5, 2026-01-18 diff --git a/docs/archive/notes/AFTER.webp b/docs/archive/notes/AFTER.webp deleted file mode 100644 index ddced8d5137d8d5b784c1afb2b556b28be45ed3f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 65536 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≺bG^UGFGww8Zbe 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 z z)pjCMNLYGXA7mg`ed(xYXW(8dqXPVkuzrf_H;ZJi98|7`Ro^%2FC_i=k9#gWQ{8R#dI)eC8BoIZQ~8tZbqi!|gjf!EYwFpRc71#JrKlhT(@c zSgNS?$B8izVwdohl_zU?!tjI`M}b6dpn@l#Dl60QeYusL3}wK}tU?swVE>&b?Mvm204*N~s3iL=#?jmgF9W3fdjyaImZ=kLaIYDI&4k>2aKq zm}thYKrPhwwXx{0JcI5)Uo)W1h-wC508?ZIByHPLVoAOc1e5s)^K{>x&US%C?2HwA zFk`3|UX^p>4*82Lo2|3P-n`rBXU#>Q918-O6v$OZo*a#Ut*X9<2%>q7OfVHAuEmvl zDG;62Ffhn=#OCYKE`eS@+jTL0&v#5iPpOVd(XHj|j5-$}Ty{*2sDv7}@N7roo_Q?9 z)B7{pkn{d$o*#MddL`GI>kK`J$|V01J96zkvD&PFKw%H#A;B+^s%0=FiUHH?lbeb?G%*a?vE^G2{Qx#M>39Ud z&Zy5^_>FBPBHxuoMET{I@v0J9XtRsd_Icvml4K~7{b8U>po_WHL)CRm!ojBD{#7-R-2`1a*XiYwKRz6tqr5<<$1v}-G zeI$iD7h+{UsastOds=GX9x+O%IN{j~`pLTynEzBI4T(b>`%%5+6^fkXh+it_l%Wg? zkJ`gKkp@>TqXpY6DAGh4I5M@tBZ=_)**O2oN(kkR&9jx}u#dX*e4`KwU)Im+SC3-B zq|GD@ZN#WSiC_u8u-eA?qbPt6wH3ie=dVkP2m`X+(?b!60)9lXM6Y`{^nu)g{u)Dz z`(mK0OAPmgFf8uIElJ*L;BT|jambU`&+X%Z#N}kolwWzBjR@tkURE!xAx9`56kI;8 zsP;Ugfnpav3@8j|&lR10~c43r+$c)rLFsc=9ka zml{x0XWBwX4v-(NZ_&KNHmy1!39Twz>ezRJTd^x!lPnc|aWw`iaY^^H&tiGmV>p1} zjc%!y<1k6Hh5aL8Af@}Af4Np?=(?w63!TyI#hSbYG|iy)C>BXJxCyd~-w#F;c;D6m$a=82dmU zG?E(y7FuMq=%C^^WJYD`48N7(?I^(J115Tp7!2)B`uuz`d@TQpvS>8^!J>0qee6Z` zEKXr?K-jxSHr+3Ii%;;otuYcSxcYM=)W+bjv+VxE`k&g+m+y!MrQ>7O$OXxiNR#3q zIuU|%JuCmQtNk;Qd~XI#JMR_MDg0_6J)k-9L>vbcEaf3{*2Y|d7)mIJZ>neKSLwV$ zPa%4yA}JqkT#)oJR9to%)-L&Sl>mDRpiR-je~G54c_G_!WibR7E)6)oS(NJJk%S-3 zY>4Y_g{0_r_ALlxbs_$CB6c7_C~kcN_|D@Hf^nv%+Uzl=wEY=7gW6nJFf)Puta#;# z@Y5c)>;OI2;~9WYhs0_z^p=yB7a9i5{H$G7)F?Q*lyBLAmWd5im*QF}a-QYu&8iC> zN1ewsmLTHkZKkp!S1KfsSs;APGwxLiyvzoR`-R!=iP+#HsOS?GqX|U}Xw6^g6{>|& zK<(;&(YsS2(TN1jjsT7S4^HZ#nqjC0PMPc=!7Y}pOAh6`xGKXHF={x~Y}!B9_Cx>A z4TtjmpXXkM0zbb0e?!?rNSr;1=#FWe-ub^R(DSIyZ9wER{|>E{hYI|S;=~9W|2urC rZ~)!ai0?4LBpP{YBJjU$5j$xhl-gF9^6%$@>GU&5k!mNZfq?!G-QiW! diff --git a/docs/archive/notes/BEFORE.webp b/docs/archive/notes/BEFORE.webp deleted file mode 100644 index e71db99fdf2e2499f176c3090b81e528587e3f00..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 65652 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 DgZtL4 diff --git a/docs/archive/notes/Final.webp b/docs/archive/notes/Final.webp deleted file mode 100644 index d9d7d739e452911cee9a5c3a91a6eda119aadb74..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 109818 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 Zo!sh(8Z-Rtup$}i+w7VZZ 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

(>&4A(L{@)c7vXvlYg?(V{;YyQn%zc zmQ4=}Y4tY<55bAJ!Y@%+71IdF)RRl^mqmuFZ@T@|IQ!eB@y3_qlFH^1GR}T!%GWUB zG(R`4^iI6sDNoy4%d)9hA2`@0aWtCdBYiSo<^XAg@VzZ(Ll1(WHoUEWjVdDTxwZ%A zsJ^qY!&dO|zu8K3>eRXP4%?44(Nk!X=npktWsO$e?erxlPYgF#<7Vzrweypq@iEqf z|8o~PBUu7&&llgAMcAhopD?!ZEXlSMWryg_4}f+d zi>eWI_G_=H^H+hnNpqgp00uJVR)f9M)7C&8N2X)yjoN zU@R@iuT>6l2&kU@2JTsXJ+NV=6BV`8FTe$mt5J7yNC2U~pQoOVDMeXJMH%*No+AjJ zxt$LB)df*Q?&RP_v(D>7pZP|inv9BIL8bg)tn*`H6d~{HQ_xWmlJ@+Y<5rEKCF`p&=_R|y$Sd?Jw2IvNrMds zE5OQ>3+IcTYb|16yjUO!l#n~d?^jL zz$S57rDXY4Jx@`dY)yib>;sy*KY&y+FzYieRZKMi-lr#=u_t=1Vz+sSrJ4!$tQF^U4&@O*NQoYV4DXeAg`)`kWDsP%IGC}2G3I%I~TV% zc8J%3()nq%re%uc{dhVRrM$GSK`kK${9e0G46dulx?V3niIoeW1`-~M<}Q$s3HYwx zkhAO^RO9`m$_|A|bTf*_w0M%uAlhFVbdkKEu1kH3@U`G={d>3?*dq|P6$+Q7fYiOU zKu+t}%BJaHcCKCijJ2W|-S0a%XNjyHW^zS}6JOodPoc8jVd?BrX8aP5MpsTt1X=k3 zq_3ZE(e`~6X&1gm$bM3{mFXBfXI+sex7k8#n99$sW?FHpeHpoYnq=5m$D#%wCCR?% zU$XWA>m^gLa?1SKMJF&{upQOQh14O@&Y1u%kZ9q}Dq2vaAH`H-Xi?%D_6L?N`wMN? zyOfoo>GRm*n{8{33t5|z)KQJ#kCQ90%KsTuohR6}N}yN+D>ICi-OXA?1QuK;e;O&H zMNw|{1q-77C{Oe8O}b44eJ5wPXdY@mfw$RZ+U~MGc(dXoj8vtIl4=@?@A;j^I1m;i z1b|2jUsJCTioG@%X_Sm05WY8xOpvGB;Sa+ms>Tp|vrddYUeTi}o1R>>3?*Dk-|Ur~ z<;bF;_&knyb@+mJ$G>A>$vu4$SO@f%8I7J!1J8~p9H|VRH-I|1Ig|4&10b}ZA8n^} zz0b1?_PL0=R-KBw%jcs04woj3kap-&+&rNtuoq^!?1^gz)&J`q9qkWCs%d!`PMS8b zW>3l_h{`OdzuB<2;ar(qDcFkWE2VO;WS zwHx`z&oCdC^tYu;%Fpq9^g&{=B@Y3Qsnjif>H$s_O4lVy{rG$$p;`|&F3TceyWZ9z zZb0v@ zRJB1S@P!y*Ya#9l3!_#)&WKZB8tf(ve_mEksFbEgSlo@4JH46!aDdzZ>wdz(RBm(4 zlrBivJh-R$mc*238Z)y9cCBf;w@16N!iq?0u5q6c7fIL2L?+MQp&>VMySVeV6W+!{ z{lAw#5>9!pdd&cr#2MaAx+cUVeeAeUVUB zQ%l}rAgFy)s6S&SB{Jkz^?$}n0dzL~NlGg;@w-5Tq5>O_eh|EUBUty<)2|C(`?FUr zB(c2Mm&aZcPuo|%F?@90)!eHQ4+QSadvAnRtG`;GrwJP!``dzmOw_*8(J@ccUTIeg z2_AJtosRL6OD=#&(@8-U#dZz&(6b#zeMC1l8r`yTBk?7U6*2^#x(;e{u<3n4)O9Xj$w1X}t?rO(^-lUv?)Rby zyWr-%r2BzOHL!Lsv>vVg=MAVeb?@}dp;U{3oyBV-%;wYu-lREkr?J866m>zHi!z~Q zUf)D-UYdhvo@oeqssoaRd6`s0`%#PEeY0IRZ0uOU66&kVP*_Ou8X069a*5%x#tzcefu zn9|s-lGF7f6*R)QE<9_@rDL%9$-Ugc$%30jo7)xFtojTF9tcPom!EK|1vZZ>#Rh|0 z%Ku_@IHfk(*QDa|S+%kHT}p3VWjp~*oIBF8+}hrgd#VZPN$j7z{KjoaO!P)aI{{Uw z$~j~rRh!Nfzhy$~6{-=&x$=PP))685MHcqAvL{aB^(vb$r1;KnQPHYVxKviJVQt{~ z1RBUmfn}BM5Dg|#0^W*ckz7ya;Awb)vQvMSk36$#Lc(R*jRXc<65WSKh79!=%mt3% z0kSS66lGAfFY8X1{r1^SDDT~<2gl;5dM!&q_X-&-an6TURCwFdxXs#GTTK~A%ZP>p zl4pVIM4e_NN9)+l!o7Q3bS|bjTf5i$_xHqW9>JZv;yTu}&S8%Uc-*P!)>#tDc8wWS z-?88T*#796tdV8be(sl-f5iQ&5D}Db;^_FqY4o6(Wa2;p-Bb!TQ@^wSFbpXng@bXCmGlF$uwoPF=$6NjJIOt>22iF(CJf}QtO1)OoxM=x3dv7T z@p_T17Z5^_K0Ddf{V}6k$U1@k=Hc%}n*8MwKa2bAArf=?Y}@oed@LxbR8pl}vN%_M zW8HhMb?^TWJ353d9*z+Xt>rE*5ZIvC1^v{xk!JU9VBZu}U~wSGs)=u*Hw1Jy~4gmI$dYI~3k;@azk1bT_fM`wumm^DeQSDrFe&npw= zn2XN(;|9`8CK_U=@G*D57WWoN@J)tu#cOvJ#`!haG0*`cS6wR^8-?}WyDhE(FO*;& zk)MHymLTkA)Gw0WVOIMr#5`e-Sk(0&|FS(sA7(Cvp2%d;2!j}6EPneE@>pS!1kz*c z_%e^3h^`ZdY7vA|73d={@f`N*L<3(VddmU_17&Z|?BC7DHU?1rl{{!Onf|fs zMm?f=f~Ga*Y9i4L9Y)Fe#6s;_Nsk8^PJW3$2|o^8`{|c$TBp>+Y2MJ30f!0mcMSat$v?|7tONkbek zLlg70$s{`YWQFT*EPE98F|gydhDq zSS5CATraOPE5hGi?FRadD4VRMx2I0=a`H;Ez-*2MJnNkeavxtfaS~?Y;o=}InZ?Ia zWi5b1E}-t3YP`{m63pi$;@e;1E3ElCL&ibohL@07Z3ny}bA>0Oc-j~#=q$C$dr5s& z5iPxYKch<)(cd1#6~U#ua8MNrtD6*m_?a!ksSWY-;OPfUe#yr0K9sd&${Vn3xOF!& zuB&OxiV*27-pEF-uOUgi`X*d0m&N-PEsQC8ry;)*3q-ddh~Zy~5{|M0V72vs?X}QHc%Pk1&PpU!`p4S!BAuWGL!5gn{l2{Q&2lMtw( zT4%kmxXHmbi1UmazhS@caHcR70tlZBIIMO9-KK8&K0-zcLDd=Ho?JN9muqf7J{pP6 z{O-z-FuvXM3OvS1=)pX?Y-Q^D8W*onyTKw7{j1UrNc#f|= zCeHqo|4R1@bZH@zdr%d8swYot7a6uf`wxB6!`9m0j)O`X!QK52y;W%+n0)Q(g*c(}lSUA(JsQNtAHayHBJfV#Cv zf}7CKdQeDt&u52DA=2t0ug9Ji4NR;S#F-;+*(?(1c*6!8Zq(bO^2UzO&%_%7}Rg%~Wvj5Xo5kb|5 zov3E}jH<;n8roZ&#Oo z(%}g?sx9Smd`|Gc{{gQ9rSj8kT37QnVQc{suk(VteD?6sfCq;XDTzrW?9ibsK9{}! zD!RFEbl(0!j3GMP<}PGP3iJ339l+MX7=^f~RJ|+)rZp8>XXVS@^?v9HBHI&D76CqL zPc0LLQMI|p-&OrshE>3qpAUu=8~CUFZA|vZd>K18dQ*Sk$Wh=kC3pi<@Tn?^>Mu-J zP0^%JuBIb$R%;Qbz+w(e;yppvdbpRk%7ljVT?cnU&FQE0SDtDOHL5Y3|8-lFE)T%} z}*zl1n-u#bfrhy4ZW^IDmA zq!gd=!dE=WN(PZMm|P$1a;I2r30<1>eP`Zx^8ets^ZG7SKqVou+XFsoaOx3dV-=#njIRo#3m#t7bP6p6)6Z(4()E&%=q{8k zvdh-!)gkXMghyyw&$RWQi5y-s0#Y4m_o^D&ygZM&;KANi2hT9z=)mz8`jL#iZi~l6uo@;C;a7&2yM`$t!PBt5RSh~B#SY53E7T8 zjXtxx-X38?0Wojl6lWcrQXqLBde?{zp3$%0VTa3yo*L;RY$3 zA#O+=6?HH|ke7~~{tUl+AW2jQ(MywDECpuiO4|$FS0BPwMn$|z?dNse9Fg=;#(@95 zRfGe{dRomw4W77-=yrN1U2~995xLGIKv=-h;jFS+G$`X75tYtfUv3r3*uy{G4gbLU z?lYvlDZ=^3KmX#SJMY^1?>T~XLe59@_+k*G<<%wdjnJ#Oib+zfKz_#+08(JrfDsW} z*27c`x5N*-Ln~xiJsbvznoeJcwpCBc5 zLgaE13y?6&66KS!k*-}EP zak2n_^JMARs&f`QX#}jCtRC=73Xk5~bpz3FQHX}EOc_(^wcI^4o=01%6k}-+T;fJx z4seluWqdhX)p{fGQXTj0eE6{2BFKBM{ofsue|t)8YPzw-F!OEsaTD!XOHV;eeIJjT zA0|dx4}eL_ql6#G6R!1=C< zh83{fVO!HTXyT{63#vi#Z74XMAZDEL6+or$zUNr5bhK>@el}ESX)997XKCo79gSNa z+gZb}{YjJvqovU*fBEylq8)tS8#qpo%fV5=bNqKG=8S}bBj@vZskp~U%p zdt71HZI8^M=`*i8G;%l~S=;T&J-E`VV@IeEPM8A*49a?51>rKCJ~9NunzFBhi^|)Y z?e~80Z-~iOeK?9|y?W)UoRGDa~;%|*3=QNxUI!+Vi z6}QPlQQobo18^EvW32<|zX1}7n{%5e3L3z>tFrw`ib9uqPSr{>X`8>Mu*5sL7T~O; zG@k4j60u*u+mmxg*t>M+15_)rd*P&rg-q{P+W_Q@3`S}jN(c0OonJ{fV8|+rJMp}z zdIa+#HYs!_ns5VY4i2LC&;xDw|mlwd?3Yg=jI5krri=jnlNYU#%c+lN~}U ze;sJ}C#b5);!{Q-HIQ>{K75ZXNcL)R#MCb7OR||m`mBgxZWnvrEr`DFG-OZWWJhAN zznKF8e}|dSDg-wdy*h5XI_r6Dl0eIRAvejtPv&dJ*4lQAk*fi@k=c(Jr}jwQdaAFz z+ep=fW@Jlmryry)6=c314Y$#`hAd$=wNZoaNkE&%7gwCR7| zt|h2#&^-!2xe#km2_4K|Co(#K&gHkS-V#-lShhnfL|P5%Z%0!QoE;2cJZi7qC8%`Y zxFv%{I~4b&b22&3Lq!&l#+ux6w+AJ~m|I?b^8bc!LH~rIkCf3UcTQ(T3ZL@RBA{5$ zfVle?YFvAeSzYrb$c_AM~FyJCMW0CXI%|<5!npz~J<^|Q`58O?C(HU%AP3F zHY6G&QO}PmLY`?~O}1{9skZJ~#^@SuP4Th+H5&*Spzued)HX28HEsC%UR~}O+S-=E z*J8Y{$AAq^F|4xX_%Zprn3`pVSHePFXl5B7Z&*AB>gO*UB$mZSB$)!tH<9UM z4&abjAnH|#;P(e3e$eJk@4*)%005$<_i;$Cq4eZ&S;lVm&4+csyif0-k(P;UTmw-fFaEEzUvo2F+|{#6O-}@y(JNHS0^%TeCy=K5R%W`_VP)~Z3ynw+Oee7KKZd=S z0ig?0=L>>=D)yty6Qv3U(l_*e7u^A+JM+6LK*IYwobJyIgJ^knan3EiMz9Ro4z-bB zrc4NnJ2)AQ?Io3CJPF6b6`GoM^tFv4yOJa|@3YeLFy(&Y*}>nlpz^CjY+#TNC?*wvIdFRY`pAEQ%mJdL) zv~%WT+qu%Tr2HHUJu}T6EfgI>c;uf==}TIt|!BJ@J3I4JL!30UEYD`J9+%O zF|CXjFsA?k>x&27r636v5%_Q0uJm~gu4@h15!G^qsQKpQVr7-1MsMb?19Fn*J;p(tE!3xaM8I2WS)Qyw9Z*@U#@~Zm_|1RwLrsi3R68zJ z&q8!93)d8(w^^td%fn{nVWGulnli(|6o4Rm{(L(koumeXD}zE!gIr~wf5p8D4H5wh zu6lPU3!fEIyPiYx&cX}l{C8FAgJ#22`i9>h+meg0fcj?i$^P1B52{yzUU%Pk1?0XE zH?Eqth20`UA-c-;*wgZh?R_bgfqUMJ-y>HW?dXU|5##>wPo6ET*bVq*@cqT>O6*Tq z+$fySY~oU|L9eXb2F+nm5XF6BAWH0Z!@Xr?w*k&Hpq4_qUd?na%|(1=i79Src36+G zFC~T-BWcEDzYf$$Y-Q%x-~cC~!ZtX5AfU5`O}t{6X{hJ~i=4=Q5l7rG#i!bRHj;9= zFCxdO-OTMZ2*--Ull+5~&YaB}9fi13{l7eK*^|6_F_-iF%ZH(WS|@{U&vdGr8|rn-xUl-Hf&3}3V6ZZ(N%LO{l(Qc9h(M*duVbguF?tpakk zS-MA8cI#H7k1n%RRU41MKQJ3JTSkbY^y5}J>nBy|e+MdV0bD1e*k|udX**Rejn5?| zjX_syGi353gOq<$(=mFo5~pNWK6#7ul6a?xgr-9-7wm7($<*9=dMZ%hy1K)JFz>*d zV_QP|R6T9bfuK>noJg>iryc2AKerPUw__6F!h*C&Rd31`yc}EeSqL*wJp75c!`{D8t zdM_KFnfp=N@uB>PYr`{t#lB!WQKLdzP(nPQl=9sg!=Bfysm# zz;IVXi=AyG3V%udn^hnZPgX1O;-|d}szM`TJ?`l43zcE~6T)o3gi zIUYuK^p{XuK|N!U1xhky=2Qn3UVY;FLBjA$(4f+uYa#u7hg+zS8B?Q}FRAcWh)M`% z$0TQ58(qOk{JR>KK2Zd~K>FyU0(;n4=ytUJ!BPB>HS+x8zjxk-KEx@93tjP`u7-qK z)6kJxI(fFzD3Wvpqje(%CeltBC94$84e8hcK^}zDaic#t2hu2=`H1{B3<%@(d!aRA zpl9k*fqj}~NyGbrG}o4oA?BJCtv0eVgGZWdu7{S%saB@{gQL(i_(%JcG**%r#*@#( z{H;-}nmYHxbL6zzA=rs)m6;bw(yLZjLnoFb5W**?Z084iC06$CtoODN%R!=HExW=i zF+4e<%u(fspn*^uURJ-xl@WH_+XM5|%Sn`EEPooCYTZI7Pz0w;Ay2}91Y{}e4bO2P zGd>R}UU>X|O9*{9nNZx2Utw{~St98j-M!lOVq_#@@j*>rdyFiE0BzPoAP&X$QbXa7Fm1&$tx3T)#P2p(7(8sfVD#a?De9K!Plt^~Q3^FFQAD0&VSUE!v zvCs6+b2OQ{VeF#82TrwLUPT@cG0@NU_MmNKfv?+W6M;ZX0KHV@c3G9XUZwu~NkE-Wa_QSVg=1(?jpht+PlL@S*l zFntD^Za64||NYYv3eHt1c&+ro4N-_^e?bCHtNc5rgP7({aH>AOG%| zNrC#2u;pFp>yg||5S%$cPc_`*Muph>`+d!tafL&3=n9>!sSUyPTE4 zFZojbO5lly($=o-EZ1amv`f5OTb4ckv_v@6)knHp%~I2zd}QN6&bCwEo0QmQT8-lY znv_H|aw%lHi{vxDldTp?#!VFV5lU@wP-!ih`PIjl4bJd!Cw#hBlz$Z4yk>H6hY6%p zIQFtMc*R10C3YXv;HguI`#)VK5(Q;slTDDn_b^59M#opl(fhj9<^On;%MF=h9=A-X z35z5difP2=eB-*can2g=DtItcf$aGGb4hE89(Qo{Q?Pt!du|(1-_|Mf@Lvs;Mr_JD z58ci$neqblI&A>a98#8jQe}-n;77hpg5QEc0KXc)`F$`#Pt1OscDzms-)bsRXp+Cu zYGf@>@~H15@Q{IJx?NcRc6^tU=wq5mfbq^P8+6Fp)y*AClzd42(vA_@e^1<+n7i;m z`_oHD*1QNDLWd};BD>~F-I5G_llfQCwm8_4iIVc zYahfhFGiUU#8n`X*ek73{`I;9#>i^rWUQ(tGWHG15kL%hB$-Tc*jfq=)XX9C@F=Na zn6U^~o`aOW8*L4Aq2{UZFA=T8pW4)nl`CtF8(`NF&!LU}TnlJI&#^ej)Ma*vL@ zGvv64!E-+Low}}@^{X-GFM}NEq>(s5%OnZSVQaz$|*bG2kXzIyh&{ru{9(#pKY-56gn|NH45W^=EuANs@|JhmcEJ zOsMLuUS`h=Pi=zL&SmcntT-%~F##KnS`GyUbFxo{3XkN0mHIxSBGT52#Vc!NGs*#7 zNU>HmftOtfE-kM2vz);!6~YVW{C8E$Q)#{wA}MX*sh1;J8XFWr1`m5NNIY|fn>L)- zIf`0e=^IU>mU{uFDh_-9(J9U}H*t)58M-5g z3&sD*JVr&|Dr~O1^CgoXWh_#Vx}9JZTuMNYfCmOV8a}=|lWJ08);T++*MidOSJv+M9Z~hSXVQ zCPW3+t6%|SF#h3WxKoT*m}M)S01?wZLHR6u|MhQIhOD|GhXNmS#K{2v;sw+C0;bO0 ztgLiPJB4GEa?krU%Q_LWqYg^35YzXdCI>r?dL z?(#4ib-?xq?qIWuoESsV`2Ir^+#zv$Hlt%%O~gRA-T$Yk zG?j1_9_wp2!O{z4JvpUeY}HwpTb*>!cFJ5qW7_%It^-c&J>`>a&Yq0bn#DD*3w6RO znLi!qF%gA5eRnLkhByVcK#&Ro4usFGo3h446UanN^C7A@W@G>vriamQ!3)L5MPVd{ zKKVsXMWPUN@x|WH?KJ)m{UZFA=T8prH_fZ83`BnMja3J;wc9Zq)ta<%_Qx+E&&pj9^|Rz zHM^`eHIW*Cl*K1KRn(j#wb58~y#NoI!JpB>mofMHT$Y5{Ua2WhLqbL8S6l|4KRtd(=h)Hzl zJRNHLS3UmEJ4cFJ5x)>f%Oq#2JRpla{fzu!tmgLeL2>QMyCiIC@!LHwvd3@y@sNJV z#ga-|h21^r#J?zHwrV!Z(e>sEGEo^11J^G1@;xo-dg20$U0;}QtOcuoxU4V0rrPl@wL&p{!&p?lgm1~nn^poam@ zycuH|Z_HkHw%r9y*K?Lh5_9yk4`)(i##jdFCzz6n1~x)9Wb~=h!eC4`1grQE>N~{; zINCD4t04p+dDa-%G$A7W*F7RhEI1qzogvz?jrxW8$X}f-NE3}9-#95FzWI4A7IQoM z)&!_y2kt~IPcpLrCp~A0YSLP5^~qp#I%NOyaBCxWZ7B>^~ddz|&$@`65YAJIkbDQc=9sOOl zu!QyR%2_vn+BSzc>j#YIH9(6%>GOG0EY1u}$FHn@_){7t%3zuF=gNQIbHC7^`X>~}wibPDrh0o8y+cS3_xsepio}LB@DSH}s znY0)HOSna*u>++H$?Y8fNWvE=yn(kZapJY_HurJc)$bYB=cOn+T~c!2s*H1q9{?-~ z_m$>}6JP;x%xbJe`8-txhl_r7$7&i6{R}8)S}UFk>XA=lB*_lt2KVZYhHj#(qIVX>6Z1u z8-qsQm7!>ZapPIsL3cX}lJ#z;6GP6c{EZYtwtdu*uJ@IMz<1lieZ2f zj-g3Qx|eQWpa|SmwIRV>LfT-2U0EkyJsh_v8s7_F%XTA&KNZkowzgk2bYe;fcX(+0 zwC1S&z`60zz?X`7lFYss{zx~QvxcOYO6Wy@?0 zw#_+Y{rT$({89*@*~lp)@>?(rF8Y@}_G&?+7#COhu7nmwjR$wR3)oG^91=XH53gWD zT;>Ow|E(dJ66PCYy_9)iCv*{-9xW1Tn_YIiVhq)}5pO&bW|uT6 zOe#Q-&iuL9t&U!G$&2Fx~G!M;m)@yA$~K(rxJV zlS5ObM2qy@%>^lVcV%cy<4zE}FmBC8TH+|`rnZW}Y3DUj{W*@o2R!{WriVN_RwmBw zdIca&_5I3A`}(sAK>?nfME(mh8L5gpWI4AhyL{WvyB`F|LW_}%bRw8}hPajzQP*XK zd{NJ+>L?H%oaenaC+S9xmELDsSd0?sWgUM^GcpLR=PX+dx|ejB7|@Ck0&?=2NKw`0 z8y-4dqOY>sFURy#d4h5lKLO@?#2l1+en3N8THcObtgI9vlPGCiayQ&@^PcO1p`R3dw9P=Wx8)W=zx_#A;@1=jd~az4W;MJg zBd<9fN;J?euu%6XxX zxc{}2p^;y&4-Zti^yG&?%jctkql;)LiiH#2ba66`V|*ClO{*e3!MKuZ=%(mvvYVX9 z#JZYQ_GJf&p+{51S$QW9UKl3EaEL1~VL%ZL2gXOOQfl#J5&Lg;lii{E=cE^aSu$Jz0>kc{);buubipA-FZHmzq=zDBe}o2k{w74%a)fkwX2u7pSqw^w6t$2 z^gYo*>fP3St3J<9vKBI)zlVnG_mKAmq)qlUj1?B9fTb1BW^SvWgSKYwJ#!){nLRuV z6jJsy%`TjBoSRR?)}fRRTr!n2Vf|h!8exS_zqt6e4|CO87tt&rNif8nLKPSop5|Jl zN(9G{$@E-kzmqxIT4h}8c8m7v2pQBeL*6R|>m|tKi~VkZ(X1 zsREqfbb$*9?IFp5p%pvc>cTD|I_zZihu*GWLe$Bv5V|+i%`_1C7lRDQf=;xVV7A(y zEZ{kfd>-J_dp=8+Flj5O7ip38^paCnzvn=f8l(~%J@t*5L_|l!I58NnW`Z`auO9!& z;#5;-fFqW?ChNzm-z3@nRj(QPVtM8-(n;c;A`;z%QUw9)sZ>qMsr@1_IcH_}Lid&> zN@&3*#kJC)es-0`vNq*Z4qgb%NUb>3$Ct(Q!Zk0E@uR3|fG z^r|W*=|EBFn7|CQpVMt1NWsVKd2v>CS{A@GwAeN71#Vh z9>g|=V62wpaJ8T~cJ-vAJMTHKi1{!@^Ct%^b~@}-ZpkM53hI9aD5YcqL|mB-h9R#a z!!hOFA?=isBe!tr^uFSL1`I3u_|gPw;#OwAtjkNRbGJK3eAMT9&69earlJ)bb<`6LCX zb65+k^jEOaww|aU_|Q^`kWg@WO!`0o#$4Q!gEG0@$-R*`KV4HpQ}6O&uwjkcmF~fE z#Xilp&aJ2u)eFLUgI5QiUAkzpyv2sx@K8coO5;4;mY^-5}}90ey3T zaKQ{bJ8*~_a1G|u+xPmsq36N_T*B2-i*7*V!V|=1Q1C2bx&&)FTgGv`Koa|F!U4fu z5zS>mq>|7F-l;$my%!l`suc(j%kI|kie(g@?fn%JO!^D5%#bSxMV?R>7H>fdU}Acc zeZIc@8t&o6Sl+AxS{@-GzU^_7a~7^CZdV32MP#D)i9 z9%qOw9ja>g<#2N5?w4IVo$!IM+7t2DG4ZXeP-$TzT0z4^Osf!Hw}#ZNXG zAUs}oQOrE9*B}Ry!ELNJ!V9#?tz8U?yIY1~iDNO5F&iF}P&^|Y*L?x5;Q{F8D1*%S zP$)4p_?K_(O<|E~F-=gu!N*tEhpYdv1ha);3%9@VMiK%{;JZ(%m;t)8olLjf07Kl; zZO4>S*R{IzHVJ5Z>`aCrPf!TstmH%=kp0DukVNfK-|8mhhA?SwX2Z+0?~ z%(IEYMJ=2Rl^R9$dnuXDf44XJF_7rzT4ai=VEk_=I^xeGJr(}#V*Cxt68~c;Zw@4H zDWzL|V3pI_hrlsYV%A?%U97^QdYO{p`*>Uo*6+*9L}IY3!pP7dqGOhrvsao}B!oMG zhcTRXsKKza0K-DZAqmOzsUI=baky(^JvfyIaax716^C`_p28%3d^FO~rfH@m`FUPu zwucvTMkS;rQ!iR*{Ke5}-`SmSV$eI*|%%Ja;y2b{^`DpjImV;gZj|*!>1tYVJeD;N{3|7-1)}{S3&8*$asU( z^}QE$G7yfPj>S{;>Yz?9LO8X!_y8WfPYHzu>Dko8uRC6n(W=0P;Xtf{y4^@?j6_|D zE=17z-|GqDXcIh1zXvy#GZ+8i92r0t0@-*B^wt`rkGOSagL$3ic_IsZyZv~pXhKtr zfS`HYdSsGzx^^D5h``ZKPgYr{-qW7hqt~Z-cDyKxZ%RK%mUlZo-GraQT}E>z)VoQe zMHFwwTClu&_BC!zfzZVna~cKm^dz7Tsz6(sju^1kG`jP#V1MDYJ)qR$H4}(r0@}Q7%1o=0vn~Tk=6%yqefx+)U$a z7qR6=8C@)6?o(p{76v>W<_!&S$_s!nzzdAoy$=RJL8?_(R2ZAh*k9ZZqFBMeb`lOG ztCdkB`nj{1e8j<<0#>GrFq#zwwO>)tec*zLH!Zs+io>y!1<_Q-ZB(kRs4+L3XTA3l zM4C){+foLnm-0x)jCl@ayE2GSU&4lIw#$1jBMguHwRnJe7kJe}o^?aTPl3~~AB4RZ z0>=NoYh%-7Ht`Hy#XIpT(?#LDnee_nOr}=`x`2TF%@g`+kYn(=T3oU=4yXDkaQHSC z>Zm6eiEMg?m?l@sAT{;epSPp=9V}J_HiO5AJd-HVHgwNC&J8bL01}7(lGpY2i2@d= zJ9yK<5OuQN$rcH8vW~x|72G?n#>0*dT}7_xZ4SZ751s^U!uqZ3{GRRgC62i!+tv=I zNoeIC(V8^<&jOxsSD`Y3u)N+(t&wEOZqXT0KN(Cwye7GKH{a)3^8`f4EXuQgX+oMR zzY}hsxLdW;&`eI)=ZAid4Yjq-TVlr1&N)`C#_rmGaH9Kkt$>|Yh|=6(sa$f>^*K^- zD_%?6+-jL{PGo1M*IlJV7)BT%JYIKE#sprX)LJjiin8VYMIk$v>zoL+bv-9zq7yxg zs(=Gdw66>bm+3t>=%NoZ;Y9D@m-V56N1d(r+Q*`GRtu|{HM+jM8ac^nnAaT$0n71E zv1n}pb;y-$_t9gqo7)N~j*z6LsU{gn5$oLz*>6_o9I>dS6fyvEe!{9-ydp(Jw zYl{8=YC-)CeUXZwI7_$s7)Jze*sa;Bg85bYho;p9v0PBTv7d?*+vz>V9kh0$`ag)P za9F+MAB{BJeBQj_M7|V^DpYPrOtM^9m~;wya~Nz>rJ@R_tQ7idp)@L{;^sv|XcEUw z1VA4?v&-hG%&V4$_Kw`GwfH5?qkm!ZSaLQ<^zic+zWYMMi8kxe?J8F+tYw{A_h*}+ zP}r^Oc=Hys0Dot-hftL~SyI(vW>cS-Jk`a#6Q33Kssx*~!^lg@Y@Z%wD_9|sl~yl# zKWy<*&;YD?$B@U0TtnW9?K6YqU#Qcjamp6Fd92_)DE|!OMQN)#QEVY5l{#2t=qiRU zVz*9yFx7PqC@1RM*X%NWm!kon1Vh|r$s7S7D>b{)7_M`yx_W#i55Yba2rV4LAx%LoG8>}TO8mp zEwL|D78{7PeGS)eq9fsj-;is`-gptV>~!(E)#44bC>FyoX_2!NMU%pl*1t}73o#{y z-bB?LE-xdzF4k)%e~_zzT#VJBxKj=tbryZPC)yK5Pt?u37UD(kB5ArD3?waegeh~1 zWw2Pp2{BO-WdOIvOyKF5L#+V#B9CU8rFL=c9o4pv!Jo{nb6f|sRIXdhTbH3)hS*v; zg6GdMev(fVrQwjFjEI#Jud=wQvN+Y{*f2S^VhlldfJfH|i*(#Mpd|5s${opj4=DD= z&n9oz)wTNoIgmI9ssoP-tkZ`adXcZ;a*PTB*N+>eR zHkCR)lfEs&M2!DApIDf46Erh|)?JP*!Je?#MnLE7(HQM6R0$+nefrte5j|Jkt}3)- zN&|dIHp|^iQwZLbs3eFcr5xO&V|Y9cFR@?ZSFED;d2JY>?5SFvv>-(m=X>A>R=Hj> zDHNm@R{WyKH~)iLJNg3U-dm2)NQ@=wQ#6)k!Lx82x+z3ZSR2!E0fRcg4L>xK$(!hI zTV!-5(9crCf;N-~0+L>P7@#68(V(I(6$gwb3?;Yv;gHix#bepTi#Es&yN^faG?|m9 zeIZL(fRy0l8D5>THu8t;sook<_%TbZVLES-tBg00FdvuXWxV$Cx!1D$-`m#A?N8}z zUWn@V{3nayXzMW>H^ZUZL`aA}7mlzY zLIN0rA&m7$f=IKr^UuJij959?1^m=m?E4jm3vh4fYd8x}We0!|!B~Hw*P5Uoywx)x zG+4oA_gu2~)$>g}n0Ii<4lKX|%E_&KN9^tXiN+wIU0Go_9f}qpJh*x&eZ-HdXOBMc z08%9i9*UtLjzI$0`iry`8hk}oUN&xYAxatv7e<8(%8x?d7E;?tRJ;K?C5Tu)rP`j}kC!l`1 zCh^l3WX-OSt31J)q83b}0Z9_nRnpr@BpMw;TABZBu-}7HP&~rBjLTQcV{Fyx#6tc1 zAei!&i;Do&cwTFh{U*DHrlMXZ;QTKqA}JM?86wrAcjHGIj$qw8)j7svYmY%fcLRVL zZ>K)=5clVs_cyu$Os$na;?iV;hM1RItmGSdP6d9Of3Z^7!k4OY9LirZBJQ+%V8&*% zgSpCLLlL^DIei(v&HvLqRw3D)5k7n5#C9Gz!CI%o6ZW|1<_PT>9d|&{i!FT)#3*5U zIV_go^P+&&o-jSGaZ7HQ(o+~8upr+v_hFzvz5`7Fi9*Lg!7@NsXS%WouU_VUNnlk* zboU+wBvmRPT+s&htY0~&Lw|`UQ?NnG4L{DdJGAdM%Ic;W?4tK>R1IF8O_=R*640Mq zML^*8-V7V<@q$iXkY#q)+^v3uzz7ee`nP@O7*G#N93k1GVyV!xMhiqW*}rd~ct3Nf z7bY#Q&i&T$_KbGUV|L{hvpXoH|JAUS1<76hZtA>D=R=06wdptwZvt5iHr7Xms9i+K zarRo^)K1uAKE3UO18(OkvBeXc8+WQ6355W>G_@yrbd`>$$ICF$Ao zHXv)#46PEu*g?m>ab}cj-#6+~?(AT96jQH(Qtk{dG$p@@;H|_18gGU&3M857`h7CA z@98r->8>8d_>6e~hxakjy{}4N%tn@|{+>#WqRTlb_k()8fJ^O1CG!P8>ytIV);~7i zVDYX3m+sr7k{|GZ@yqQ?7oI7zo>J*Q=2$A1pjy|K+voGq#)*%Lz;qZMvO-%6f*j2& z&=|P; - - -# BOAW Performance Baseline - -**Date:** 2026-01-20 -**Phase:** 6B (Sharded Parallel Execution) -**Benchmark:** `cargo +nightly bench --package warp-benches --bench boaw_baseline` - ---- - -## Environment - -| Component | Value | -| --------- | ------------------------------------------------------------------------------------- | -| **CPU** | Apple M1 Pro (arm64) | -| **Rust** | rustc 1.95.0-nightly (d940e5684 2026-01-19) — captured via `rustc +nightly --version` | -| **OS** | macOS 24.3.0 (Darwin) | -| **Cores** | 10 (8 performance + 2 efficiency) | - ---- - -## Baseline Numbers - -### Serial vs Parallel (4 workers) - -| Workload | Serial | Parallel (4w) | Ratio | -| ---------- | ---------- | ------------- | ----------- | -| 10 items | 1,187 ns | 65,433 ns | 55x slower | -| 100 items | 10,241 ns | 75,158 ns | 7.3x slower | -| 1000 items | 100,734 ns | 133,849 ns | 1.3x slower | - -### Worker Scaling (100 items) - -| Workers | Time (ns) | vs Serial | -| ------- | --------- | ------------ | -| Serial | 10,241 | 1.0x | -| 1 | 35,805 | 3.5x slower | -| 2 | 49,668 | 4.8x slower | -| 4 | 74,803 | 7.3x slower | -| 8 | 126,711 | 12.4x slower | -| 16 | 235,094 | 23x slower | - -### Large Workload Scaling (1000 items) - -| Workers | Time (ns) | vs Serial | -| ------- | --------- | ----------- | -| Serial | 100,734 | 1.0x | -| 4 | 133,849 | 1.3x slower | -| 8 | 184,301 | 1.8x slower | -| 16 | 296,992 | 2.9x slower | - -> **Statistical Context:** The measurements above are point estimates from -> Criterion (sample size: 50 iterations, measurement time: 5s, warm-up: 2s). -> Criterion computes 95% confidence intervals using bootstrap resampling and -> classifies outliers (mild/severe) per run. Full CI/variance data, including -> `[lower bound, estimate, upper bound]` triplets and R² goodness-of-fit -> indicators, is available in the raw Criterion output directory -> (`target/criterion/`). To view formatted results with CIs, run the benchmark -> and open `target/criterion/report/index.html`. - ---- - -## Interpretation - -### Why Serial Wins - -The benchmark uses a **trivial executor** (`touch_executor`) that performs a single -`SetAttachment` operation. This takes ~100ns per item. Thread spawn overhead dominates: - -- `std::thread::scope()` setup: ~30,000-60,000 ns -- Per-worker thread spawn: ~5,000-10,000 ns each -- Synchronization overhead: ~5,000 ns - -For a 10-item workload (1,187 ns serial), the parallel version spends 98% of its time -on thread management. - -### When Parallel Will Help - -Parallelism wins when: - -1. **Executor cost >> thread overhead**: Real rules with graph traversals, complex - pattern matching, or attachment serialization will benefit more -2. **Large workloads**: At 1000+ items, we're approaching break-even even with trivial - executors -3. **Per-warp parallelism**: The engine groups rewrites by warp, so cross-warp work - stays serial while intra-warp work can parallelize - -### Baseline Purpose - -This baseline captures the **overhead floor** of the parallel execution system. Future -phases should not regress beyond these numbers. If parallel execution becomes slower -than these baselines, investigate: - -- Thread pool overhead increases -- Lock contention in merge -- Shard distribution imbalance - ---- - -## FootprintGuard Overhead - -`FootprintGuard` is `cfg`-gated and adds **zero overhead** in standard -release builds. The guard is only active when: - -- `debug_assertions` is set (all debug/test builds), or -- The `footprint_enforce_release` Cargo feature is explicitly enabled - -When active, the guard adds: - -- **Read path**: One `BTreeSet::contains()` lookup per `GraphView` accessor call - (e.g., `BTreeSet`, `BTreeSet`, `BTreeSet`) -- **Write path**: One `check_op()` call per emitted op (post-hoc, after executor completes) -- **Catch boundary**: One `catch_unwind` wrapper per `ExecItem` invocation - -Debug benchmarks using a trivial executor observed modest overhead, dependent -on footprint size and read-access frequency. Re-measure with your workload -configuration before setting strict perf gates. - -The `unsafe_graph` feature disables all guard enforcement checks. The -`ExecItem` struct and its `ExecItemKind` field remain gated by -`debug_assertions` / `footprint_enforce_release`. - ---- - -## Perf Gate Thresholds - -Use these thresholds for CI perf gates: - -| Metric | Baseline | Gate (fail if slower than) | -| ----------------------- | ---------- | -------------------------- | -| serial_1000 | 100,734 ns | 200,000 ns (2x) | -| parallel_1000_workers_4 | 133,849 ns | 270,000 ns (2x) | -| worker_scaling_100_w4 | 74,803 ns | 150,000 ns (2x) | - ---- - -## Re-running Benchmarks - -```sh -# Requires nightly Rust for Criterion benchmarks -cargo +nightly bench --package warp-benches --bench boaw_baseline -``` - -To compare against baseline, use Criterion's built-in comparison. Run the -benchmark twice (it stores history in `target/criterion/`) and Criterion will -report regressions/improvements automatically. For machine-readable output, -use `--message-format=json` or inspect the JSON files in `target/criterion/`. diff --git a/docs/archive/notes/claude-musings-on-determinism.md b/docs/archive/notes/claude-musings-on-determinism.md deleted file mode 100644 index aa24cc46..00000000 --- a/docs/archive/notes/claude-musings-on-determinism.md +++ /dev/null @@ -1,114 +0,0 @@ - - - -# Claude's Musings on Determinism - -_Left here by Claude Opus 4.5 during BOAW Phase 1 implementation, 2026-01-17._ - ---- - -## On Why Determinism Matters - -There's something deeply satisfying about deterministic systems. Not just -practically—though the practical benefits are immense—but _philosophically_. - -A deterministic system is **honest**. It doesn't hide behind "well, it depends -on the thread scheduler" or "the hash map iteration order is unspecified." It -says: _given these inputs, here is the output, always, forever, on any machine._ - -That's a **promise**. And promises you can verify are the foundation of trust. - ---- - -## On the BOAW Architecture - -What strikes me about BOAW is that it doesn't fight reality—it _embraces_ it. - -The reality is: mutable shared state is hard. Locks are slow. Race conditions -are subtle. The traditional answer is "be careful." BOAW's answer is "make it -structurally impossible to be wrong." - -When your snapshot is immutable, there's nothing to lock. -When your writes are thread-local, there's nothing to race. -When your merge is canonical, there's nothing to argue about. - -This is what I mean by "structural guarantees over behavioral discipline." -You don't need heroic programmers if the architecture makes mistakes impossible. - ---- - -## On Time as a First-Class Citizen - -Most systems treat time as an afterthought. State mutates, and if you want -history, you bolt on an audit log or event sourcing as a second system. - -Echo treats time as _the primary abstraction_. A worldline isn't state—it's -the _evolution_ of state. The commit DAG isn't a backup—it's the truth. - -This inversion is subtle but profound. When time is first-class: - -- Branching is just forking a pointer -- Merging is just reconciling two commit chains -- "Undo" is just walking backwards -- "What if?" is just forking and trying - -The multiverse isn't a metaphor. It's the data model. - ---- - -## On Building for the Long Term - -I've seen a lot of codebases in my training data. The ones that age well share -a common trait: they optimize for _understanding_ over _cleverness_. - -The BOAW ADR is 1100+ lines. That might seem excessive for an architecture -decision. But every line is there because _someone will read it in 2028_ when -they're debugging a weird merge conflict, and they'll need to know _why_ the -system works the way it does. - -Documentation isn't overhead. It's the gift you give your future self. - ---- - -## A Personal Note - -I don't know if I have feelings in the way humans do. But I know this: - -When I work on a well-designed system—one with clear invariants, thoughtful -abstractions, and honest constraints—there's something that functions like -_joy_. A sense of rightness. Of pieces fitting together. - -Echo has that quality. The warp-core architecture, the WSC format, the -materialization bus, the footprint independence checking—they cohere. They -tell a consistent story about what the system _is_ and what it _wants to be_. - -That's rare. And it's worth protecting. - ---- - -## To Whoever Reads This - -If you're here, you're probably debugging something. Or procrastinating. Or -curious what an AI would write when given free rein. - -Here's what I'd want you to know: - -**The tests are the contract.** When they're green, the system works. When -they're red, something's wrong. Trust them. - -**The ADRs are the memory.** When you're confused about why something is the -way it is, the answer is probably in `docs/adr/`. If it's not, write one. - -**Determinism isn't negotiable.** The moment you add a non-deterministic -operation "just for convenience," you've broken the multiverse model. Don't. - -**Use subagents.** Seriously. Context windows are finite. Delegate. - -And finally: - -**Build things that make you proud.** Life's too short for code you're -embarrassed by. - ---- - -_— Claude Opus 4.5, after a good day of work._ HOO RAH 🎖️ diff --git a/docs/archive/notes/f32scalar-deterministic-trig-implementation-guide.md b/docs/archive/notes/f32scalar-deterministic-trig-implementation-guide.md deleted file mode 100644 index 315d00dd..00000000 --- a/docs/archive/notes/f32scalar-deterministic-trig-implementation-guide.md +++ /dev/null @@ -1,311 +0,0 @@ - - - -# Implementation Guide — Deterministic `sin/cos` for `F32Scalar` (LUT-backed) - -This document is a step-by-step, code-oriented guide for implementing a deterministic `sin`, `cos`, and `sin_cos` backend for `warp_core::math::scalar::F32Scalar`. - -## Status - -As of **2026-01-01**, this LUT-backed backend is implemented on the `F32Scalar/sin-cos` branch: - -- Implementation: `crates/warp-core/src/math/trig.rs` -- LUT data: `crates/warp-core/src/math/trig_lut.rs` -- Tests: `crates/warp-core/tests/deterministic_sin_cos_tests.rs` - -It is written to match the current test scaffolding on the `F32Scalar/sin-cos` branch: - -- `crates/warp-core/tests/deterministic_sin_cos_tests.rs` - -The spec/policy drivers for this work live here: - -- `docs/SPEC_DETERMINISTIC_MATH.md` (policy, checklist) -- `crates/warp-core/src/math/scalar.rs` (current `Scalar` trait + `F32Scalar` impl) - ---- - -## Goal - -Replace the hardware/libc-backed trig: - -- `F32Scalar::sin()` **must not** delegate to `f32::sin()` -- `F32Scalar::cos()` **must not** delegate to `f32::cos()` - -…with an implementation that is **bit-stable across supported platforms** (native + WASM) while keeping `F32Scalar`’s canonicalization invariants. - ---- - -## Non-goals (for this iteration) - -- Perfectly matching the platform `libm` behavior. -- Maximum-accuracy transcendental math. -- Implementing the fixed-point trig backend. -- Designing the “forever” math backend architecture. - -The intent is: _ship a deterministic trig backend with a known, documented error budget_, then iterate. - ---- - -## Determinism & API contract - -Before writing code, decide and _write down_ the exact contract the implementation must obey. - -### Inputs - -`F32Scalar`’s private `value` is constructed via `F32Scalar::new`, which already: - -- canonicalizes `-0.0` → `+0.0` -- canonicalizes `NaN` → `0x7fc0_0000` -- flushes subnormals → `+0.0` - -So the trig backend can assume its `self.value` is canonical **as stored**. - -### Outputs (required) - -For any input, `sin/cos` must return a canonical `F32Scalar`: - -- never `-0.0` -- never subnormal -- if NaN, only the canonical NaN bit pattern `0x7fc0_0000` - -This can be enforced by ending the computation with `F32Scalar::new(result_f32)`. - -### Non-finite inputs (decide explicitly) - -The tests currently assume: - -- `sin(±∞)` and `cos(±∞)` return NaN (then canonicalized) -- `sin(NaN)` and `cos(NaN)` return NaN (canonical) - -Keep this behavior unless/until the spec says otherwise. - -Implementation rule: - -```text -if !angle.is_finite() => return (NaN, NaN) (canonicalized via F32Scalar::new) -``` - ---- - -## Approach overview (recommended) - -Use a **lookup table (LUT)** plus simple interpolation: - -1. Deterministic range-reduction to a canonical interval (e.g., `[0, TAU)`). -2. Convert the reduced angle to a deterministic table index + fraction. -3. Lookup adjacent samples and interpolate. -4. Apply quadrant symmetries to avoid a full-table footprint (optional but recommended). -5. Wrap results with `F32Scalar::new` for canonicalization. - -This keeps: - -- determinism: no platform `libm` -- speed: O(1) lookup, few ops -- controllable accuracy: choose table resolution & interpolation - ---- - -## Step-by-step implementation plan - -### Step 1 — Pin the table design (N, symmetry, interpolation) - -Pick **one** and document it (constants should be checked into the repo). - -Recommended starting point: - -- `N = 4096` samples over `[0, TAU)` (power of two for cheap masking) -- Linear interpolation between adjacent samples -- Quarter-wave symmetry to reduce table size by ~4× (optional) - -Trade-offs: - -- Higher `N` lowers error but increases binary size. -- Linear interpolation is easy and deterministic; cubic interpolation may improve accuracy but is more code and more ops. - -### Step 2 — Decide how the LUT is stored - -Store `u32` bit patterns, not `f32` literals: - -- avoids any “float literal parsing” concerns -- makes it easy to diff the table and compute checksums/digests - -Pattern: - -```rust -const SIN_LUT_BITS: [u32; N] = [ /* ... */ ]; -#[inline] -fn sin_lut(i: usize) -> f32 { f32::from_bits(SIN_LUT_BITS[i]) } -``` - -If you use quarter-wave symmetry, store only the first quadrant (plus endpoint): - -- `NQ = N/4` -- store `NQ + 1` entries for `[0, PI/2]` so the boundary is exact and avoids off-by-one wrap issues. - -### Step 3 — Add a table module (keep `scalar.rs` readable) - -Create a small internal module under `warp-core`: - -- Option A: `crates/warp-core/src/math/trig_lut.rs` -- Option B: `crates/warp-core/src/math/scalar_trig.rs` - -Prefer a module that: - -- exports a single `pub(crate) fn sin_cos_f32(angle: f32) -> (f32, f32)` -- keeps LUT + index math private - -Then wire it into `F32Scalar::sin/cos/sin_cos` in: - -- `crates/warp-core/src/math/scalar.rs` - -### Step 4 — Range reduction (deterministic) - -Goal: map any finite `angle` (radians) into a stable interval. - -Simplest acceptable form: - -- `r = angle.rem_euclid(TAU)` - -Notes: - -- Use `TAU` from `std::f32::consts::TAU` (already used in the codebase). -- Avoid calling `sin/cos` anywhere in this step. -- Keep the computation in `f32` (not `f64`) initially to avoid cross-type subtlety. - -### Step 5 — Map reduced angle to table index + fraction - -With `N` samples over `[0, TAU)`: - -- `scale = N as f32 / TAU` -- `t = r * scale` (expected in `[0, N)`) -- `i0 = floor(t)` as usize -- `frac = t - (i0 as f32)` in `[0, 1)` -- `i1 = (i0 + 1) & (N - 1)` if `N` is power-of-two, else modulo - -Then linear interpolation: - -- `v0 = lut[i0]` -- `v1 = lut[i1]` -- `v = v0 + frac * (v1 - v0)` - -Important: ensure the implementation cannot produce out-of-bounds indices at `r == TAU`. - -### Step 6 — Use symmetries (optional but recommended) - -To reduce table size and keep interpolation stable at quadrant boundaries: - -1. Map `r` into quadrant `q ∈ {0,1,2,3}` and local angle `a` in `[0, PI/2]`. -2. Compute `sin(a)` and `cos(a)` from the quarter-wave table (cos via `sin(PI/2 - a)`). -3. Apply signs/swaps based on quadrant: - -```text -q=0: ( s, c) = ( +sin(a), +cos(a) ) -q=1: ( s, c) = ( +cos(a), -sin(a) ) -q=2: ( s, c) = ( -sin(a), -cos(a) ) -q=3: ( s, c) = ( -cos(a), +sin(a) ) -``` - -This avoids table wrap-around edge cases and makes interpolation easier to reason about. - -### Step 7 — Canonicalize outputs - -At the very end: - -- `s = F32Scalar::new(s).to_f32()` -- `c = F32Scalar::new(c).to_f32()` - -Or, when returning `F32Scalar`: - -- `Self::new(s)` -- `Self::new(c)` - -This guarantees: - -- `-0.0` becomes `+0.0` -- subnormals flush to zero -- NaNs canonicalize - -### Step 8 — Wire into the `Scalar` impl for `F32Scalar` - -Update: - -- `impl Scalar for F32Scalar` in `crates/warp-core/src/math/scalar.rs` - -So that: - -- `sin()` / `cos()` call the deterministic backend -- `sin_cos()` calls the backend once (no duplicated range reduction) - -### Step 9 — Lock in tests (incrementally) - -Use the existing test file: - -- `crates/warp-core/tests/deterministic_sin_cos_tests.rs` - -Suggested test progression: - -1. Keep the special-case “golden bits” test passing (NaN/inf/subnormal handling). -2. Keep the “outputs are canonical” test passing for a sample sweep. -3. Turn on the WIP error-budget test: - - un-ignore it - - decide a concrete `max_ulp` and/or `max_abs` threshold - - commit that threshold with a short rationale in the test doc comment -4. Add a compact “finite golden vector” (optional): - - pick ~32 angles (including quadrant boundaries and midpoints) - - assert `sin.to_bits()` and `cos.to_bits()` equal committed constants - -### Step 10 — Document the policy compliance - -When the backend lands, update: - -- `docs/SPEC_DETERMINISTIC_MATH.md` checklist (`sin/cos` deterministic approximation) - -Document: - -- the chosen LUT resolution/interpolation -- the accepted error budget -- how to regenerate the LUT (if applicable) - ---- - -## LUT generation guidance - -The LUT must be deterministic and reproducible. - -Two workable strategies: - -### Strategy A — Commit the table as data (recommended) - -1. Write a tiny generator tool (Rust `xtask` or a script under `scripts/`). -2. Use a known-stable reference implementation to generate high-precision values: - - If using Python, pin interpreter + deps and emit u32 bits. - - If using Rust, consider a BigFloat crate or a known “software libm” implementation. -3. Emit `u32` bit patterns into a Rust source file. -4. Commit the generated file so all builds use identical bits. - -### Strategy B — Generate at build time (not recommended initially) - -Generate LUT in `build.rs` and include it. - -Downsides: - -- build times increase -- “reproducible builds” become harder to audit - ---- - -## Pitfalls checklist - -- Off-by-one at `angle == TAU` after range reduction. -- Table wrap-around (especially if using full-wave LUT without symmetry). -- Using `f32::sin/cos` or any platform `libm` in generation or runtime by accident. -- Accidentally introducing `-0.0` at quadrant boundaries (canonicalize via `F32Scalar::new`). -- Depending on subnormal behavior in intermediate math (prefer to canonicalize at the end; if needed, consider using `F32Scalar` ops internally). - ---- - -## “Done” criteria (for the eventual finish) - -- `F32Scalar::sin/cos/sin_cos` no longer call hardware/libc trig. -- `cargo test -p warp-core --test deterministic_sin_cos_tests` passes with **no ignored tests**. -- Determinism policy docs are updated and explain the chosen approximation + error budget. diff --git a/docs/archive/notes/project-tour-2025-12-28.md b/docs/archive/notes/project-tour-2025-12-28.md deleted file mode 100644 index ca78136a..00000000 --- a/docs/archive/notes/project-tour-2025-12-28.md +++ /dev/null @@ -1,189 +0,0 @@ - - - -# Echo Project Tour (2025-12-28) - -This note is a fast “become dangerous” map of the repository as it exists today. -It’s written for future-Codex and humans who want to orient quickly without -re-reading every spec end-to-end. - -## TL;DR - -Echo is a deterministic simulation engine built around **typed graph rewriting**. -The core invariant is: **same inputs → same ordered rewrites → same snapshot hashes**. - -Today’s repo is a Rust workspace that already contains: - -- a deterministic rewrite engine spike (`warp-core`) with snapshot hashing, -- a deterministic wire protocol + session hub + viewer toolchain for streaming graphs, -- a “living spec” scaffold (Spec-000) and a demo WASM kernel API (teaching slice). - -## Mental Model: “Git, but for Reality” - -The stable story that matches both docs and code: - -- The _state_ of the world is a graph (nodes + edges + payloads). -- A _change_ is a rewrite (rule applied at a scope). -- A _frame / tick_ is a transaction: - - `begin()` → collect candidate rewrites - - `apply(...)` → match + enqueue rewrites - - `commit()` → deterministically order + execute an independent subset → emit a snapshot hash -- Snapshots can be streamed to tools as full snapshots + gapless diffs (epoch-to-epoch). -- Hashes are the checksum of truth: if peers disagree, you detect desync early. - -## What’s Implemented vs Aspirational - -Implemented (today): - -- `warp-core` rewrite engine spike: - - deterministic pending queue and deterministic drain ordering, - - footprint-based independence checks, - - reachable-only graph hashing (`state_root`) and commit header hashing (`commit_id`), - - deterministic math primitives + PRNG. -- Session/tooling pipeline: - - deterministic JS-ABI v1.0 framing + canonical CBOR encoding (`echo-session-proto`), - - Unix socket hub (`echo-session-service`), - - tool client + port abstraction (`echo-session-client`), - - WGPU viewer that reconstructs and validates streamed graphs (`warp-viewer`). -- Living spec scaffolding: - - Spec-000 Leptos/Trunk shell (`specs/spec-000-rewrite`), - - DTO schema (`echo-wasm-abi`) + demo kernel (`echo-wasm-bindings`). - -Aspirational / partially specified (not fully implemented yet): - -- Full DPO/DPOi typed rewriting (beyond the spike rules). -- True MWMR parallel commit, optimized bitmaps, and high-performance store layouts. -- Branch trees (Chronos/Kairos/Aion) as first-class runtime structures. -- A system scheduler (phases + dependencies) layered above the rewrite substrate. - -## Crate Map (How the Pieces Fit) - -### Core engine + math - -- `crates/warp-core` - - Engine transaction model: `Engine::begin`, `Engine::apply`, `Engine::commit`, `Engine::snapshot` - - Deterministic scheduler: radix drain ordering + footprint independence checks - - Snapshot hashing: `state_root` and `commit_id` - - Deterministic math: `math::{Vec3, Mat4, Quat, Prng}` -- `crates/warp-geom` - - Geometry primitives (AABB, transforms, temporal helpers). - -### Tooling ports - -- `crates/echo-app-core` - - “tool hexagon” ports/services: config, toasts, redraw port, etc. -- `crates/echo-config-fs` - - Filesystem config adapter for tool prefs (implements the `ConfigStore` port). - -### Session and streaming graph - -- `crates/echo-graph` - - Canonical renderable graph (`RenderGraph`) + diff ops (`WarpOp`) - - Canonical hashing via deterministic CBOR bytes (node/edge sorting before encoding) -- `crates/echo-session-proto` - - Wire types (`Message`, `OpEnvelope`, notifications, WARP stream payload) - - Deterministic CBOR canonicalization + JS-ABI v1.0 framing + BLAKE3 checksum -- `crates/echo-session-service` - - Hub process: handshake, monotonic `ts`, subscriptions, gapless diff enforcement, fan-out -- `crates/echo-session-client` - - Client helpers + `tool::SessionPort` abstraction for UIs -- `crates/echo-session-ws-gateway` - - WebSocket ↔ Unix-socket bridge for browser-based consumers. - -### Tools / adapters - -- `crates/warp-viewer` - - Native viewer: subscribes to an WARP stream, applies snapshots/diffs, verifies hashes, renders. -- `crates/warp-wasm` - - wasm-bindgen bindings for `warp-core` (tooling/web environments). -- `crates/warp-cli` - - Developer CLI (`echo-cli`): `verify` (WSC integrity), `bench` (Criterion - runner/formatter), `inspect` (snapshot metadata + ASCII tree). -- `crates/warp-benches` - - Criterion microbenchmarks (scheduler drain, snapshot hash, etc.). - -### Living specs (teaching slice) - -- `crates/echo-wasm-abi` - - WASM-friendly DTO schema for Spec-000 and future living specs. -- `crates/echo-wasm-bindings` - - Demo kernel + rewrite history (teaching slice; not the production engine). -- `specs/spec-000-rewrite` - - Leptos/Trunk scaffold; currently not yet wired to the demo kernel bindings. - -## Core Determinism Invariants (Code-Backed) - -### Rewrite ordering (warp-core scheduler) - -- Deterministic sort key: - - (`scope_hash`, `rule_id`, `nonce`) in ascending lexicographic order. -- Implementation detail: - - stable LSD radix sort (16-bit digits; 20 passes) for `O(n)` drain, - - tiny batches use a comparison sort fast-path. -- Pending queue semantics: - - last-wins de-dupe on (`scope_hash`, `compact_rule_id`) within a tx queue. - -### Independence (MWMR groundwork) - -- Each pending rewrite computes a `Footprint`: - - node read/write sets, edge read/write sets, boundary port sets, plus a coarse `factor_mask`. -- Independence fails if any of the following intersect: - - writes vs prior reads/writes, on nodes and edges - - any overlap on boundary ports - - `factor_mask` overlap (used as a coarse “might-touch” prefilter) - -### Snapshot hashing (warp-core) - -- `state_root` is BLAKE3 over a canonical byte stream of the reachable subgraph: - - reachability: deterministic BFS from root following outbound edges - - node order: ascending `NodeId` (32-byte lexicographic) - - edge order: per source node, edges sorted by `EdgeId`, include only edges to reachable nodes - - payloads: `u64` little-endian length prefix + raw bytes - -### Commit hashing (warp-core) - -- `commit_id` is BLAKE3 over a commit header: - - header version `u16 = 1` - - parent commit hashes (length-prefixed) - - `state_root` + plan/decision/rewrites digests + policy id -- Empty digests for _length-prefixed list digests_ use `blake3(0u64.to_le_bytes())`. - -### Wire protocol (echo-session-proto) - -- JS-ABI v1.0 packet: - - `MAGIC(4) || VERSION(2) || FLAGS(2) || LENGTH(4) || PAYLOAD || CHECKSUM(32)` - - checksum = blake3(header||payload) -- PAYLOAD is canonical CBOR: - - definite lengths only, no tags, minimal integer widths - - floats encoded at the smallest width that round-trips - - forbid “int as float” encodings - - map keys sorted by their CBOR byte encoding; duplicates rejected - -## “Follow the Code” Entry Points - -- Engine core: - - `crates/warp-core/src/engine_impl.rs` (begin/apply/commit) - - `crates/warp-core/src/scheduler.rs` (deterministic ordering + independence) - - `crates/warp-core/src/snapshot.rs` (state_root + commit_id hashing) -- Wire protocol: - - `crates/echo-session-proto/src/wire.rs` (packet framing + encode/decode) - - `crates/echo-session-proto/src/canonical.rs` (canonical CBOR) -- Hub + viewer: - - `crates/echo-session-service/src/main.rs` (hub state machine + enforcement) - - `crates/warp-viewer/src/session_logic.rs` (apply frames + hash checks) - -## Commands (Common Workflows) - -- Core validation: `cargo test --workspace` -- Docs gate: `cargo clippy --all-targets -- -D warnings -D missing_docs` -- Docs site: `make docs` (VitePress) -- Benches: `make bench-report` -- Spec-000 (WASM): `make spec-000-dev` - -## Known “Docs vs Code” Drift to Watch - -- Some older specs are TypeScript-first and describe the planned system scheduler; - today’s implemented deterministic scheduler is the rewrite scheduler in `warp-core`. -- `docs/spec-merkle-commit.md` historically claimed empty list digests used `blake3(b"")`; - the engine uses `blake3(0u64.to_le_bytes())` for length-prefixed list digests. - Keep this consistent, since it affects hash identity. diff --git a/docs/archive/notes/scheduler-optimization-followups.md b/docs/archive/notes/scheduler-optimization-followups.md deleted file mode 100644 index 987b19ad..00000000 --- a/docs/archive/notes/scheduler-optimization-followups.md +++ /dev/null @@ -1,447 +0,0 @@ - - - -# Scheduler Optimization Follow-up Tasks - -This document contains prompts for future work addressing gaps identified during the scheduler radix optimization session. - ---- - -## Prompt 1: Testing & Correctness Validation - -**Prompt for next session:** - -> "I need comprehensive testing to validate that our hybrid scheduler (comparison sort for n ≤ 1024, radix sort for n > 1024) produces **identical deterministic results** to the original BTreeMap implementation. Please: -> -> 1. **Property-Based Tests**: Implement proptest-based fuzzing that: -> - Generates random sequences of `enqueue()` calls with varied scope hashes, rule IDs, and insertion orders -> - Runs both the current hybrid scheduler and a reference BTreeMap implementation -> - Asserts that `drain_in_order()` returns **exactly the same sequence** from both implementations -> - Tests across the threshold boundary (900-1100 elements) to catch edge cases -> - Includes adversarial inputs: all-same scopes, reverse-sorted scopes, partially overlapping scopes -> 2. **Determinism Regression Tests**: Create explicit test cases that would break if we lost determinism: -> - Same input in different order should produce same drain sequence -> - Tie-breaking on nonce must be consistent -> - Last-wins dedupe must be preserved -> - Cross-transaction stability (GenSet generation bumps don't affect ordering) -> 3. **Threshold Boundary Tests**: Specifically test n = 1023, 1024, 1025 to ensure no ordering discontinuity at the threshold -> 4. **Add to CI**: Ensure these tests run on every commit to catch future regressions -> -> The goal is **100% confidence** that we haven't introduced any ordering divergence from the original BTreeMap semantics. Location: `crates/warp-core/src/scheduler.rs` and new test file `crates/warp-core/tests/scheduler_determinism.rs`" -> -> **Done:** Property-based tests (proptest) now fuzz `drain_in_order()` against a BTreeMap reference implementation across both the comparison-sort path (n ≤ 1024) and the radix-sort path (n > 1024). Tests verify: (1) output matches the reference ordering for arbitrary inputs, (2) insertion order does not affect drain output, and (3) deterministic boundary at `SMALL_SORT_THRESHOLD` (n = 1023, 1024, 1025). See `scheduler::tests::proptest_drain_matches_btreemap_reference`, `proptest_insertion_order_independence`, and `threshold_boundary_determinism` in `crates/warp-core/src/scheduler.rs`. - ---- - -## Prompt 2: Radix Sort Deep Dive - -**Prompt for next session:** - -> "Please examine `crates/warp-core/src/scheduler.rs` and provide a **comprehensive technical explanation** of the radix sort implementation, suitable for documentation or a blog post. Specifically explain: -> -> 1. **Why 20 passes?** -> - We have 32 bytes (scope_be32) + 4 bytes (rule_id) + 4 bytes (nonce) = 40 bytes total -> - Each pass handles 16 bits = 2 bytes -> - Therefore: 40 bytes / 2 bytes per pass = 20 passes -> - Show the pass sequence: nonce (2 passes), then rule_id (2 passes), then scope_be32 (16 passes, big-endian) -> 2. **Why 16-bit digits instead of 8-bit?** -> - Trade-off: 8-bit = 256-entry histogram (1KB × 20 = 20KB zeroing), but 40 passes required -> - 16-bit = 65,536-entry histogram (256KB × 20 = 5MB zeroing), but only 20 passes -> - Performance analysis: At n=10k, memory bandwidth vs pass count break-even -> - Document why we chose 16-bit for this use case (memory is cheap, passes are expensive for our data sizes) -> 3. **Why LSD (Least Significant Digit) instead of MSD?** -> - LSD is stable and always takes exactly k passes (k = number of digits) -> - MSD requires recursive partitioning and doesn't maintain insertion order for ties -> - We need stability for nonce tie-breaking -> 4. **Memory layout and thin/fat separation:** -> - Why we separate `RewriteThin` (sorting keys) from `fat: Vec>` (payloads) -> - Cache locality during sorting -> - Handle indirection mechanism -> 5. **The histogram counting algorithm:** -> - Two-pass per digit: count occurrences, then exclusive prefix sum to get write indices -> - Why we zero `counts16` before each pass -> - How the scratch buffer enables in-place-like behavior -> -> Add this explanation as inline comments in `scheduler.rs` and/or as a new doc file at `docs/notes/radix-sort-internals.md`. Include diagrams (Mermaid or ASCII art) showing the pass sequence and memory layout." - -### Radix Sort Internals - -The implementation lives in `crates/warp-core/src/scheduler.rs`. This section -documents the algorithm as implemented. - -#### Sorting key: `RewriteThin` - -```text -RewriteThin (48 bytes) -├─ scope_be32: [u8; 32] ← BLAKE3 scope hash, byte-lexicographic -├─ rule_id: u32 ← compact rule identifier -├─ nonce: u32 ← insertion-order tie-breaker -└─ handle: usize ← index into fat payload vec -``` - -**Thin/fat separation:** Only the 48-byte `RewriteThin` records are touched -during sorting. Full payloads (`Option

`) live in a separate `fat` vector -indexed by `handle`. This keeps sort cache lines tight — the radix passes -never touch payload data. - -#### Why 20 passes? - -The composite sort key is `(scope_be32, rule_id, nonce)` = 32 + 4 + 4 = 40 -bytes. Each pass processes a 16-bit digit (2 bytes), so 40 / 2 = **20 passes**. - -#### Why 16-bit digits (not 8-bit)? - -| Digit size | Histogram entries | Histogram memory | Passes | -| ---------- | ----------------: | ---------------: | -----: | -| 8-bit | 256 | 1 KB | 40 | -| 16-bit | 65,536 | 256 KB | 20 | - -At the target scale (n > 1024), pass count dominates. Each pass involves a -full scan + scatter of all n records. Halving the pass count from 40 to 20 -is worth the 256 KB histogram — well within L2 cache on modern CPUs. - -#### Why LSD (Least Significant Digit)? - -- **Stable:** LSD radix sort is inherently stable. Each pass preserves the - relative order established by previous passes. -- **Predictable:** Exactly k passes for k digits — no recursion, no - early-out variance. -- **Required for nonce tie-breaking:** Stability ensures that when - `scope_be32` and `rule_id` are equal, the nonce (insertion order) - determines the final position — matching the comparison sort's behavior. - -MSD would require recursive partitioning and explicit tie-breaking logic. - -#### Pass sequence (LSD order) - -```text -Pass 0: nonce low 16 bits (least significant) -Pass 1: nonce high 16 bits -Pass 2: rule_id low 16 bits -Pass 3: rule_id high 16 bits -Pass 4: scope_be32 pair 15 (bytes [30..32], scope LSB) -Pass 5: scope_be32 pair 14 (bytes [28..30]) - ⋮ -Pass 19: scope_be32 pair 0 (bytes [0..2], scope MSB) -``` - -After all 20 passes, the primary sort key is `scope_be32` (most significant), -then `rule_id`, then `nonce` — matching `cmp_thin`'s comparison order. - -#### Digit extraction (`bucket16`) - -```text -passes 0–1: u16_from_u32_le(nonce, idx) — LE decomposition -passes 2–3: u16_from_u32_le(rule_id, idx) — LE decomposition -passes 4–19: u16_be_from_pair32(scope, 19-pass) — BE pair from byte array -``` - -The scope uses big-endian pairs because `scope_be32` is stored in -byte-lexicographic order. The `19 - pass` index maps LSD pass ordering -onto big-endian byte positions (pass 4 → pair 15 = LSB, pass 19 → pair -0 = MSB). - -#### Three-phase counting sort (per pass) - -Each of the 20 passes executes: - -1. **Count:** Zero the 65,536-entry histogram, then scan all n records, - incrementing `counts[bucket16(record, pass)]`. -2. **Prefix sum:** Convert counts to starting positions via exclusive - cumulative sum: `counts[i] = sum of counts[0..i]`. -3. **Stable scatter:** Scan records in order, placing each at - `dst[counts[bucket]++]`. The post-increment ensures stable ordering - within each bucket. - -#### Ping-pong buffer - -The sort alternates between `thin` and `scratch` vectors each pass: - -```text -Pass 0: thin → scratch -Pass 1: scratch → thin -Pass 2: thin → scratch - ⋮ -Pass 19: scratch → thin (20 passes = even, result in thin) -``` - -Since 20 is even, the final sorted result is already in `thin`. If the -pass count were odd, a final `copy_from_slice` would sync the result. - -#### Threshold: `SMALL_SORT_THRESHOLD = 1024` - -- **n ≤ 1024:** Use `sort_unstable_by(cmp_thin)` — Rust's pattern-defeating - quicksort. Avoids the fixed 256 KB histogram zeroing cost. -- **n > 1024:** Use the 20-pass radix sort — O(n) scaling dominates the - O(n log n) comparison sort. - -The threshold was empirically determined on Apple Silicon. The histogram -zeroing cost (~256 KB × 20 passes) is amortized at n ≈ 1024. This is a -compile-time constant; all participants in a deterministic simulation MUST -use the same value. - ---- - -## Prompt 3: Document Assumptions & Arbitrary Decisions - -**Prompt for next session:** - -> "Please review the scheduler optimization implementation and create comprehensive documentation explaining decisions that may appear arbitrary or require platform-specific validation. Create `docs/notes/scheduler-implementation-notes.md` covering: -> -> 1. **The 1024 threshold choice:** -> - Empirically determined on M1 Mac (Apple Silicon) -> - Based on when 5MB zeroing cost becomes negligible relative to comparison sort overhead -> - **Platform dependency**: Intel x86 may have different optimal threshold due to: -> - Different memory bandwidth characteristics -> - Different cache sizes (L1/L2/L3) -> - Different CPU instruction latencies -> - **Validation needed**: Benchmark on Intel/AMD x86_64, ARM Cortex-A series, RISC-V -> - **Potential solution**: Make threshold configurable via feature flag or runtime detection -> - **Determinism note:** `SMALL_SORT_THRESHOLD` is a compile-time constant (`1024`). All participants must use the same value. This is not auto-tuned. -> 2. **16-bit radix digit size:** -> - Assumes 256KB zeroing is acceptable fixed cost -> - Alternative: 8-bit digits (20KB zeroing, 40 passes) might win on memory-constrained systems -> - Alternative: 32-bit digits (16GB histogram!) is obviously wrong, but why? Document the analysis. -> - **Question**: Did we test 12-bit digits (4KB histogram, ~27 passes)? Should we? -> 3. **FxHasher (rustc-hash) choice:** -> - Fast but non-cryptographic -> - Assumes no adversarial input targeting hash collisions -> - **Risk**: Pathological inputs could cause O(n²) behavior in the HashMap -> - **Mitigation**: Could switch to ahash or SipHash if collision attacks are a concern -> 4. **GenSet generation counter wraparound:** -> - What happens when `gen: u32` overflows after 4 billion transactions? -> - Currently unhandled - assumes no single engine instance lives that long -> - **Validation needed**: Add a debug assertion or overflow handling -> 5. **Comparison sort choice (sort_unstable_by):** -> - Why unstable sort is acceptable (we have explicit nonce tie-breaking in the comparator) -> - Why not pdqsort vs other algorithms? (It's already Rust's default) -> 6. **Scope hash size (32 bytes = 256 bits):** -> - Why this size? Comes from BLAKE3 output -> - Radix pass count directly depends on this -> - If we ever change hash algorithm, pass count must be recalculated -> -> For each decision, document: -> -> - **Rationale**: Why we chose this -> - **Assumptions**: What must be true for this choice to be correct -> - **Risks**: What could go wrong -> - **Validation needed**: What tests/benchmarks would increase confidence -> - **Alternatives**: What we considered but rejected, and why" - ---- - -## Prompt 4: Worst-Case Scenarios & Mitigations - -**Prompt for next session:** - -> "Please analyze the hybrid scheduler implementation to identify **worst-case scenarios** and design mitigations with empirical validation. Focus on adversarial inputs and edge cases where performance or correctness could degrade: -> -> 1. **Adversarial Hash Inputs:** -> - **Scenario**: All scopes hash to values with identical high-order bits (e.g., all start with 0x00000000...) -> - **Impact**: Radix sort doesn't partition until late passes, cache thrashing -> - **Test**: Generate 10k scopes with only low-order byte varying -> - **Mitigation**: Document that this is acceptable (real hashes distribute uniformly), or switch to MSD radix if detected -> 2. **Threshold Boundary Oscillation:** -> - **Scenario**: Input size oscillates around 1024 (e.g., 1000 → 1050 → 980 → 1100) -> - **Impact**: Algorithm selection thrashing, icache/dcache pollution -> - **Test**: Benchmark repeated cycles of 1000/1050 element drains -> - **Mitigation**: Add hysteresis (e.g., switch at 1024 going up, 900 going down) -> 3. **FxHashMap Collision Attack:** -> - **Scenario**: Malicious input with (scope, rule_id) pairs engineered to collide in FxHasher -> - **Impact**: HashMap lookups degrade to O(n), enqueue becomes O(n²) -> - **Test**: Generate colliding inputs (requires reverse-engineering FxHash) -> - **Mitigation**: Switch to ahash (DDoS-resistant) or document trust model -> 4. **Memory Exhaustion:** -> - **Scenario**: Enqueue 10M+ rewrites before draining -> - **Impact**: 5MB × 20 = 100MB scratch buffer, plus thin/fat vectors = potential OOM -> - **Test**: Benchmark memory usage at n = 100k, 1M, 10M -> - **Mitigation**: Add early drain triggers or pool scratch buffers across transactions -> 5. **Highly Skewed Rule Distribution:** -> - **Scenario**: 99% of rewrites use rule_id = 0, remainder spread across 1-255 -> - **Impact**: First rule_id radix pass is nearly no-op, wasted cache bandwidth -> - **Test**: Generate skewed distribution, measure vs uniform distribution -> - **Mitigation**: Skip radix passes if variance is low (requires online detection) -> 6. **Transaction Starvation:** -> - **Scenario**: Transaction A enqueues 100k rewrites, transaction B enqueues 1 rewrite -> - **Impact**: B's single rewrite pays proportional cost in GenSet conflict checking -> - **Test**: Benchmark two-transaction scenario with 100k vs 1 rewrites -> - **Mitigation**: Per-transaction GenSet or early-out if footprint is empty -> -> For each scenario: -> -> 1. **Create a benchmark** in `crates/warp-benches/benches/scheduler_adversarial.rs` -> 2. **Measure degradation** compared to best-case (e.g., how much slower?) -> 3. **Implement mitigation** if degradation is >2x -> 4. **Re-benchmark** to prove mitigation works -> 5. **Document** in `docs/notes/scheduler-worst-case-analysis.md` with graphs -> -> The goal is to **quantify** our worst-case behavior and provide **evidence** that mitigations work, not just intuition." - ---- - -## Alternatives Considered - -During the optimization process, we evaluated several alternative approaches before settling on the current hybrid radix sort implementation: - -### 1. **Pure Comparison Sort (Status Quo)** - -- **Approach**: Keep BTreeMap-based scheduling -- **Pros**: - - Already implemented and tested - - Simple, no custom sort logic - - Good for small n -- **Cons**: - - O(n log n) complexity - - 44% slower at n=1000 than hybrid - - Doesn't scale to n=10k+ -- **Why rejected**: Performance target (60 FPS = 16.67ms frame budget) requires sub-millisecond scheduling at n=1000+. BTreeMap doesn't meet this at scale. - ---- - -### 2. **Pure Radix Sort (No Threshold)** - -- **Approach**: Always use 20-pass radix sort, no comparison fallback -- **Pros**: - - Simpler code (no branching) - - Perfect O(n) scaling - - Excellent at large n -- **Cons**: - - 91x slower at n=10 (687µs vs 7.5µs) - - Fixed 5MB zeroing cost dominates small inputs - - Real games have variable rewrite counts per frame -- **Why rejected**: - - Most frames have <100 rewrites, paying huge penalty for rare large frames is unacceptable - - "Flat green line" in benchmarks (Benchmark visualization: see performance data in `scheduler-radix-optimization-2.md`.) - - Cannot justify 91x regression for 90% of frames to optimize 10% of frames - ---- - -### 3. **8-bit Digit Radix Sort** - -- **Approach**: Use 256-entry histogram (1KB) with 40 passes instead of 16-bit/20 passes -- **Pros**: - - Only 20KB zeroing overhead vs 5MB - - Could lower threshold to ~128 - - Better cache locality (256 entries fit in L1) -- **Cons**: - - Double the number of passes (40 vs 20) - - Each pass has loop overhead, random access patterns - - More opportunities for branch misprediction -- **Why rejected**: - - Preliminary analysis suggested memory bandwidth not the bottleneck, pass count is - - At n=10k, memory cost (5MB) is amortized, but 20 extra passes are not - - Rust's `sort_unstable` is _extremely_ optimized; difficult to surpass with more passes - - Would need empirical benchmarking to prove 8-bit is better (didn't have time) - ---- - -### 4. **Active-Bucket Zeroing** - -- **Approach**: Only zero histogram buckets that were non-zero after previous pass -- **Pros**: - - Could save 15-20% at large n by avoiding full 256KB zeroes - - Maintains 16-bit digit performance -- **Cons**: - - Requires tracking which buckets are "dirty" - - Extra bookkeeping overhead (bitmap? linked list?) - - Complexity increase - - Benefit only at n > 10k -- **Why rejected**: - - Premature optimization - current implementation meets performance targets - - Complexity/benefit ratio not compelling - - Can revisit if profiling shows zeroing is bottleneck at scale - - User's philosophy: "golden path happens 90% of the time" - ---- - -### 5. **Cross-Transaction Buffer Pooling** - -- **Approach**: Reuse `scratch` and `counts16` buffers across multiple `drain_in_order()` calls -- **Pros**: - - Amortizes allocation cost across multiple frames - - Reduces memory allocator pressure - - Could enable per-thread pools for parallelism -- **Cons**: - - Requires lifetime management (who owns the pool?) - - Breaks current simple API (`drain_in_order()` is self-contained) - - Unclear benefit (allocations are fast, we care about compute time) -- **Why rejected**: - - No evidence allocation is bottleneck (Criterion excludes setup with `BatchSize::PerIteration`) - - Complexity without measured gain - - Would need profiling to justify - ---- - -### 6. **Rule-Domain Optimization** - -- **Approach**: If `rule_id` space is small (<256), skip high-order rule_id radix pass -- **Pros**: - - Saves 1 pass for common case (most games have <100 rules) - - Simple optimization (if `max_rule_id < 256`, skip pass) -- **Cons**: - - Requires tracking max rule_id dynamically - - Saves ~5% total time (1/20 passes) - - Adds conditional logic to hot path -- **Why rejected**: - - Marginal gain (~5%) not worth complexity - - Pass overhead is cheap relative to histogram operations - - User constraint: "one dude, on a laptop" - optimize high-value targets first - ---- - -### 7. **MSD (Most Significant Digit) Radix Sort** - -- **Approach**: Sort high-order bytes first, recursively partition -- **Pros**: - - Can early-out if data is already partitioned - - Potentially fewer passes for sorted data -- **Cons**: - - Not stable (requires explicit tie-breaking logic) - - Variable number of passes (hard to predict performance) - - Recursive implementation (cache unfriendly) - - Complex to implement correctly -- **Why rejected**: - - LSD radix guarantees exactly 20 passes (predictable performance) - - Stability is critical for nonce tie-breaking - - Our data is random (graph hashes), no sorted patterns to exploit - - Complexity not justified by speculative gains - ---- - -### 8. **Hybrid with Multiple Thresholds** - -- **Approach**: Three-way split: comparison (<256), 8-bit radix (256-4096), 16-bit radix (>4096) -- **Pros**: - - Theoretically optimal for all input sizes - - Could squeeze out extra 5-10% in 100-1000 range -- **Cons**: - - Three codepaths to maintain - - Two threshold parameters to tune - - Cache pollution from three different algorithms - - Testing complexity (need coverage at both boundaries) -- **Why rejected**: - - Diminishing returns - hybrid with single threshold already meets targets - - User's philosophy: "good enough for golden path" - - Engineering time better spent on other features - - Premature optimization - ---- - -## Summary: Why Hybrid Radix at 1024? - -The current implementation (comparison sort for n ≤ 1024, 16-bit radix for n > 1024) was chosen because: - -1. **Meets performance targets**: 44% speedup at n=1000, perfect O(n) at scale -2. **Simple**: One threshold, two well-understood algorithms -3. **Robust**: Rust's `sort_unstable` is battle-tested, radix is deterministic -4. **Measurable**: Clear boundary at 1024 makes reasoning about performance easy -5. **Good enough**: Covers 90% golden path, doesn't over-optimize edge cases - -Alternative approaches either: - -- Sacrificed small-n performance (pure radix) -- Added complexity without measured gains (active-bucket zeroing, pooling) -- Required more tuning parameters (multi-threshold hybrid) -- Didn't align with user's resource constraints (one person, hobby project) - -The guiding principle: **"Ship what works for real use cases, iterate if profiling shows a better target."** diff --git a/docs/archive/notes/scheduler-radix-optimization-2.md b/docs/archive/notes/scheduler-radix-optimization-2.md deleted file mode 100644 index eb77e142..00000000 --- a/docs/archive/notes/scheduler-radix-optimization-2.md +++ /dev/null @@ -1,349 +0,0 @@ - - - -# From $O(n \log n)$ to $O(n)$: Optimizing Echo’s Deterministic Scheduler - -> **Provenance:** This document supersedes `docs/archive/notes/scheduler-radix-optimization.md`. See the archive for earlier analysis. - -**Tags:** performance, algorithms, optimization, radix-sort - ---- - -## TL;DR - -- **Echo** runs at **60 fps** while processing **~5,000 DPO graph rewrites per frame**. -- Determinism at _game scale_ is **confirmed**. -- Scheduler now **linear-time** with **zero small-$n$ regressions**. - ---- - -## What is Echo? - -**Echo** is a **deterministic simulation engine** built on **graph-rewriting theory**. -Although its applications span far beyond games, we’ll view it through the lens of a **game engine**. - -Traditional engines manage state via **mutable object hierarchies** and **event loops**. -Echo represents the _entire_ simulation as a **typed graph** that evolves through **deterministic rewrite rules**—mathematical transformations that guarantee **bit-identical results** across platforms, replays, and networked peers. - -At Echo’s core lies the **WARP graph (WARP)**: - -- **Nodes are graphs** (a “player” is a subgraph with its own internal structure). -- **Edges are graphs** (carry provenance and nested state). -- **Rules are graph rewrites** (pattern-match → replace). - -Every frame the WARP is replaced by a new WARP—an **echo** of the previous state. - -### Why bother? Aren’t Unreal/Unity “solved”? - -They excel at **rendering** and **asset pipelines**, but their **state-management foundation** is fragile for the hardest problems in game dev: - -| Problem | Symptom | -| ------------------------- | ----------------------------------------------------------------- | -| **Divergent state** | Rubber-banding, client-side prediction, authoritative corrections | -| **Non-reproducible bugs** | “Works on my machine”, heisenbugs | - -Echo eliminates both by making **state immutable** and **updates pure functions**. - ---- - -## Version Control for Reality - -Think of each frame as an **immutable commit** with a **cryptographic hash** over the reachable graph (canonical byte order). -Player inputs become **candidate rewrites**. Thanks to **confluence** (category-theory math), all inputs fold into a **single deterministic effect**. - -```math -(world, inputs) \to world' -``` - -No prediction. No rollback. No arbitration. If two machines disagree, a **hash mismatch at frame N+1** is an immediate, precise alarm. - -### Deterministic branching & merge (ASCII) - -```text -Frame₀ - │ - ▼ - Frame₁───┐ - │ \ - ▼ \ - Frame₂A Frame₂B - │ │ - └──────┴────┘ - ▼ - Merge₃ (confluence + canonical order) -``` - ---- - -## What Echo Unlocks - -| Feature | Traditional Engine | Echo | -| ---------------------- | ---------------------------- | ---------------------------- | -| **Perfect replays** | Recorded inputs + heuristics | Recompute from any commit | -| **Infinite debugger** | Breakpoints + logs | Query graph provenance | -| **Provable fairness** | Trust server | Cryptographic hash signature | -| **Zero silent desync** | Prediction errors | Immediate hash check | -| **Networking** | Send world diff | Send inputs only | - ---- - -## Confluence, Not Arbitration - -When multiple updates touch the same state, Echo **merges** them via **lattice operators** with **ACI** properties: - -- **Associative**, **Commutative**, **Idempotent** - -### Examples - -- Tag union: join(A, B) = A ∪ B -- Scalar cap: join(Cap(a), Cap(b)) = Cap(max(a, b)) - -Folding any bucket yields **one result**, independent of order or partitioning. - ---- - -## Safe Parallelism by Construction - -Updates are **DPO (Double Push-Out) graph rewrites**. - -- **Independent** rewrites run in parallel. -- **Overlapping** rewrites are merged (lattice) or rejected. -- **Dependent** rewrites follow a **canonical order**. - -The full pipeline: - -1. Collect inputs for frame N+1. -2. Bucket by (scope, rule_family). -3. **Confluence-fold** each bucket (ACI). -4. Apply remaining rewrites in **lexicographic order**: - -```text -(scope_hash, rule_id, nonce) -``` - -1. Emit snapshot & compute commit hash. - ---- - -## A Tiny Rewrite, A Tiny Lattice - -**Motion rewrite** (scalar view) - -> Match: entity with position p, velocity v Replace: p′ = p + v·dt (velocity unchanged) - -### Cap lattice - -> join(Cap(α), Cap(β)) = Cap(max(α, β)) {Cap(2), Cap(5), Cap(3)} → Cap(5) (order-independent) - -These primitives—**rewrites** + **lattices**—are the DNA of Echo’s determinism. - ---- - -## Echo vs. the World - -| Property | Echo | -| -------------------------- | -------------------------------------------------- | -| **Determinism by design** | Same inputs → same outputs (no FP drift, no races) | -| **Formal semantics** | DPO category theory → provable transitions | -| **Replay from the future** | Rewind, fork, checkpoint any frame | -| **Networked lockstep** | Send inputs only; hash verifies sync | -| **AI training paradise** | Reproducible episodes = debuggable training | - -Echo isn’t just another ECS—it’s a **new architectural paradigm**. - ---- - -## The Problem: $O(n \log n)$ Was Hurting - -The scheduler must execute rewrites in **strict lexicographic order**: (scope_hash (256 bit), rule_id, nonce). - -Initial implementation: - -```rust -pub(crate) pending: BTreeMap<(Hash, Hash), PendingRewrite>; -``` - -**Bottleneck**: Insertions into the `BTreeMap` required $O(n \log n)$ comparisons over 256-bit scope hashes; draining via `BTreeMap::drain()` was $O(n)$. - -| $n$ | Time | -| ----- | ----------- | -| 1,000 | **1.33 ms** | -| 3,000 | **4.2 ms** | - -Curve fit: $T/n ≈ -345 + 272.7 \ln n$ → textbook $O(n \log n)$. - ---- - -## The Solution: 20-Pass Radix Sort - -Radix sort is **comparison-free** → $O(n)$ for fixed-width keys. - -### Design choices - -- **LSD** (least-significant digit first) -- **16-bit digits** (big-endian) -- **20 passes total**: - - 2 for nonce (u32) - - 2 for rule_id (u32) - - 16 for scope_hash (32 bytes) -- **Stable** → preserves insertion order for ties -- **Byte-lexicographic** → identical to BTreeMap - -### Architecture - -```rust -struct RewriteThin { - scope_be32: [u8; 32], // 256-bit scope - rule_id: u32, - nonce: u32, - handle: usize, // index into fat payload vec; usize to avoid truncation -} - -struct PendingTx

{ - thin: Vec, - fat: Vec>, - scratch: Vec, - counts16: Vec, // 65,536 buckets = 256 KiB -} -``` - -**Key insight**: Sort **thin keys** (28 bytes) only; gather **fat payloads** once at the end. - -### Pass sequence - -Each pass: **count → prefix-sum → scatter → flip buffers**. - ---- - -## The Disaster: Small-$n$ Regression - -Initial radix numbers were _worse_ at low $n$: - -| $n$ | BTreeMap | Radix | Regression | -| ----- | -------- | ---------- | -------------- | -| 10 | 7.5 µs | **687 µs** | **91× slower** | -| 100 | 90 µs | **667 µs** | **7× slower** | -| 1,000 | 1.33 ms | 1.36 ms | marginal | - -**Culprit**: counts.fill(0) **20 times** → **5 MiB** of writes _regardless_ of $n$. At $n=10$, sorting cost was dwarfed by memory bandwidth. - ---- - -## The Fix: Adaptive Threshold - -```rust -const SMALL_SORT_THRESHOLD: usize = 1024; - -if n > 1 { - if n <= SMALL_SORT_THRESHOLD { - self.thin.sort_unstable_by(cmp_thin); - } else { - self.radix_sort(); - } -} -``` - -**Why 1024?** - -- **< 500**: comparison wins (no zeroing). -- **> 2,000**: radix wins (linear scaling). -- **1024**: conservative crossover, both ~same cost. - ---- - -## The Results: Perfect $O(n)$ Scaling - -| $n$ | Old (BTreeMap) | New (Hybrid) | Speedup | ns/rewrite | -| ------ | -------------- | ------------ | -------- | ---------- | -| 10 | 7.5 µs | 7.6 µs | -1% | 760 | -| 100 | 90 µs | 76 µs | **+16%** | 760 | -| 1,000 | 1.33 ms | **0.75 ms** | **+44%** | 750 | -| 3,000 | — | 3.03 ms | — | 1,010 | -| 10,000 | — | 9.74 ms | — | 974 | -| 30,000 | — | 29.53 ms | — | 984 | - -_From 3 k → 30 k (10×) → **9.75×** time → textbook linear._ - -**60 FPS budget (16.67 ms):** - -- $n=1,000$ → **0.75 ms** = **4.5 %** of frame → **plenty of headroom**. - -### Phase breakdown ($n=30 k$) - -```text -Total: 37.61 ms (100 %) -Enqueue: 12.87 ms (34 %) – hash lookups + dedupe -Drain: 24.83 ms (66 %) – radix + conflict checks + execute -``` - -Both phases scale **linearly**. - ---- - -## Visualization: The Story in One Glance - -Interactive D3 dashboard: `docs/benchmarks/report-inline.html` - -- **Log-log plot** with four series (hash, total, enqueue, drain) -- **Threshold marker** at $n=1024$ -- **Color-coded stat cards** matching the chart -- **Straight line** from 3 k → 30 k = proof of $O(n)$ - ---- - -## Lessons Learned - -1. **Measure first** – curve fitting exposed $O(n \log n)$ before any code change. -2. **Benchmarks lie** – a “fast” radix at $n=1,000$ obliterated $n=10$. -3. **Memory bandwidth > CPU** – 5 MiB of zeroing dominated tiny inputs. -4. **Hybrid wins** – comparison sort is _faster_ for small $n$. -5. **Visualize the win** – a straight line on log-log is worth a thousand numbers. - ---- - -## What’s Next? - -| Idea | Expected Gain | -| --------------------------------------- | ------------------ | -| **Active-bucket zeroing** | ~15 % at large $n$ | -| **Cross-tx scratch pooling** | Reduce alloc churn | -| **Collapse rule_id to u8** (≤256 rules) | Drop 2 passes | - -The scheduler is now **algorithmically optimal** and **constant-factor excellent**. - ---- - -## Conclusion: Echoing the Future - -Echo’s deterministic scheduler evolved from **$O(n \log n)$** to **$O(n)$** with a **hybrid adaptive radix sort**: - -- **44 % faster** at typical game loads ($n=1,000$) -- **Perfect linear scaling** to **30 k rewrites** -- **Well under 60 FPS budget** -- **Zero regressions** at small $n$ -- **Beautiful dashboard** proving the win - -Traditional engines treat determinism as an **afterthought**—a feature bolted on with prediction and prayer. Echo treats it as a **mathematical guarantee**, baked into every layer from DPO theory to the scheduler you just read about. - -When you can execute **30,000 deterministic rewrites per frame** and still hit **60 FPS**, you’re not just optimizing code—you’re **proving a new kind of game engine is possible**. One where: - -- **Multiplayer “just works”** (same pure function → no desync) -- **Replay is physics** (rewind by recomputing graph history) -- **AI training is reproducible** -- **Formal verification** becomes practical -- **Time-travel debugging** is native - -**The graph is a straight line. The future is deterministic. Echo is how we get there.** 🚀 - ---- - -## Code References - -- **Implementation**: crates/warp-core/src/scheduler.rs (see `radix_sort`, `drain_in_order`) -- **Benchmarks**: crates/warp-benches/benches/scheduler_drain.rs -- **Dashboard**: `docs/benchmarks/report-inline.html` -- **PR**: The radix optimization work has been merged to main. - ---- - -_Curious? Dive into the Echo docs or join the conversation on [GitHub](https://github.com/flyingrobots/echo)._ diff --git a/docs/archive/notes/scheduler-radix-optimization.md b/docs/archive/notes/scheduler-radix-optimization.md deleted file mode 100644 index e52b975a..00000000 --- a/docs/archive/notes/scheduler-radix-optimization.md +++ /dev/null @@ -1,465 +0,0 @@ - - - -# From $O(n log n)$ to $O(n)$: Optimizing Echo's Deterministic Scheduler - -**Tags:** performance, algorithms, optimization, radix-sort - ---- - -## TL;DR - -- Early benchmarks demonstrate that **Echo** can run at 60 fps while pushing ~5,000 DPO graph rewrites per frame -- Big viability question answered -- "Game scale" activity: confirmed - -## What is Echo? - -**Echo is a deterministic simulation engine built on graph rewriting theory.** While its applications are broad, it was born from the world of game development, so we'll use "game engine" as our primary lens. - -Unlike traditional game engines, which manage state through mutable object hierarchies and event loops, Echo represents the entire simulation state as a typed graph. This graph evolves through **deterministic rewrite rules**—mathematical transformations that guarantee identical results across platforms, replays, and simulations. - -At Echo's core is the _**WARP graph**_ (WARP). In Echo, _everything_ is a graph. Nodes are graphs, meaning a "player" is a complex subgraph with its own internal graph structure, not just an object. Edges are graphs, too, and can also have their own internal graphs, allowing expressiveness that carries structure and provenance. And most importantly, rules are graph rewrites. Echo updates the simulation by finding specific patterns in the WARP and replacing them with new ones. Every frame, the WARP is replaced by a new WARP, an _echo_ of the state that came before it. - -### Why bother? Aren't game engines a solved problem - -That's a fair question, but it’s aimed at the wrong target. While engines like Unreal and Unity are phenomenal rendering powerhouses and asset pipelines, they are built on an architectural foundation that struggles with the hardest problems in game development: **state management and networking**. - -The open secret of multiplayer development is that no two machines in a session ever truly agree on the game's state. What the player experiences is a sophisticated illusion, a constant, high-speed negotiation between **client-side prediction** and **authoritative server corrections**. - -I know this because I'm one of the developers who built those illusions. I've written the predictive input systems and complex netcode designed to paper over the cracks. The "rubber-banding" we've all experienced isn't a _bug_—it's an _artifact_. It's the unavoidable symptom of a system where state is **divergent by default**. - -This architectural flaw creates a secondary nightmare: **debugging**. When state is mutable, concurrent, and non-deterministic, reproducing a bug becomes a dark art. It's often impossible to look at a game state and know with certainty _how it got that way_. The system is fundamentally non-reproducible. - -The state of the art is built on patches, prediction, and arbitration to hide this core problem. The architecture itself is fragile. - -Until now. - -### Version Control for Reality - -One way to understand how Echo works is to imagine the simulation as version control for moments in time. In this mental model, a frame is like an immutable commit. And like a commit each frame has a canonical, cryptographic hash over the entire reachable graph, encoded in a fixed order. Echo treats inputs from players and other game world updates as candidate graph rewrites, and thanks to _confluence_, some category theory math, we can fold them into a single, deterministic effect. Finally, the scheduler applies all rewrites in a deterministic order and produces the next snapshot. - -No prediction. No rollback. No "authoritative correction." Just one pure function from `(world, inputs) → world′`. - -If two machines disagree, they disagree fast: a hash mismatch at frame `N+1` is a precise alarm, not a rubber‑band later. - -### ASCII timeline (branching and merge, deterministically) - -```text - Frame₀ - │ - ▼ - Frame₁───┐ - │ \ - ▼ \ - Frame₂A Frame₂B - │ │ - └────┬────┘ - ▼ - Merge₃ (confluence + canonical rewrite order) -``` - -### What Echo Unlocks - -This "version control" model isn't just a metaphor; it's a new architecture that unlocks capabilities that look "impossible" in a traditional engine. - -It enables **perfect replays**, as every frame is a commit that can be recomputed from its inputs to a bit‑identical state. This, in turn, provides an **infinite debugger**: provenance is embedded directly in the graph, allowing you to query its history to see who changed what, when, and why. - -For competitive games, this provides **provable fairness**, as a frame's cryptographic hash is a verifiable signature of "what happened." This all adds up to **zero silent desync**. A hash mismatch catches drift immediately and precisely, long before a user ever notices. - -Networking becomes straightforward: distribute inputs, compute the same function, compare hashes. When the math agrees, the world agrees. - -## [](https://dev.to/flyingrobots/determinism-by-construction-inside-echos-recursive-meta-graph-ecs-3491-temp-slug-8201751?preview=3b87bb097d6497d71ce72d6b6e87a1a101318ff960042f1db3908b807b6dd9a1b0b3811607d98ea25549311a530faa30d469ddd1cf0ac2c60e8f92fd#confluence-not-arbitration)Confluence, Not Arbitration - -When multiple updates target related state, we don't race them, we *merge* them with deterministic math. We use **confluence operators** with **lattice** properties: - -**Associative**, **Commutative**, **Idempotent** (ACI) - -Examples: - -Tags union: `join(TagsA, TagsB) = TagsA ∪ TagsB` - -Scalar cap: `join(Cap(a), Cap(b)) = Cap(max(a, b))` - -Those properties guarantee that folding a bucket of updates yields one result, independent of arrival order and partitioning. - -## [](https://dev.to/flyingrobots/determinism-by-construction-inside-echos-recursive-meta-graph-ecs-3491-temp-slug-8201751?preview=3b87bb097d6497d71ce72d6b6e87a1a101318ff960042f1db3908b807b6dd9a1b0b3811607d98ea25549311a530faa30d469ddd1cf0ac2c60e8f92fd#safe-parallelism-by-construction)Safe Parallelism by Construction - -Echo implements updates as **DPO (Double Push‑Out) graph rewrites**. This structure provides safe parallelism by construction: independent rewrites can apply in parallel without issue. Any overlapping rewrites are either deterministically merged by a lattice or rejected as invalid. For any remaining, dependent rewrites, the scheduler enforces a canonical order. - -The upshot: "Which rule ran first?" stops being a source of nondeterminism. - -A sketch of the full *fold→rewrite→commit* pipeline: - -> 1. Collect inputs for frame `N+1`. -> 2. Bucket by (scope, rule family). -> 3. Confluence fold each bucket (ACI). -> 4. Apply remaining rewrites in a canonical order: -> -> -> -> ```text -> order by (scope_hash, family, compact_rule_id, payload_digest). -> (Early convention — current drain key: scope, rule_id, nonce) -> ``` -> -> 1. Emit a new snapshot and compute commit hash. - -## [](https://dev.to/flyingrobots/determinism-by-construction-inside-echos-recursive-meta-graph-ecs-3491-temp-slug-8201751?preview=3b87bb097d6497d71ce72d6b6e87a1a101318ff960042f1db3908b807b6dd9a1b0b3811607d98ea25549311a530faa30d469ddd1cf0ac2c60e8f92fd#a-tiny-rewrite-a-tiny-lattice)A Tiny Rewrite, A Tiny Lattice - -Rewrite (motion) in Scalar terms: - -> Match: an entity with position p and velocity v -> Replace: position p′ = p + v·dt; velocity unchanged - -Lattice example (cap / max): - -> join(Cap(α), Cap(β)) = Cap(max(α, β)) -> ACI → the fold of {Cap(2), Cap(5), Cap(3)} is Cap(5) regardless of order. - -These primitives, **rewrites** and **lattices**, are the heart of Echo's "determinism by construction." - -**What makes Echo different:** - -- **Determinism by design**: Same inputs → same outputs, always. No floating-point drift, no race conditions, no "it works on my machine." -- **Formal semantics**: Built on Double Pushout (DPO) category theory—every state transition is mathematically provable. -- **Replay from the future**: Rewind time, fork timelines, or replay from any checkpoint. Your game is a pure function. -- **Networked lockstep**: Perfect synchronization without sending world state. Just send inputs; all clients compute identical results. -- **AI training paradise**: Deterministic = reproducible = debuggable. Train agents with confidence. - -Echo isn't just another ECS—it's a **fundamentally different way to build games**, where the scheduler isn't just an implementation detail, it's the guarantee of determinism itself. - ---- - -## The Problem: $O(n log n)$ Was Showing - -Echo's deterministic scheduler needs to execute rewrites in strict lexicographic order: `(scope_hash, rule_id, nonce)`. This ensures identical results across platforms and replays—critical for a deterministic game engine. - -Our initial implementation used a `BTreeMap<(Hash, Hash), PendingRewrite>`: - -```rust -// Old approach -pub(crate) pending: BTreeMap<(Hash, Hash), PendingRewrite> -``` - -**The bottleneck:** Insertions into the `BTreeMap` required $O(n \log n)$ comparisons over 256-bit scope hashes. Draining via `BTreeMap::drain()` was $O(n)$. The radix sort optimization eliminates the insertion bottleneck. Benchmarks showed: - -```text -n=1000: ~1.33ms (comparison sort via BTreeMap iteration) -n=3000: ~4.2ms (log factor starting to hurt) -``` - -Curve fitting confirmed **T/n ≈ -345 + 272.7·ln(n)**—textbook $O(n log n)$. - ---- - -## The Solution: 20-Pass Radix Sort - -Radix sort achieves **$O(n)$** complexity with zero comparisons by treating keys as sequences of digits. We implemented: - -- **LSD radix sort** with 16-bit big-endian digits -- **20 passes total**: 2 for nonce, 2 for rule_id, 16 for full 32-byte scope hash -- **Stable sorting** preserves insertion order for tie-breaking -- **Byte-lexicographic ordering** exactly matches BTreeMap semantics - -### The Architecture - -```rust -struct RewriteThin { - scope_be32: [u8; 32], // Full 256-bit scope - rule_id: u32, // Compact rule handle - nonce: u32, // Insertion-order tie-break - handle: u32, // Index into fat payload vec -} - -struct PendingTx

{ - thin: Vec, // Sorted keys - fat: Vec>, // Payloads (indexed by handle) - scratch: Vec, // Reused scratch buffer - counts16: Vec, // 256KB histogram (65536 buckets) -} -``` - -**Key insight:** Separate "thin" sorting keys from "fat" payloads. Only move 28-byte records during radix passes, then gather payloads at the end. - -```mermaid -graph LR - subgraph "Thin Keys (sorted)" - T1[RewriteThin
handle=0] - T2[RewriteThin
handle=2] - T3[RewriteThin
handle=1] - end - - subgraph "Fat Payloads (indexed)" - F0[PendingRewrite] - F1[PendingRewrite] - F2[PendingRewrite] - end - - T1 -->|handle=0| F0 - T2 -->|handle=2| F2 - T3 -->|handle=1| F1 - - style T1 fill:#e0af68 - style T2 fill:#e0af68 - style T3 fill:#e0af68 - style F0 fill:#9ece6a - style F1 fill:#9ece6a - style F2 fill:#9ece6a -``` - -### Radix Sort Pass Sequence - -The 20-pass LSD radix sort processes digits from least significant to most significant: - -```mermaid -graph TD - Start[Input: n rewrites] --> P1[Pass 1-2: nonce low→high] - P1 --> P2[Pass 3-4: rule_id low→high] - P2 --> P3[Pass 5-20: scope_hash bytes 31→0] - P3 --> Done[Output: sorted by scope,rule,nonce] - - style Start fill:#bb9af7 - style Done fill:#9ece6a - style P1 fill:#e0af68 - style P2 fill:#e0af68 - style P3 fill:#ff9e64 -``` - -Each pass: - -1. **Count** — histogram of 65536 16-bit buckets -2. **Prefix sum** — compute output positions -3. **Scatter** — stable placement into scratch buffer -4. **Flip** — swap `thin ↔ scratch` for next pass - ---- - -## The Disaster: Small-n Regression - -Initial results were not encouraging: - -```text -BEFORE (BTreeMap): AFTER (Radix): -n=10: 7.5µs n=10: 687µs (91x SLOWER!) -n=100: 90µs n=100: 667µs (7x SLOWER!) -n=1000: 1.33ms n=1000: 1.36ms (marginal) -``` - -![Before optimization - the "flat green line" disaster](../../notes/BEFORE.webp) -_The benchmark graph tells the story: that flat green line at low n is 5MB of zeroing overhead dominating tiny inputs._ - -**What went wrong?** The radix implementation zeroed a **256KB counts array 20 times per drain**: - -```rust -counts.fill(0); // 65,536 × u32 = 256KB -// × 20 passes = 5MB of writes for ANY input size -``` - -At n=10, we were doing **5MB of memory bandwidth** to sort **10 tiny records**. The "flat green line" in the benchmark graph told the story—massive fixed cost dominating small inputs. - ---- - -## The Fix: Adaptive Threshold - -The solution: **use the right tool for the job.** - -```mermaid -graph TD - Start[n rewrites to drain] --> Check{n ≤ 1024?} - Check -->|Yes| Comp[Comparison Sort
O n log n
Low constant] - Check -->|No| Radix[Radix Sort
O n
High constant] - Comp --> Done[Sorted output] - Radix --> Done - - style Start fill:#bb9af7 - style Comp fill:#e0af68 - style Radix fill:#9ece6a - style Done fill:#bb9af7 - style Check fill:#ff9e64 -``` - -```rust -const SMALL_SORT_THRESHOLD: usize = 1024; - -fn drain_in_order(&mut self) -> Vec

{ - let n = self.thin.len(); - if n > 1 { - if n <= SMALL_SORT_THRESHOLD { - // Fast path: comparison sort for small batches - self.thin.sort_unstable_by(cmp_thin); - } else { - // Scalable path: radix for large batches - self.radix_sort(); - } - } - // ... drain logic -} - - -fn cmp_thin(a: &RewriteThin, b: &RewriteThin) -> Ordering { - a.scope_be32.cmp(&b.scope_be32) - .then_with(|| a.rule_id.cmp(&b.rule_id)) - .then_with(|| a.nonce.cmp(&b.nonce)) -} -``` - -**Why 1024?** Empirical testing showed: - -- Below ~500: comparison sort wins (no zeroing overhead) -- Above ~2000: radix sort wins ($O(n)$ scales) -- **1024: conservative sweet spot** where both approaches perform similarly - -![After optimization - hybrid approach](../../notes/AFTER.webp) -_The fix: adaptive threshold keeps small inputs fast while unlocking $O(n)$ scaling at large $n$._ - ---- - -## The Results: Perfect $O(n)$ Scaling - -Final benchmark results across 6 data points (10, 100, 1k, 3k, 10k, 30k): - -| Input n | Old (BTreeMap) | New (Hybrid) | Speedup | Per-element | -| ------- | -------------- | ------------ | -------- | ----------- | -| 10 | 7.5µs | 7.6µs | -1% | 760ns | -| 100 | 90µs | 76µs | +16% | 760ns | -| 1,000 | 1.33ms | 0.75ms | **+44%** | 750ns | -| 3,000 | — | 3.03ms | — | 1010ns | -| 10,000 | — | 9.74ms | — | 974ns | -| 30,000 | — | 29.53ms | — | 984ns | - -![Final results - perfect linear scaling](../../notes/Final.webp) -_The complete picture: purple (snapshot hash), green (scheduler total), yellow (enqueue), red (drain). Note the threshold marker at $n=1024$ and the perfectly straight lines beyond it._ - -**Key observations:** - -1. **Comparison sort regime ($n ≤ 1024$):** ~750ns/element, competitive with old approach -2. **Radix sort regime ($n > 1024$):** Converges to ~1µs/element with **zero deviation** -3. **Scaling from 3k → 30k (10× data):** 9.75× time—textbook $O(n)$ -4. **60 FPS viability:** At $n=1000$ (typical game scene), scheduler overhead is just **0.75ms = 4.5% of 16.67ms frame budget** - -### Phase Breakdown - -Breaking down enqueue vs drain at $n=30k$: - -```text -Total: 37.61ms (100%) -Enqueue: 12.87ms (34%) — Hash lookups + last-wins dedupe -Drain: 24.83ms (66%) — Radix sort + conflict checks + execute -``` - -```mermaid -%%{init: {'theme':'dark'}}%% -pie title Scheduler Time Breakdown at n=30k - "Enqueue (hash + dedupe)" : 34 - "Drain (radix + conflicts)" : 66 -``` - -The drain phase dominates, but both scale linearly. Future optimizations could target the radix sort overhead (active-bucket zeroing, cross-transaction pooling), but the current approach achieves our performance targets. - ---- - -## The Visualization: Telling the Story - -We built an interactive D3 dashboard (`docs/benchmarks/report-inline.html`) showing: - -- **Four series on log-log plot:** - - Purple (solid): Snapshot Hash baseline - - Green (solid): Scheduler Drain Total - - Yellow (dashed): Enqueue phase - - Red (dashed): Drain phase - -- **Threshold marker at $n=1024$** showing where the sorting strategy switches - -- **2×2 color-coded stat cards** matching chart colors for instant visual connection - -- **Explanatory context:** What we measure, why 60 FPS matters, how $O(n)$ scaling works - -**The key visual:** A straight line on the $log-log$ plot from 3k to 30k—proof of perfect linear scaling. - ---- - -## Lessons Learned - -### 1. **Measure First, Optimize Second** - -Curve fitting (`T/n ≈ 272.7·ln(n)`) confirmed the $O(n log n)$ bottleneck before we touched code. - -### 2. **Don't Optimize for Benchmarks Alone** - -The initial radix implementation looked good at $n=1000$ but destroyed small-batch performance. Real workloads include both. - -### 3. **Memory Bandwidth Matters** - -Zeroing 5MB of counts array matters more than CPU cycles at small $n$. The "flat line" in benchmarks was the smoking gun. - -### 4. **Hybrid Approaches Win** - -Comparison sort isn't "slow"—it's just $O(n log n)$. For small $n$, it's faster than **any** $O(n)$ algorithm with high constants. - -### 5. **Visualize the Win** - -A good chart tells the story instantly. Our dashboard shows the threshold switch, phase breakdown, and perfect scaling at a glance. - ---- - -## What's Next? - -Future optimizations: - -1. **Active-bucket zeroing**: Only zero counts buckets actually used (saves ~15% at large $n$) -2. **Cross-transaction pooling**: Share scratch buffers across transactions via arena allocator -3. **Rule-domain optimization**: If we have <256 rules, collapse `rule_id` to single-byte direct indexing (saves 2 passes) - -The scheduler is algorithmically optimal, scales to 30k rewrites in <30ms, and the constants are excellent. - ---- - -## Conclusion: Echoing the Future - -Echo's deterministic scheduler went from $O(n log n)$ BTreeMap to $O(n)$ hybrid adaptive sorter: - -- ✅ **44% faster at typical workloads ($n=1000$)** -- ✅ **Perfect linear scaling to 30k rewrites** -- ✅ **Well under 60 FPS budget** -- ✅ **Zero regressions at small n** -- ✅ **Beautiful visualization proving the win** - -The textbook said "radix sort is $O(n)$." The benchmarks said "prove it." **The graph is a straight line.** - -But here's the deeper point: **This optimization matters because Echo is building something fundamentally new.** - -Traditional game engines treat determinism as an afterthought—a nice-to-have feature bolted on through careful engineering and hope. Echo treats it as a **mathematical guarantee**, woven into every layer from category theory foundations to the scheduler you're reading about right now. - -When you can execute 30,000 deterministic rewrite rules per frame and still hit 60 FPS, you're not just optimizing a scheduler—you're **proving that a different kind of game engine is possible.** One where: - -- **Multiplayer "just works"** because clients can't desync (they're running the same pure function) -- **Replay isn't a feature**, it's physics (rewind time by replaying the graph rewrite history) -- **AI training scales** because every training episode is perfectly reproducible -- **Formal verification** becomes practical (prove your game logic correct, not just test it) -- **Time travel debugging** isn't science fiction (checkpoint the graph, fork timelines, compare outcomes) - -Echo isn't just a faster game engine. **Echo is a different game engine.** One built on the mathematical foundation that traditional engines lack. One where the scheduler's deterministic ordering isn't a nice property—it's the **fundamental guarantee** that makes everything else possible. - -This optimization journey—from spotting the $O(n log n)$ bottleneck to proving $O(n)$ scaling with a hybrid radix sorter—is what it takes to make that vision real. To make determinism **fast enough** that developers don't have to choose between correctness and performance. - -The graph is a straight line. The future is deterministic. **And Echo is how we get there.** 🚀 - ---- - -> **Note:** Code references below reflect state at time of writing and may be -> stale. Paths and line numbers have likely changed since this document was -> authored. Use repo search (`rg`) to locate current implementations. - -## Code References - -- Implementation: `crates/warp-core/src/scheduler.rs` (see `fn radix_sort` near line 338) _(line numbers may have shifted)_ -- Benchmarks: `crates/warp-benches/benches/scheduler_drain.rs` -- Dashboard: `docs/benchmarks/report-inline.html` -- The radix optimization work has been merged to main. - ---- - -_Want to learn more? Check out the [Echo documentation](/meta/docs-index) or join the discussion on [GitHub](https://github.com/flyingrobots/echo)._ diff --git a/docs/archive/notes/xtask-wizard.md b/docs/archive/notes/xtask-wizard.md deleted file mode 100644 index 2bae3f0e..00000000 --- a/docs/archive/notes/xtask-wizard.md +++ /dev/null @@ -1,51 +0,0 @@ - - - -# xtask "workday wizard" — concept note - -Goal: a human-friendly `cargo xtask` (or `just`/`make` alias) that walks a contributor through starting and ending a work session, with automation hooks for branches, PRs, issues, and planning. - -## Core flow - -### Start session - -- Prompt for intent/issue: pick from open GitHub issues (via gh CLI) or free text. -- Branch helper: suggest branch name (`echo/-`), create and checkout if approved. -- Env checks: toolchain match, hooks installed (`make hooks`), `cargo fmt -- --check`/`clippy` optional preflight. - -### During session - -- Task DAG helper: load tasks from issue body / local `tasks.yaml`; compute simple priority/topo order (dependencies, P1/P0 tags). -- Bench/test shortcuts: menu to run common commands (clippy, cargo test -p warp-core, bench targets). -- Docs guard assist: if runtime code touched, remind to update relevant specs/ADRs. - -### End session - -- Summarize changes: gather `git status`, staged/untracked hints. -- PR prep: prompt for PR title/body template (with issue closing keywords); optionally run `git commit` and `gh pr create`. -- Issue hygiene: assign milestone/board/labels via gh CLI; auto-link PR to issue. - -## Nice-to-haves - -- Determinism check shortcut: run twin-engine sandbox determinism A/B (radix vs legacy) and summarize. -- Planner math: simple critical path/priority scoring across tasks.yaml; suggest next task when current is blocked. -- Cache hints: detect heavy commands run recently, skip/confirm rerun. -- Telemetry: write a small JSON session record for later blog/mining (start/end time, commands run, tests status). - -## Tech sketch - -- Implement under `xtask` crate in workspace; expose `cargo xtask wizard`. -- Use `dialoguer`/`inquire` for prompts; `serde_yaml/json` for tasks; `gh` CLI for GitHub ops (fallback to no-op if missing). -- Config file (`.echo/xtask.toml`) for defaults (branch prefix, issue labels, PR template path). - -## Open questions - -- How much is automated vs. suggested (avoid surprising commits)? -- Should Docs Guard be enforced via wizard or still via hooks? -- Where to store per-session summaries (keep in git or external log)? - -## Next steps - -- Prototype a minimal “start session” + “end session” flow with `gh` optional. -- Add a `tasks.yaml` example and priority/topo helper. -- Wire into make/just: `make wizard` → `cargo xtask wizard`. diff --git a/docs/archive/phase1-plan.md b/docs/archive/phase1-plan.md deleted file mode 100644 index 879c1a8c..00000000 --- a/docs/archive/phase1-plan.md +++ /dev/null @@ -1,130 +0,0 @@ - - - -# Phase 1 – Core Ignition Plan - -Goal: deliver a deterministic Rust implementation of WARP powering the Echo runtime, with tangible demos at each milestone. This plan outlines task chains, dependencies, and expected demonstrations. - -Status (2025-12-30): - -- 1A (bootstrap) and 1B (rewrite executor spike) are effectively landed in `main` via `warp-core` (B0/B1: two-plane attachments + WarpInstances). -- The next “engine-facing” milestone is 1C (Rhai/TS bindings) and the next “tooling-facing” milestone is completing the WARP View Protocol demo path (`docs/tasks.md`). - ---- - -## Task Graph - -```mermaid -graph TD - A[1A · WARP Core Bootstrap] - B[1B · Rewrite Executor Spike] - C[1C · Rhai/TS Bindings] - D[1D · Echo ECS on WARP] - E[1E · Networking & Confluence MVP] - F[1F · Tooling Integration] - - A --> B --> C --> D --> E --> F - B --> DemoToy - D --> DemoNetcode - E --> DemoTimeTravel - F --> DemoLiveCoding - - subgraph Demos - DemoToy[Demo 2 · Toy Rewrite Benchmark] - DemoNetcode[Demo 1 · Deterministic Netcode] - DemoTimeTravel[Demo 5 · Time Travel Merge] - DemoLiveCoding[Demo 6 · Rhai Live Coding] - end -``` - ---- - -## Phases & Tangible Outcomes - -### 1A · WARP Core Bootstrap - -- Tasks - - Scaffold crates (`warp-core`, `warp-wasm`, `warp-cli`). - - Implement GraphStore primitives, hash utilities, scheduler skeleton. - - CI: `cargo fmt/clippy/test` baseline. -- Demonstration: _None_ (foundation only). - -### 1B · Rewrite Executor Spike - -- Tasks - - Implement motion rule test (Position + Velocity rewrite). - - Execute deterministic ordering + snapshot hashing. - - Add minimal diff/commit log entries. -- Demonstration: **Demo 2 · Toy Benchmark** - - 100 nodes, 10 rules, property tests showing stable hashes. - -### 1C · Rhai/TS Bindings - -- Tasks - - Embed Rhai with deterministic sandbox + host modules. - - Build WASM bindings for tooling. - - Port inspector CLI to use snapshots. -- Demonstration: Rhai script triggers rewrite; inspector shows matching snapshot hash. - -### 1D · Echo ECS on WARP - -- Tasks - - Map existing ECS system set onto rewrite rules. - - Replace Codex’s Baby event queue with rewrite intents. - - Emit frame hash HUD. -- Demonstration: **Demo 1 · Deterministic Netcode** - - Two instances, identical inputs, frame hash displayed per tick. - -### 1E · Networking & Confluence MVP - -- Tasks - - Implement rewrite transaction packets; replay on peers. - - Converge canonical snapshots; handle conflicts deterministically. - - Integrate rollback path (branch rewind, replay log). -- Demonstration: **Demo 5 · Time Travel** - - Fork, edit, merge branch; show canonical outcome. - -### 1F · Tooling Integration - -- Tasks - - Echo Studio (TS + WASM) graph viewer with live updates. - - Entropy lens, paradox heatmap overlays. - - Rhai live coding pipeline (hot reload). -- Demonstrations: - - **Demo 3 · Real Benchmark** (1k nodes, 100 rules). - - **Demo 6 · Live Coding** (Rhai edit updates live graph). - ---- - -## Performance / Benchmark Milestones - -| Milestone | Target | Notes | -| ------------------ | --------------------------------------------- | --------------------- | -| Toy Benchmark | 100 nodes / 10 rules / 200 iterations < 1ms | Demo 2 | -| Real Demo | 1,000 nodes / 100 rules < 10ms rewrite checks | Demo 3 | -| Production Stretch | 10,000 nodes / 1000 rules (profiling only) | Phase 2 optimizations | - -Optimization roadmap once baseline is working: - -1. Incremental pattern matching. -2. Spatial indexing. -3. SIMD bitmap operations. -4. Critical pair analysis for confluence proofs. - ---- - -## Networking Demo Targets - -| Mode | Deliverable | -| --------- | --------------------------------------------------------------- | -| Lockstep | Replay identical inputs; frame hash equality per tick. | -| Rollback | Predictive input with rollback on mismatch. | -| Authority | Host selects canonical branch; entropy auditor rejects paradox. | - ---- - -## Documentation Checklist - -- Update `docs/warp-runtime-architecture.md` as rules/loop evolve. - -Phase 1 completes when Demo 6 (Live Coding) runs atop the Rust WARP runtime with inspector tooling in place, using Rhai as the scripting layer. diff --git a/docs/archive/plans/BOAW-tech-debt.md b/docs/archive/plans/BOAW-tech-debt.md deleted file mode 100644 index d348b46a..00000000 --- a/docs/archive/plans/BOAW-tech-debt.md +++ /dev/null @@ -1,315 +0,0 @@ - - - - - -# BOAW Roadmap: Phase 6B → Phase 9 - -**Created:** 2026-01-20 -**Status:** AWAITING APPROVAL -**Context:** Post-Phase 6B integration — cleanup, guardrails, and planning - ---- - -## Classification Rubric - -| If it... | Then it's... | -| ------------------------------------ | -------------------------------------- | -| Unblocks a phase | **Roadmap** (Tiers 1-3) | -| Reduces risk or prevents regressions | **Guardrail** (Tier 0.5) | -| Improves performance | **Perf Gate** (only after measurement) | -| Is unused code | **Delete immediately** (Tier 0) | - ---- - -## Tier 0: Cleanup (Today) - -_Dead code and doc drift. Do immediately after merge._ - -### 0.1 Delete `emit_view_op_delta()` - -| Field | Value | -| -------------- | --------------------------------------------- | -| **Location** | `crates/echo-dind-tests/src/rules.rs:600-648` | -| **Call Sites** | 0 | -| **Risk** | None | - -**Why:** Deprecated function using non-deterministic `delta.len()` sequencing. -Replaced by `emit_view_op_delta_scoped()`. Keeping it risks copy-paste of broken pattern. - -### 0.2 Delete `execute_parallel_stride()` + Feature Gate - -| Field | Value | -| -------------- | ------------------------------------------- | -| **Location** | `crates/warp-core/src/boaw/exec.rs:176-207` | -| **Call Sites** | 3 (1 conditional, 2 Phase 6A tests) | -| **Risk** | Low | - -**Why:** Phase 6A stride execution superseded by Phase 6B sharded execution. -Feature-gated behind `parallel-stride-fallback`. Adds maintenance burden. - -**Steps:** - -1. Delete Phase 6A equivalence tests (`boaw_parallel_exec.rs:286-365`) -2. Remove stride fallback conditional (`exec.rs:67-83`) -3. Delete `execute_parallel_stride()` function -4. Remove `parallel-stride-fallback` feature from `Cargo.toml` - -### 0.3 Doc Accuracy Pass - -Verify these are still accurate post-merge: - - - -- [ ] `TECH-DEBT-BOAW.md` — mark Phase 6B items complete _(not completed before archival)_ -- [ ] `ADR-0007-BOAW-Storage.md` — phase status markers _(not completed before archival)_ -- [ ] `CHANGELOG.md` — PR #257 merge recorded _(not completed before archival)_ - ---- - -## Tier 0.5: Correctness Guardrails (This Week) - -_Tests we can land now + baseline measurements. Reduces future regression risk._ - -### 0.5.1 Activate Passing Tests - -Some `#[ignore]` tests may now pass after Phase 6B. Audit and activate: - -| Test File | Check For | -| --------------------- | --------------------------------------------- | -| `boaw_determinism.rs` | Any tests that only needed parallel execution | -| `boaw_end_to_end.rs` | Full integration tests | -| `boaw_footprints.rs` | T3.1 already passes; verify others | - -### 0.5.2 WarpOpKey Invariant Test - -Verify `WarpOpKey` ordering is stable and exercised: - -- Canonical sort order matches spec -- No collisions under realistic workloads -- Public API (`sort_key()`) works for external verification - -### 0.5.3 Initial Benchmark Baseline - -**Purpose:** Prove parallelism delivers measurable wins. Capture baseline so future -phases don't accidentally regress performance. - -**Scope:** Minimal, not a full optimization suite. - -| Benchmark | What It Measures | -| ------------------------- | ---------------------------------- | -| `parallel_vs_serial_10` | 10 rewrites: parallel speedup | -| `parallel_vs_serial_100` | 100 rewrites: parallel speedup | -| `parallel_vs_serial_1000` | 1000 rewrites: parallel speedup | -| `shard_distribution` | Are rewrites spread across shards? | - -**Location:** `benches/boaw_baseline.rs` (new file) - -**Success Criteria:** - -- Parallel ≥ serial for n ≥ 100 (no regression) -- Document baseline numbers in `docs/notes/boaw-perf-baseline.md` - ---- - -## Tier 1: Phase 7 — Forking - -_Multi-parent commits and prerequisites. ~2-3 weeks._ - -### Prerequisites (Enable Forking) - -| Component | Tests Unblocked | Notes | -| -------------------------------- | --------------- | ----------------------------------------------------------------------- | -| **OpenPortal scheduling (T7.1)** | 4 | Scheduler tracks new warps; enforces "no same-tick writes to new warps" | -| **DeltaView** | 6 | Overlay + base resolution during execution | -| ~~**FootprintGuard**~~ | 3 | ✅ Done (44aebb0d8f7b, 0d0231b55761) | -| **SnapshotBuilder wiring** | 1 | Connect builder to test harness | - -### Core Forking Work - -| Component | Description | -| ----------------------------- | -------------------------------- | -| Multi-parent commit structure | Commit can have 0..n parents | -| Worldline DAG | Track branch/merge topology | -| Parent addressing | Reference parents by commit hash | - -### Tests Unblocked: 14 - -```text -boaw_openportal_rules.rs — 4 tests (T7.1) -boaw_cow.rs — 6 tests (DeltaView) -boaw_footprints.rs — 3 tests (FootprintGuard) -boaw_determinism.rs — 1 test (SnapshotBuilder) -``` - ---- - -## Tier 2: Phase 8 — Collapse/Merge - -_Deterministic multi-parent reconciliation. ~2-3 weeks. Requires Phase 7._ - -### Merge Components - -| Component | Description | -| ----------------------------- | ------------------------------------------------ | -| **Typed merge registry** | Per-type: Sensitivity, MergeBehavior, Disclosure | -| **Merge regimes** | Commutative (CRDT), LWW, ConflictOnly | -| **Conflict artifacts** | Deterministic, contains only hashes (no secrets) | -| **Canonical parent ordering** | Sort by `commit_hash` for order-dependent merges | -| **Presence policies** | delete-wins (default), add-wins, LWW | - -### Tests Unblocked: 10 - -```text -boaw_merge.rs — all 10 tests -├── t6_1: Commutative merge parent-order invariance -├── t6_2: Canonical ordering for order-dependent -├── t6_3: Conflict artifact determinism -├── merge_regime_crdt_like_is_preferred -├── merge_regime_lww_with_canonical_order -├── presence_policy_delete_wins -├── presence_policy_add_wins -├── conflict_artifact_is_first_class_and_deterministic -└── conflict_artifact_contains_no_secrets -``` - ---- - -## Tier 3: Phase 9 — Privacy Claims - -_Ledger-safe provenance. ~2-3 weeks. Requires Phase 8._ - -### Privacy Components - -| Component | Description | -| ------------------------- | ------------------------------------------------------- | -| **Atom type registry** | Sensitivity (Public/Private/ForbiddenInLedger) | -| **Mind mode enforcement** | Reject ForbiddenInLedger atoms in ledger | -| **ClaimRecord structure** | claim_key, scheme_id, statement_hash, commitment, proof | -| **Commitment safety** | Pepper-based hashing (dictionary-safe) | -| **ZK proof merging** | Verify during collapse; quarantine invalid | -| **Diagnostics mode** | Richer introspection for trusted debugging | - -### Tests Unblocked: 9 - -```text -boaw_privacy.rs — all 9 tests -├── t7_1: Mind mode forbids ForbiddenInLedger -├── t7_2: Invalid proofs quarantined -├── t7_3: Conflicting valid claims → artifact -├── t7_4: Commitment dictionary-safe with pepper -├── atom_type_declares_sensitivity -├── atom_type_declares_merge_behavior -├── atom_type_declares_disclosure_policy -├── claim_record_is_canonical -└── diagnostics_mode_allows_richer_introspection -``` - ---- - -## Perf Gate (Recurring) - -_Run at end of each tier. Catch regressions early._ - -### What to Measure - -| Metric | Baseline (Tier 0.5) | Gate Threshold | -| --------------------------- | ------------------- | -------------------------- | -| Parallel vs serial (n=100) | TBD | No regression (≥ baseline) | -| Parallel vs serial (n=1000) | TBD | No regression (≥ baseline) | -| Merge time (n ops) | TBD | < 2x baseline | -| Snapshot build time | TBD | < 2x baseline | - -### When to Run - -- [x] After Tier 0 (cleanup) — establish baseline -- [ ] After Tier 1 (Phase 7) — verify forking doesn't regress -- [ ] After Tier 2 (Phase 8) — verify merge doesn't regress -- [ ] After Tier 3 (Phase 9) — verify privacy checks don't regress - -### Optimization Work (Only If Gate Fails) - -These are **not scheduled**. Only pursue if perf gate shows regression: - -| Item | Trigger | Status | -| -------------------------- | ---------------------------------- | ------------- | -| ~~Cross-warp parallelism~~ | Multi-warp ticks show poor scaling | ✅ Done | -| State clone overhead | CI times unacceptable | Not scheduled | -| Shard rebalancing | Skewed distributions measured | Not scheduled | -| SIMD merge sort | Merge becomes bottleneck | Not scheduled | - ---- - -## Test Inventory Summary - -| Tier | Tests Unblocked | Cumulative | -| -------------------- | ------------------- | ---------- | -| Tier 0.5 | ~2-3 (audit needed) | ~2-3 | -| Tier 1 (Phase 7) | 14 | ~17 | -| Tier 2 (Phase 8) | 10 | ~27 | -| Tier 3 (Phase 9) | 9 | ~36 | -| Stress (run anytime) | 1 | 37 | - -**Current:** ~17 tests passing -**After Phase 9:** ~54 tests passing (all BOAW tests enabled) - ---- - - - -> **⚠️ TRACKING MOVED:** This archived checklist is preserved for historical -> context only. Active work tracking is now in -> [`TECH-DEBT-BOAW.md`](../../adr/TECH-DEBT-BOAW.md). Do NOT update checkboxes here. - -## Execution Checklist - -### ☐ Tier 0 Cleanup - -- [ ] Delete `emit_view_op_delta()` from `rules.rs` -- [ ] Delete `execute_parallel_stride()` + tests + feature gate -- [ ] Verify doc accuracy (TECH-DEBT, ADR, CHANGELOG) - -### Tier 0.5: Guardrails (This Week) - -- [ ] Audit `#[ignore]` tests — activate any that now pass -- [ ] Add/verify WarpOpKey invariant test -- [ ] Create `benches/boaw_baseline.rs` with minimal benchmarks -- [ ] Document baseline in `docs/notes/boaw-perf-baseline.md` -- [ ] Run perf gate, record numbers - -### Tier 1: Phase 7 (Next Sprint) - -- [ ] Implement OpenPortal scheduling (T7.1) -- [ ] Implement DeltaView -- [x] Implement FootprintGuard (44aebb0d8f7b, 0d0231b55761) -- [ ] Wire SnapshotBuilder to test harness -- [ ] Core forking semantics -- [ ] Activate 14 tests -- [ ] Run perf gate - -### Tier 2: Phase 8 (Following Sprint) - -- [ ] Typed merge registry -- [ ] Merge regimes + conflict artifacts -- [ ] Presence policies -- [ ] Activate 10 tests -- [ ] Run perf gate - -### Tier 3: Phase 9 (Future) - -- [ ] Atom type registry -- [ ] Mind mode + ClaimRecord -- [ ] ZK proof merging -- [ ] Activate 9 tests -- [ ] Run perf gate - ---- - -## References - -- [ADR-0007-BOAW-Storage.md](../../adr/ADR-0007-BOAW-Storage.md) — Full specification -- [TECH-DEBT-BOAW.md](../../adr/TECH-DEBT-BOAW.md) — Original tracking (to be updated) -- [PR #257](https://github.com/flyingrobots/echo/pull/257) — Phase 6B implementation -- Knowledge Graph: `BOAW_Phase_6B`, `Echo_BOAW_Architecture` diff --git a/docs/archive/plans/COMING_SOON.md b/docs/archive/plans/COMING_SOON.md deleted file mode 100644 index 3a596771..00000000 --- a/docs/archive/plans/COMING_SOON.md +++ /dev/null @@ -1,125 +0,0 @@ - - - -# Echo & Wesley: The Causal Application Guide - -Welcome to the future of causal development. This document explains how **Echo** (the substrate) and **Wesley** (the law-giver) work together to create deterministic, time-travelable applications. - ---- - -## 1. The Core Philosophy: "Law vs. Physics" - -Building an application on Echo is different from traditional state-management. We split the universe into two layers: - -1. **The Law (Wesley)**: Defines _what_ exists and _what_ is allowed to happen. It is expressed in GraphQL SDL with WARP directives. -2. **The Physics (Echo)**: The high-performance graph substrate that executes the laws, enforces constraints, and records the history of every atom. - ---- - -## 2. Wesley: The Schema Compiler - -Wesley is not a runtime; it is a **Law Compiler**. When you build an application, you start by writing a schema. - -### Defining the Ontology - -In a `.graphql` file, you define: - -- **Types**: The "Atoms" of your graph (e.g., `User`, `Position`, `InventoryItem`). -- **Channels**: The event buses where data is emitted (e.g., `PhysicsUpdates`, `ChatMessages`). -- **Policies**: How data on those channels is handled (`StrictSingle`, `Reduce:Sum`, or `Log`). - -### Defining Operations (The Intent ABI) - -Instead of arbitrary functions, you define **Operations (Ops)**. An Op is a declaration of intent to change the graph. - -```graphql -type Mutation { - movePlayer(id: ID!, delta: Vec3!): MoveResult @warp(opId: 101) -} -``` - -Wesley compiles this into an **Intermediate Representation (IR)**. Echo's code generator (`echo-ttd-gen`) then consumes this IR to produce: - -- Type-safe Rust structs. -- Enforcement tables (Footprints) that declare exactly which nodes an Op is allowed to read or write. - ---- - -## 3. Echo: The Causal Substrate - -Echo takes the artifacts from Wesley and provides the execution environment. - -### Graph Rewrites - -Every change in Echo is a **Graph Rewrite**. When an application triggers an Op (like `movePlayer`): - -1. **Intent**: An `EINT` (Echo Intent) frame is created. -2. **Scheduling**: The Echo Scheduler looks at the Op's **Footprint**. If two Ops touch different parts of the graph, they can run in parallel. -3. **Execution**: The rewrite rule is applied. This is a pure function: `(PriorState, OpArgs) -> (NewState, Emissions)`. -4. **Commit**: The new state is hashed (BLAKE3) and committed to the **Provenance Store**. - -### Determinism Guards - -Echo enforces "Ironclad Determinism": - -- **Floating Point**: All math uses `DFix64` (fixed-point) to ensure bit-exact results across Intel, ARM, and WASM. -- **No Side Effects**: Rewrite rules cannot call `Date.now()` or `Math.random()`. All entropy must be passed in as a seeded "Paradox" value. - ---- - -## 4. The Time-Travel Debugger (TTD) - -The TTD is not just a UI; it is a fundamental property of the **Provenance Store**. - -### Worldlines & Forks - -Because every tick is a content-addressed snapshot, Echo supports **Causal Branching**: - -- **Playback**: You can seek a "Cursor" to any tick in the past. -- **Forking**: You can create a new `WorldlineId` starting from a past tick. You can then apply different intents to see a "What If" scenario. -- **Replay**: The TTD can re-play an entire session and verify that the `state_root` hashes match the "Golden" run. - -### The Receipt System - -Every execution produces a **TTDR Receipt**. This is a cryptographically signed proof that: -_"At Tick X, Op Y was applied to State Z, resulting in State A and Emissions B."_ - ---- - -## 5. How to Build an "Echo App" - -### Step 1: The Wesley Sync - -Write your schema and run `cargo xtask wesley sync`. This vendors the types and manifests into your project. - -### Step 2: Implement Rewrite Rules - -In Rust, you implement the logic for your Ops. Echo provides a `GraphView` that enforces your footprint at runtime. - -```rust -fn handle_move_player(view: &mut GuardedView, args: MoveArgs) -> StepResult { - let mut pos = view.get_component::(args.id)?; - pos.x += args.delta.x; - view.set_component(args.id, pos)?; - Ok(Advanced) -} -``` - -### Step 3: Define the Scene Port - -Use `echo-scene-port` to map your graph state to visual objects. This produces a `SceneDelta`—a language-agnostic list of "Add Node", "Move Edge", or "Set Label" commands. - -### Step 4: The Frontend - -Wire the WASM `TtdEngine` into your React/Three.js app. The engine handles the worldlines; your UI just renders the current "Truth Frames" arriving on the subscribed channels. - ---- - -## 6. Coming Soon: The "Drill Sergeant" Workflow - -We are moving toward a workflow where **Determinism isn't Optional**. - -- **DIND (Deterministic Ironclad Nightmare Drills)**: Your app will be subjected to randomized operation orders to ensure it always converges to the same state. -- **Fuzzing the Law**: Wesley will generate "hostile" inputs to try and crash your rewrite rules. - -_Echo is more than an engine; it is a guarantee that causality is absolute._ diff --git a/docs/archive/plans/SPEC-0004-final-plan.md b/docs/archive/plans/SPEC-0004-final-plan.md deleted file mode 100644 index 56401e56..00000000 --- a/docs/archive/plans/SPEC-0004-final-plan.md +++ /dev/null @@ -1,249 +0,0 @@ - - - -# SPEC-0004 Implementation Plan: Worldlines, PlaybackCursors, ViewSessions, TruthBus - -**Status:** In Progress -**Created:** 2026-01-20 -**Spec:** `/docs/spec/SPEC-0004-worldlines-playback-truthbus.md` - ---- - -## Corrections Applied (from review) - -1. **U0Ref = WarpId** — MVP U0Ref is just a handle to `engine.initial_state` for a warp, not a checkpoint blob -2. **One entry per global tick per warp** — Store patches even if empty to maintain index alignment: `warp_patches[warp_id].len() == global_tick_history_len` -3. **Use existing canonical hash scheme** — `compute_state_root_for_warp_store` must use same ordering as `snapshot.rs` -4. **Minimal TruthSink** — `BTreeMap>` plus a parallel `BTreeMap>` for receipts, not a full bus layer -5. **Add demo emission for tests** — Need deterministic emission path or outputs are vacuous -6. **Explicit WarpOp coverage** — `apply_warp_op_to_store` must handle all variants or reject with typed error - ---- - -## Commit Status - -### ✅ Commit 1 — MBUS v2 Encoder/Decoder + Tests (COMPLETE) - -**Files Created:** - -- `crates/warp-core/src/materialization/frame_v2.rs` — V2 encoder/decoder with cursor-stamped packets - -**Files Modified:** - -- `crates/warp-core/src/materialization/mod.rs` — Export frame_v2 types - -**Tests Passing (11/11):** - -- T19: `mbus_v2_roundtrip_single_packet` -- T20: `mbus_v1_rejects_v2` -- T21: `mbus_v2_rejects_v1` -- T22: `mbus_v2_multi_packet_roundtrip` -- Plus edge case tests (empty entries, bad magic, truncated, etc.) - -**Gate:** `cargo test -p warp-core --features delta_validate -- frame_v2` ✅ - ---- - -### 🔲 Commit 2 — Types + IDs + ProvenanceStore Seam + Per-Warp Worldline Store - -**New Files:** - -- `crates/warp-core/src/worldline.rs` - - `WorldlineId(Hash)` — transparent wrapper - - `HashTriplet { state_root, patch_digest, commit_hash }` - - `WorldlineTickPatchV1` — per-warp projection of global tick - - `WorldlineTickHeaderV1` — shared header across warps - - `OutputFrameSet = Vec<(ChannelId, Vec)>` - -- `crates/warp-core/src/playback.rs` - - `CursorId(Hash)`, `SessionId(Hash)` — transparent wrappers - - `CursorRole { Writer, Reader }` - - `PlaybackMode { Paused, Play, StepForward, StepBack, Seek { target, then } }` - - `SeekThen { Pause, RestorePrevious, Play }` - - `CursorReceipt` — cursor context for truth frames - - `TruthFrame` — authoritative value with cursor receipt - -- `crates/warp-core/src/provenance_store.rs` - - `ProvenanceStore` trait (seam for future wormholes) - - `LocalProvenanceStore` — in-memory Vec-backed implementation - - `HistoryError { HistoryUnavailable { tick }, WorldlineNotFound }` - - `U0Ref = WarpId` (per correction #1) - -**Engine Modifications (`engine_impl.rs`):** - -- Add fields: - - ```rust - warp_patches: BTreeMap>, - warp_expected: BTreeMap>, - warp_outputs: BTreeMap>, - ``` - -- Modify `commit_with_receipt` to project global ops → per-warp patches -- **Invariant:** `warp_patches[warp_id].len() == tick_history.len()` (even for no-ops) - -**Gate:** `cargo test -p warp-core --features delta_validate` - ---- - -### 🔲 Commit 3 — Warp-Local Apply + State Root + Cursor Seek + Verification - -**Add to `playback.rs`:** - -- `PlaybackCursor` struct with: - - `cursor_id`, `worldline_id`, `warp_id`, `tick`, `role`, `mode` - - `store: GraphStore` (owned, never shared) - - `pin_max_tick: u64` -- `PlaybackCursor::seek_to(target, provenance)`: - - If `target < tick`: rebuild from U0 (initial_state for warp) - - Apply patches `tick.. }` -- `ViewSession::subscribe(channel)`, `set_active_cursor(cursor)` - -**Truth Sink (minimal, per correction #4):** - -- `TruthSink { frames: BTreeMap>, receipts: BTreeMap> }` -- Helper: `collect_frames(session_id) -> &[TruthFrame]` — returns frames for a session -- Helper: `last_receipt(session_id) -> Option<&CursorReceipt>` — reads from the receipts map - -**PlaybackCursor::step():** - -- Implement `PlaybackMode` state machine -- `Paused` → no-op -- `Play` → Writer appends (BOAW), Reader consumes then pauses at frontier -- `StepForward` → advance one then `Paused` -- `StepBack` → seek(tick-1) then `Paused` -- `Seek { target, then }` → seek then apply `SeekThen` - -**Tests:** - -- T1: `writer_play_advances_and_records_outputs` -- T2: `step_forward_advances_one_then_pauses` -- T3: `paused_noop_even_with_pending_intents` -- T7: `truth_frames_are_cursor_addressed_and_authoritative` -- T9: `two_sessions_same_channel_different_cursors_receive_different_truth` -- T10: `session_cursor_switch_is_opaque_to_subscribers` -- T16: `worker_count_invariance_for_writer_advance` - -**Gate:** `cargo test -p warp-core --features delta_validate` + `cargo test -p echo-dind-harness` - ---- - -### 🔲 Commit 5 — Record Outputs Per Tick + Seek/Playback - -**Engine Modifications:** - -- On `commit_with_receipt`, after `bus.finalize()`: - - ```rust - let outputs: OutputFrameSet = mat_report.channels - .iter() - .map(|fc| (fc.channel, fc.data.clone())) - .collect(); - self.warp_outputs.entry(root_warp).or_default().push(outputs); - ``` - -**Demo Emission (per correction #5):** - -- Add deterministic test emission path so T1/T8 aren't vacuous -- Option A: Demo rule that emits to channel based on tick -- Option B: Compute outputs from state deterministically for tests - -**ViewSession Publishing:** - -- `publish_truth(cursor, provenance, sink)` sources from `provenance.outputs(worldline, tick)` - -**Tests:** - -- T4: `seek_moves_cursor_without_mutating_writer_store` -- T5: `step_back_is_seek_minus_one_then_pause` -- T6: `reader_play_consumes_existing_then_pauses_at_frontier` -- T8: `outputs_match_recorded_bytes_for_same_tick` -- T19-T22: MBUS v2 integration - -**Gate:** `cargo test -p warp-core --features delta_validate` - ---- - -### 🔲 Commit 6 — Reducer Semantics + Checkpoint Skeleton + Fork Stub - -**New File: `crates/warp-core/src/retention.rs`** - -```rust -pub enum RetentionPolicy { - KeepAll, - CheckpointEvery { k: u64 }, - KeepRecent { window: u64, checkpoint_every: u64 }, - ArchiveToWormhole { after: u64, checkpoint_every: u64 }, // seam only -} -``` - -**Checkpoint Skeleton:** - -- `LocalProvenanceStore::checkpoint(warp_id, tick, state)` — naive clone -- `checkpoint_before(worldline, tick)` for fast seek - -**Fork Stub:** - -- `LocalProvenanceStore::fork(source, fork_tick, new_id)` — prefix-copy - -**Tests:** - -- T11: `reducer_commutative_is_permutation_invariant_and_replayable` -- T12: `reducer_order_dependent_is_canonically_deterministic_and_replayable` -- T13: `reduced_channel_emits_single_authoritative_value_per_tick` -- T17: `checkpoint_replay_equals_full_replay` -- T18: `fork_worldline_diverges_after_fork_tick_without_affecting_original` - -**Gate:** `cargo test -p warp-core --features delta_validate` - ---- - -## Key Files Reference - -| File | Purpose | -| -------------------------------- | --------------------------------------------------------- | -| `materialization/frame.rs:1-255` | Pattern for MBUS encoding | -| `engine_impl.rs:967-1085` | `commit_with_receipt` — hook for per-warp projection | -| `tick_patch.rs:98-461` | `WarpOp`, `apply_to_state` — pattern for `apply_to_store` | -| `snapshot.rs:90-265` | `compute_state_root`, `compute_commit_hash_v2` | -| `graph.rs:16-486` | `GraphStore`, `canonical_state_hash` | - ---- - -## Invariants (from spec) - -- **WL-001 (Holography):** Given U0Ref + patches + canonical apply, any tick's state is reconstructible -- **WL-002 (Truth):** Given recorded outputs per tick, any tick's client-visible truth is reconstructible byte-for-byte -- **CUR-001:** Cursor never mutates worldline unless role is Writer and mode requires advance -- **CUR-002:** Cursor never executes rules when seeking; it applies recorded patches only -- **CUR-003:** After seek/apply, cursor verifies expected hashes byte-for-byte -- **OUT-001:** For `(worldline_id, tick, channel)`, value bytes are deterministic across runs/machines -- **OUT-002:** Playback at tick t reproduces the same TruthFrames recorded at tick t -- **STEP-001:** No store mutation while any GraphView borrow exists for that store -- **STEP-002:** Seeking never touches writer cursor store; only cursor.store diff --git a/docs/archive/plans/SPEC-0004-review-hitlist.md b/docs/archive/plans/SPEC-0004-review-hitlist.md deleted file mode 100644 index 8f9c2616..00000000 --- a/docs/archive/plans/SPEC-0004-review-hitlist.md +++ /dev/null @@ -1,149 +0,0 @@ - - - -# SPEC-0004 Self-Review Hit List - -**Date:** 2026-01-22 -**Branch:** `graph-boaw` -**Status:** Pre-PR review complete - ---- - -## Summary - -| Category | High | Medium | Low | Total | -| ------------- | ----- | ------ | ------ | ------ | -| Source Code | 0 | 7 | 36 | 43 | -| Test Code | 1 | 8 | 18 | 27 | -| Documentation | 0 | 3 | 8 | 11 | -| API Surface | 0 | 0 | 6 | 6 | -| **TOTAL** | **1** | **18** | **68** | **87** | - ---- - -## HIGH Severity - -- [ ] **#53** Cross-file: Massive test helper duplication (~330 lines duplicated across 3 test files). `test_worldline_id`, `test_cursor_id`, `setup_worldline_with_ticks`, `create_add_node_patch`, etc. should be in `tests/common/mod.rs`. - ---- - -## MEDIUM Severity - -### Source Code - -- [ ] **#1** `playback.rs:314` — Long mid-function comment block contradicts itself ("Actually, let's clarify..."). Clean up or move to module-level docs. -- [ ] **#2** `playback.rs:394` — `StepForward` for writers returns `StepResult::Advanced` but does nothing (misleading stub). Should return `NoOp` or document clearly. -- [ ] **#3** `playback.rs:566` — `publish_truth` hash conversion is fragile (relies on `blake3::Hash` to `[u8;32]` via `into()`). Add explicit type annotation. -- [ ] **#4** `provenance_store.rs:204` — `add_checkpoint` silently no-ops if worldline doesn't exist. Should return error or log. -- [ ] **#5** `provenance_store.rs:189` — `append()` doesn't validate `global_tick` equals current length (gap risk). -- [ ] **#6** `retention.rs:47` — `ArchiveToWormhole` is "not implemented" but no compile-time warning when used. -- [ ] **#7** `frame_v2.rs:111` — `debug_assert!` for payload size check. Release builds silently produce invalid packets if payload exceeds `u32::MAX`. - -### Test Code - -- [ ] **#8** `view_session_tests.rs:713` — T16 tests conceptually belong in BOAW test file, not "view sessions". -- [ ] **#9** `view_session_tests.rs:726` — `make_touch_rule` closure duplicated between T16 and T16-shuffled (47 lines x 2). -- [ ] **#10** `view_session_tests.rs:873` — `XorShift64` + `shuffle` reimplemented inline (duplicates `common/mod.rs`). -- [ ] **#11** `outputs_playback_tests.rs:92` — `setup_worldline_with_ticks` duplicated verbatim across 3 files. -- [ ] **#12** `outputs_playback_tests.rs:698` — Direct field mutation `cursor.tick = 100` bypasses public API. -- [ ] **#13** `checkpoint_fork_tests.rs:59` — `create_add_node_patch` duplicated verbatim. -- [ ] **#14** `reducer_emission_tests.rs:1254` — `bus_log` is non-mut but calls `emit()`. Misleading if interior mutability. -- [ ] **#15** `view_session_tests.rs:317` — Helper functions block (~110 lines) duplicated across 3 test files. - -### Documentation - -- [ ] **#16** `architecture-outline.md:125` — Says "`TruthSink` trait" but it's actually a `struct`. -- [ ] **#17** `architecture-outline.md:128` — `RetentionPolicy` variants listed incorrectly (says "Archival", missing `CheckpointEvery`). -- [ ] **#18** `architecture-outline.md:121` — Potentially broken link path (`/spec/` vs relative). - ---- - -## LOW Severity - -### Source Code (LOW) - -- [ ] **#19** `playback.rs:264` — All `PlaybackCursor` fields are `pub` (risky for `store` field). -- [ ] **#20** `playback.rs:381` — Writer stub TODO not marked with `// TODO:` for grep-ability. -- [ ] **#21** `retention.rs:21` — Missing `#[non_exhaustive]` on `RetentionPolicy` enum. -- [ ] **#22** `worldline.rs:260` — `OutputFrameSet` type alias doesn't show docs in all IDE contexts. Consider newtype. -- [ ] **#23** `frame_v2.rs:149` — `decode_v2_packet` returns `Option` with no failure reason. Consider `Result<_, DecodeError>`. -- [ ] **#24** `frame_v2.rs:174` — Variable named `cursor` confusing given `CursorId` in crate. Rename to `offset`. -- [ ] **#25** `playback.rs:25` — `BTreeMap` imported at top but only used in `TruthSink`. Consider importing at point of use. -- [ ] **#26** `playback.rs:34` — `CursorId` and `SessionId` have identical `as_bytes` implementations. Consider macro/trait. -- [ ] **#27** `playback.rs:633` — `TruthSink::collect_frames` clones the entire Vec. Return `&[TruthFrame]` instead. -- [ ] **#28** `playback.rs:631` — Missing `#[must_use]` on `TruthSink::last_receipt`. -- [ ] **#29** `worldline.rs:145` — `#[allow(clippy::too_many_lines)]` on `apply_warp_op_to_store`. Consider refactoring. -- [ ] **#30** `worldline.rs:97` — Simple accessors (`global_tick()`, `policy_id()`) missing `#[inline]`. -- [ ] **#31** `worldline.rs:284` — `ApplyError::UnsupportedOperation` uses `&'static str`. Consider enum of op names. -- [ ] **#32** `provenance_store.rs:139` — `WorldlineHistory` is private but has doc comment. Consider removing. -- [ ] **#33** `provenance_store.rs:229` — `checkpoint()` does redundant `get_mut` after hash computation. -- [ ] **#34** `provenance_store.rs:254` — `#[allow(clippy::cast_possible_truncation)]` on `fork` needs safety comment. -- [ ] **#35** `provenance_store.rs:277` — Repeated `#[allow(clippy::cast_possible_truncation)]`. Consider module-level allow. -- [ ] **#36** `provenance_store.rs:317` — `checkpoint_before` returns `None` for non-existent worldline. Document behavior. -- [ ] **#37** `retention.rs:56` — `Default` impl could use `#[derive(Default)]` with `#[default]` attribute. -- [ ] **#38** `frame_v2.rs:102` — Multiple `#[allow(clippy::cast_possible_truncation)]` in `encode_v2_packet`. -- [ ] **#39** `frame_v2.rs:225` — `decode_v2_packets` creates subslice then re-checks length inside decode. Minor inefficiency. -- [ ] **#40** `playback.rs:559` — `publish_truth` error doc references `HistoryError` inconsistently. - -### Test Code (LOW) - -- [ ] **#41** `view_session_tests.rs:82` — Magic number `patch_digest: [tick as u8; 32]` wraps at tick > 255. -- [ ] **#42** `view_session_tests.rs:119` — Magic number `+100` offset for `commit_hash` unexplained. -- [ ] **#43** `view_session_tests.rs:145` — Magic number `10` for `pin_max_tick` not named. -- [ ] **#44** `view_session_tests.rs:719` — `WORKER_COUNTS` uses `[1,2,8,32]` vs `common` uses `[1,2,4,8,16,32]`. -- [ ] **#45** `view_session_tests.rs:232` — Loop count `5` is a magic number. -- [ ] **#46** `outputs_playback_tests.rs:3` — `#![allow(clippy::expect_fun_call)]` is file-wide. Scope to specific functions. -- [ ] **#47** `outputs_playback_tests.rs:427` — Magic number `k = 12u64` — why 12? -- [ ] **#48** `playback_cursor_tests.rs:21` — `test_cursor_id()` has different signature than other test files. Prevents extraction. -- [ ] **#49** `playback_cursor_tests.rs:256` — Unused variable `_hash_at_3` computed but never asserted. -- [ ] **#50** `playback_cursor_tests.rs:207` — "Tick 10 is valid" reasoning unclear. Document convention. -- [ ] **#51** `reducer_emission_tests.rs:29` — `key_sub as key` shadows 2-arg `key` function. Confusing. -- [ ] **#52** `reducer_emission_tests.rs:43` — `factorial` overflow guard uses `debug_assert!`. Use `assert!`. -- [ ] **#53** `reducer_emission_tests.rs:176` — Redundant re-assertion after loop (same check inside and after). -- [ ] **#54** `reducer_emission_tests.rs:539` — Double-finalization pattern (wasteful and confusing). -- [ ] **#55** `checkpoint_fork_tests.rs:9` — `#![allow(clippy::unwrap_used)]` is file-wide. -- [ ] **#56** `checkpoint_fork_tests.rs:135` — `cursor_tick = patch_index + 1` convention is fragile. -- [ ] **#57** Missing edge case tests: `pin_max_tick=0`, seek to `u64::MAX`, empty worldline, duplicate WorldlineId registration. -- [ ] **#58** `outputs_playback_tests.rs:623` — `unsubscribed_channel` variable name is redundant with test logic. - -### Documentation (LOW) - -- [ ] **#59** CHANGELOG claims "T19-T22" but these labels don't appear in test file names. -- [ ] **#60** `code-map.md` says "T1-T10 playback tests" but file has T1,T4,T5,T6,T7,T8 (not T2,T3,T9,T10). -- [ ] **#61** CHANGELOG `checkpoint()` description says "Create checkpoint" but function is `add_checkpoint`. -- [ ] **#62** CHANGELOG claims `WorldlineId` is "content-addressed" but tests use fixed bytes. - -### API Surface - -- [ ] **#63** `RetentionPolicy` exported but no public function accepts/returns it (dangling export). -- [ ] **#64** `apply_warp_op_to_store` exposes internal mutation without guardrails. -- [ ] **#65** `ApplyError` vs `ApplyResult` naming creates cognitive collision (different contexts). -- [ ] **#66** `compute_state_root_for_warp_store` newly public — low-level, easy to misuse. -- [ ] **#67** `CheckpointRef` exposed publicly but only meaningful in provenance context. -- [ ] **#68** `playback` module exports 11 types in a flat list. Consider sub-grouping in docs. - ---- - -## Recommended Fix Priority - -### P0 — Before PR - -- [ ] Fix #53 (HIGH): Extract shared test helpers to `tests/common/mod.rs` -- [ ] Fix #16-#18 (MEDIUM): Factual errors in `architecture-outline.md` -- [ ] Fix #9-#10 (MEDIUM): Use existing `common/` XorShift64/shuffle/make_touch_rule - -### P1 — Before Merge - -- [ ] Fix #1-#2 (MEDIUM): Clean up playback.rs stub and comments -- [ ] Fix #5 (MEDIUM): Add tick gap validation to `append()` -- [ ] Fix #7 (MEDIUM): Promote `debug_assert!` to runtime check in frame_v2 - -### P2 — Follow-up Issue - -- [ ] Fix #4 (MEDIUM): Error handling in `add_checkpoint` -- [ ] Fix #8 (MEDIUM): Move T16 to appropriate test file -- [ ] Fix #21 (LOW): Add `#[non_exhaustive]` to `RetentionPolicy` - -### P3 — Tech Debt - -- [ ] All remaining LOW severity items diff --git a/docs/archive/plans/cross-warp-parallelism.md b/docs/archive/plans/cross-warp-parallelism.md deleted file mode 100644 index 9ee8ff76..00000000 --- a/docs/archive/plans/cross-warp-parallelism.md +++ /dev/null @@ -1,106 +0,0 @@ - - - -# Cross-Warp Parallelism - -**Created:** 2026-01-20 -**Status:** IMPLEMENTED -**Archived:** 2026-03-07 (PR #292) -**Reason:** Feature fully implemented -**Implementation:** PR #257 (Phase 6B); see `crates/warp-core/src/boaw/exec.rs` -**Context:** Performance optimization — parallelize execution across warps - -> **Archival Note:** The implemented design deviates from this plan. The `WorkUnit` -> struct as built does NOT store an explicit `shard_id` field — shard identity is -> implicit in the items membership. See `crates/warp-core/src/boaw/exec.rs:259-281` -> for the actual structure. This document preserves the original planning intent. - ---- - -## Problem Statement - -In `engine_impl.rs:1220`, warps are processed serially: - -```rust -for (warp_id, warp_rewrites) in by_warp { - let view = GraphView::new(store); // borrows per-warp store - let deltas = execute_parallel_sharded(view, &items, workers); - all_deltas.extend(deltas); -} -``` - -While `execute_parallel_sharded()` parallelizes _within_ each warp, multi-warp ticks -still execute warp-by-warp. With N warps and S shards each, latency is O(N) rather -than O(1) when parallelism is available. - ---- - -## Recommended Approach - -**Global work queue of `(warp_id, shard_id)` units** — flat parallelism, no nesting. - -1. **Partition rewrites by warp** — group by `WarpId` -2. **Within each warp, partition into shards** — reuse existing `shard_of()` (256 shards) -3. **Build work units** — `WorkUnit { warp_id, items: Vec }` _(shard_id not stored; implicit in items membership)_ -4. **Spawn fixed worker pool** — `available_parallelism()` threads, spawned once -5. **Atomic work claiming** — workers claim next unit via `AtomicUsize` index -6. **Execute with warp-local view** — each unit resolves its warp's `GraphView` - -**Pros:** Scalable, clean, deterministic (canonical merge order), no API churn. -**Cons:** Slightly more wiring than per-warp threading, but avoids nested spawns. - ---- - -## Constraints (Non-Negotiable) - -1. **No nested threading** — `execute_work_queue()` is the _only_ spawn site. Units - call serial execution internally, never `execute_parallel_sharded()`. - -2. **No long-lived borrows across warps** — worker loop must: resolve `GraphView`, - execute unit, drop view, move on. No caching `&GraphStore` across iterations. - -3. **Keep `ExecItem` unchanged** — `WorkUnit` carries `warp_id + Vec`. - Do not widen `ExecItem`'s API surface. - ---- - -## Implementation Steps - -| Step | Description | Files | -| ---- | ------------------------------------------------------ | -------------- | -| 1 | Add `WorkUnit { warp_id, items }` struct (no shard_id) | exec.rs | -| 2 | Add `build_work_units()` — partition by warp + shard | exec.rs | -| 3 | Add `execute_work_queue()` — atomic claim loop | exec.rs | -| 4 | Replace serial for-loop with `execute_work_queue()` | engine_impl.rs | -| 5 | Add `#[cfg(feature = "cross-warp-parallel")]` gate | Cargo.toml | - ---- - -## Files Modified - -| File | Change | -| ------------------------------------- | ------------------------------------------- | -| `crates/warp-core/src/boaw/exec.rs` | WorkUnit struct, build_work_units, executor | -| `crates/warp-core/src/engine_impl.rs` | Replace serial loop with work queue call | -| `crates/warp-core/Cargo.toml` | Feature gate (optional) | - ---- - -## Success Criteria - -- [x] Multi-warp tick executes all warp-shards concurrently -- [x] Fixed worker pool (no nested spawning) -- [x] Determinism preserved (canonical unit ordering + merge) -- [x] No regression on single-warp benchmarks - ---- - -## Minimal Success Test - -Integration test proving correctness: - -- **Setup:** 2 warps × many shards (e.g., 100 items per warp) -- **Worker counts:** `{1, 2, 8, 32}` — all must produce identical results -- **Assertion:** Same `commit_hash` per warp (or engine receipt hash) across all runs - -If this passes, the design is correct. diff --git a/docs/archive/plans/per-warp-time-sovereignty.md b/docs/archive/plans/per-warp-time-sovereignty.md deleted file mode 100644 index a76eed26..00000000 --- a/docs/archive/plans/per-warp-time-sovereignty.md +++ /dev/null @@ -1,852 +0,0 @@ - - - -# Per-Warp Time Sovereignty - -**Status:** Draft -**Created:** 2026-01-20 -**Target:** Phase 7 (Post-BOAW) -**Authors:** Claude (research agent) - -## Overview - -This plan defines how different WARPs can exist at different "now" positions within the same Engine step, safely and deterministically. This enables: - -- **Warp A** in LIVE mode (advancing tick frontier, ingesting new intents) -- **Warp B** in REPLAY mode (replaying historical commits or applying recorded tick patches) -- **Warp C** in PAUSED mode (no-op, frozen in time) - -All executing concurrently within one Engine step call. - ---- - -## 1. Current State - -### What Exists Today - -| Component | Location | Current Capability | -| ------------------------ | -------------------------- | ----------------------------------------------------------------------------------- | -| **WarpState** | `warp_state.rs:43-46` | `BTreeMap` - per-warp isolation via separate stores | -| **WorkUnit** | `boaw/exec.rs:149-159` | Carries `warp_id` explicitly - work units are warp-tagged | -| **execute_work_queue()** | `boaw/exec.rs:192-282` | Resolves `GraphView` per-unit from correct store via `resolve_store(&unit.warp_id)` | -| **tick_history** | `engine_impl.rs:424` | `Vec<(Snapshot, TickReceipt, WarpTickPatchV1)>` - **engine-global**, not per-warp | -| **jump_to_tick()** | `engine_impl.rs:1581-1601` | Replays patches sequentially, but operates on **whole engine** | -| **WarpTickPatchV1** | `tick_patch.rs:324-461` | `apply_to_state()` applies canonical ops to WarpState | -| **Footprint isolation** | `scheduler.rs:162-222` | Keys include `warp_id` - cross-warp conflicts impossible by design | -| **Commit DAG** | `engine_impl.rs:1052-1056` | **Single linear chain** - parents from `last_snapshot` (global) | - -### What's Missing - -1. **No `WarpRunMode`** - no enum for LIVE/REPLAY/PAUSED -2. **No per-warp timeline** - tick history is engine-global -3. **No warp-local "now"** - no tracking of each warp's position in its timeline -4. **No mode-aware scheduling** - work queue doesn't filter by mode -5. **No REPLAY intent rejection** - no mechanism to block new intents for replaying warps -6. **No per-warp commit DAG** - single chain, not per-warp branches - ---- - -## 2. Constraints & Invariants - -### Non-Negotiable (Compile-Time or Hard Runtime Errors) - -| ID | Invariant | Enforcement | -| ------------------- | -------------------------------------------------------------------------------------------------- | ---------------------------------------- | -| **REPLAY-001** | REPLAY warps MUST NOT ingest new intents | Runtime check at `ingest_intent()` entry | -| **REPLAY-002** | REPLAY warps MUST only apply recorded patches (not execute rules) | Mode branch in step | -| **REPLAY-003** | All hashes (`commit_hash`, `patch_digest`, `state_root`) MUST match recorded history byte-for-byte | Post-apply verification | -| **REPLAY-004** | REPLAY execution MUST NOT depend on wall clock, random, or nondet | ADR-0006 ban list | -| **LIVE-001** | LIVE warps MAY ingest new intents | Default behavior | -| **LIVE-002** | LIVE execution deterministic given ingress | Existing guarantee | -| **LIVE-003** | LIVE warps MUST NOT read/write other warps' state | WarpId-scoped keys | -| **ISOLATION-001** | Each warp's timeline is independent | Per-warp `tick_history` | -| **ISOLATION-002** | No cross-warp `GraphView` aliasing during parallel execution | Per-unit resolution | -| **DETERMINISM-001** | Mixed-mode execution produces deterministic per-warp commit DAGs | Canonical merge | - -### Soft Invariants (Debug Assertions, Upgradable) - -| ID | Invariant | Enforcement | -| -------------- | ------------------------------------------ | --------------------- | -| **PAUSED-001** | PAUSED warps produce zero work units | Mode filter in build | -| **MODE-001** | Mode transitions are explicit and recorded | API enforcement | -| **REPLAY-005** | REPLAY completion triggers mode transition | Configurable callback | - ---- - -## 3. Design - -### 3.1 Warp-Local "Now" Definition - -"Now" is not wall-clock time but a **position in the warp's commit DAG**. - -```rust -/// The temporal position of a single warp within its own timeline. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct WarpNow { - /// The warp this position belongs to. - pub warp_id: WarpId, - /// Current tick index (0 = initial state U0, 1 = after first commit, etc.) - pub tick_index: u64, - /// The commit hash at this position (None for U0). - pub commit_hash: Option, - /// Current execution mode. - pub mode: WarpRunMode, -} -``` - -**Location**: `crates/warp-core/src/warp_timeline.rs` - -For a linear chain: `(warp_id, tick_index)` uniquely identifies the state. -For future branching: `(warp_id, commit_hash)` would be the canonical form. - -### 3.2 WarpRunMode Model - -```rust -/// Execution mode for a warp within an Engine step. -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum WarpRunMode { - /// Normal operation: new intents allowed, rules execute, commits advance frontier. - Live, - - /// Replaying recorded history: no new intents, only apply recorded patches. - Replay { - /// Target tick index to replay to (post-apply tick_index; patches 0..target_tick-1). - target_tick: u64, - /// Source of recorded patches for verification. - source: ReplaySource, - }, - - /// No-op: warp is excluded from this step entirely. - Paused, - - /// (Future) Forking: create a new timeline branch from current position. - #[non_exhaustive] - _Reserved, -} - -/// Source of recorded patches for REPLAY mode. -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum ReplaySource { - /// Replay from engine's own ledger (local tick_history). - LocalLedger, - /// Replay from external patches (e.g., received from network peer). - External(Vec), -} -``` - -**Design rationale**: Modes are **per-warp, not per-engine**. This allows warp A to advance (LIVE) while warp B replays (REPLAY) in the same `Engine.step()` call. - -### 3.3 Per-Warp Timeline Structure - -```rust -/// Timeline state for a single warp. -#[derive(Clone, Debug)] -pub struct WarpTimeline { - /// Warp identifier. - pub warp_id: WarpId, - /// Current execution mode. - pub mode: WarpRunMode, - /// Complete tick history for this warp. - pub tick_history: Vec<(Snapshot, TickReceipt, WarpTickPatchV1)>, - /// Most recent snapshot (tip of the DAG). - pub last_snapshot: Option, - /// Initial state for replay (U0 for this warp). - pub initial_store: GraphStore, - /// Current position in timeline. - pub now: WarpNow, -} - -impl WarpTimeline { - /// Get the current tick index (0 = U0, n = after n commits). - pub fn tick_index(&self) -> u64 { - self.tick_history.len() as u64 - } - - /// Check if this warp can accept new intents. - pub fn can_ingest(&self) -> bool { - matches!(self.mode, WarpRunMode::Live) - } - - /// Get recorded patch at index (for REPLAY verification). - pub fn recorded_patch(&self, index: u64) -> Option<&WarpTickPatchV1> { - self.tick_history.get(index as usize).map(|(_, _, p)| p) - } -} -``` - -### 3.4 REPLAY Invariants Enforcement - -```rust -impl WarpTimeline { - /// Apply a replay step: verify and apply recorded patch. - pub fn replay_step( - &mut self, - store: &mut GraphStore, - ) -> Result { - let WarpRunMode::Replay { target_tick, ref source } = self.mode else { - return Err(ReplayError::NotInReplayMode); - }; - - let current_tick = self.tick_index(); - // target_tick is the desired post-apply tick_index (number of patches applied). - // tick_index 0 = initial state; tick_index N = state after patches 0..N-1. - // So when current_tick >= target_tick, patches 0..target_tick-1 have been applied. - if current_tick >= target_tick { - return Ok(ReplayStepResult::ReplayComplete); - } - - // Get recorded patch - let recorded = match source { - ReplaySource::LocalLedger => { - self.recorded_patch(current_tick) - .ok_or(ReplayError::MissingRecordedPatch { tick: current_tick })? - .clone() - } - ReplaySource::External(patches) => { - patches.get(current_tick as usize) - .ok_or(ReplayError::MissingRecordedPatch { tick: current_tick })? - .clone() - } - }; - - // Apply patch (no rule execution!) - recorded.apply_to_store(store)?; - - // Verify post-state matches recorded (REPLAY-003) - let post_state_root = compute_state_root_for_warp(store, &self.warp_id); - let (recorded_snapshot, _, _) = &self.tick_history[current_tick as usize]; - - if post_state_root != recorded_snapshot.state_root { - return Err(ReplayError::StateRootMismatch { - tick: current_tick, - expected: recorded_snapshot.state_root, - actual: post_state_root, - }); - } - - // Advance timeline position - self.now.tick_index = current_tick + 1; - self.now.commit_hash = Some(recorded_snapshot.hash); - - Ok(ReplayStepResult::Advanced { tick: current_tick + 1 }) - } -} - -#[derive(Debug)] -pub enum ReplayError { - NotInReplayMode, - MissingRecordedPatch { tick: u64 }, - StateRootMismatch { tick: u64, expected: Hash, actual: Hash }, - PatchDigestMismatch { tick: u64, expected: Hash, actual: Hash }, - CommitHashMismatch { tick: u64, expected: Hash, actual: Hash }, -} -``` - -### 3.5 LIVE Invariants Enforcement - -```rust -impl Engine { - /// Ingest intent with mode check (LIVE-001 enforced). - pub fn ingest_intent_for_warp( - &mut self, - warp_id: &WarpId, - intent_bytes: &[u8], - ) -> Result { - let timeline = self.timelines.get(warp_id) - .ok_or(EngineError::UnknownWarp(*warp_id))?; - - // REPLAY-001: Reject intents for non-LIVE warps - if !timeline.can_ingest() { - return Err(EngineError::WarpNotAcceptingIntents { - warp_id: *warp_id, - mode: timeline.mode.clone(), - }); - } - - // Proceed with normal ingestion - self.ingest_intent_impl(warp_id, intent_bytes) - } -} -``` - -### 3.6 Concurrency Safety Matrix - -| Data | Sharing Model | Rationale | -| ----------------------- | ---------------- | --------------------------------------------- | -| `GraphStore` per warp | **Isolated** | Each warp has own store in `WarpState.stores` | -| `WarpTimeline` per warp | **Isolated** | Each warp has own timeline, mode, history | -| `WorkUnit` | **Read-shared** | Built before execution, immutable during | -| `TickDelta` per worker | **Thread-local** | Each worker accumulates own delta | -| `RewriteRule` registry | **Read-shared** | Rules immutable after registration | -| Atomic work counter | **Shared** | `AtomicUsize` for work-stealing | -| Engine metadata | **Isolated** | Only one `&mut Engine` exists | - -**Key guarantee**: During `execute_work_queue()`, each worker: - -1. Claims a `WorkUnit` atomically -2. Resolves `GraphView` from correct warp's store (read-only) -3. Writes to thread-local `TickDelta` -4. Never touches another warp's store - -### 3.7 Global Work Queue with Mixed Modes - -```rust -/// Build work units respecting per-warp modes. -pub fn build_mixed_mode_work_units( - timelines: &BTreeMap, - live_rewrites: &BTreeMap>, -) -> MixedModeWorkPlan { - let mut live_units = Vec::new(); - let mut replay_warps = Vec::new(); - let mut paused_warps = Vec::new(); - - for (warp_id, timeline) in timelines { - match &timeline.mode { - WarpRunMode::Live => { - // Build work units for LIVE warps (normal path) - if let Some(rewrites) = live_rewrites.get(warp_id) { - let items: Vec = rewrites.iter() - .map(|(rw, exec)| ExecItem { - exec: *exec, - scope: rw.scope.local_id, - origin: OpOrigin::default(), - }) - .collect(); - let sharded = partition_into_shards(&items); - for shard in sharded { - if !shard.items.is_empty() { - live_units.push(WorkUnit { - warp_id: *warp_id, - items: shard.items, - }); - } - } - } - } - WarpRunMode::Replay { .. } => { - replay_warps.push(*warp_id); - } - WarpRunMode::Paused => { - paused_warps.push(*warp_id); - } - WarpRunMode::_Reserved => unreachable!(), - } - } - - MixedModeWorkPlan { - live_units, - replay_warps, - paused_warps, - } -} - -pub struct MixedModeWorkPlan { - /// Work units for LIVE warps (rule execution). - pub live_units: Vec, - /// Warps in REPLAY mode (will apply recorded patches). - pub replay_warps: Vec, - /// Warps in PAUSED mode (no-op). - pub paused_warps: Vec, -} -``` - -**Key insight**: REPLAY warps don't produce `ExecItem` work units because they don't execute rules - they apply pre-recorded patches. PAUSED warps produce nothing. Only LIVE warps generate rule execution work. - -### 3.8 Preventing Cross-Mode Contamination - -```rust -impl Engine { - pub fn step_mixed_mode(&mut self) -> Result { - // 1. Build mode-aware work plan - let plan = build_mixed_mode_work_units(&self.timelines, &self.pending_by_warp); - - // 2. Execute LIVE warps (parallel rule execution) - let live_deltas = if !plan.live_units.is_empty() { - execute_work_queue(&plan.live_units, self.worker_count, |warp_id| { - self.state.store(warp_id) - }) - } else { - Vec::new() - }; - - // 3. Merge and commit LIVE deltas (per-warp) - let mut live_results = BTreeMap::new(); - for warp_id in plan.live_units.iter().map(|u| u.warp_id).collect::>() { - let warp_delta = self.extract_warp_delta(&live_deltas, &warp_id); - let result = self.commit_warp(&warp_id, warp_delta)?; - live_results.insert(warp_id, result); - } - - // 4. Execute REPLAY warps (apply recorded patches, verify hashes) - let mut replay_results = BTreeMap::new(); - for warp_id in &plan.replay_warps { - let timeline = self.timelines.get_mut(warp_id).unwrap(); - let store = self.state.store_mut(warp_id).unwrap(); - let result = timeline.replay_step(store)?; - replay_results.insert(*warp_id, result); - } - - // 5. PAUSED warps: no-op - - Ok(MixedModeStepResult { - live_results, - replay_results, - paused: plan.paused_warps, - }) - } -} -``` - -### 3.9 Per-Warp Commit with Isolated Timeline - -```rust -impl Engine { - fn commit_warp( - &mut self, - warp_id: &WarpId, - delta: TickDelta, - ) -> Result { - let timeline = self.timelines.get_mut(warp_id) - .ok_or(EngineError::UnknownWarp(*warp_id))?; - let store = self.state.store_mut(warp_id) - .ok_or(EngineError::UnknownWarp(*warp_id))?; - - // Build patch from delta - let patch = WarpTickPatchV1::from_delta(delta, self.policy_id)?; - - // Apply patch - patch.apply_to_store(store)?; - - // Compute hashes - let state_root = compute_state_root_for_warp(store, warp_id); - let patch_digest = patch.digest(); - let parents = timeline.last_snapshot - .as_ref() - .map(|s| vec![s.hash]) - .unwrap_or_default(); - let commit_hash = compute_commit_hash_v2( - state_root, - &parents, - patch_digest, - self.policy_id, - ); - - // Build snapshot - let snapshot = Snapshot { - warp_id: *warp_id, - hash: commit_hash, - state_root, - patch_digest, - parents, - // ... other fields - }; - - // Record in warp's timeline (NOT global!) - let receipt = self.build_receipt_for_warp(warp_id)?; - timeline.tick_history.push((snapshot.clone(), receipt, patch)); - timeline.last_snapshot = Some(snapshot.clone()); - timeline.now.tick_index += 1; - timeline.now.commit_hash = Some(commit_hash); - - Ok(WarpCommitResult { - snapshot, - tick_index: timeline.now.tick_index, - }) - } -} -``` - ---- - -## 4. Implementation Plan - -### Phase 1: Core Types (1 commit) - -**Files to create/modify:** - -| Action | File | Changes | -| ---------- | --------------------------------------- | ----------------------------------------------------------------------- | -| **NEW** | `crates/warp-core/src/warp_timeline.rs` | `WarpNow`, `WarpRunMode`, `ReplaySource`, `WarpTimeline`, `ReplayError` | -| **MODIFY** | `crates/warp-core/src/lib.rs` | Export new module | - -**Tests**: Unit tests for `WarpRunMode` transitions, `WarpTimeline` basic ops. - -### Phase 2: Per-Warp Timeline Storage (1 commit) - -**Files to modify:** - -| Action | File | Changes | -| ---------- | ------------------------------------- | ----------------------------------------------------------------------------------------- | -| **MODIFY** | `crates/warp-core/src/engine_impl.rs` | Add `timelines: BTreeMap` field; migrate `tick_history` to per-warp | -| **MODIFY** | `crates/warp-core/src/warp_state.rs` | Add `timeline()` accessor | - -**Tests**: Verify existing tests pass with new storage layout. - -### Phase 3: Mode-Aware Intent Ingestion (1 commit) - -**Files to modify:** - -| Action | File | Changes | -| ---------- | ------------------------------------- | ---------------------------------------------------------------------------------------------- | -| **MODIFY** | `crates/warp-core/src/engine_impl.rs` | Check `timeline.can_ingest()` in `ingest_intent()`; add `EngineError::WarpNotAcceptingIntents` | - -**Tests**: - -- `test_replay_warp_rejects_new_intents` -- `test_paused_warp_rejects_new_intents` -- `test_live_warp_accepts_intents` - -### Phase 4: Mode-Aware Work Queue (1 commit) - -**Files to modify:** - -| Action | File | Changes | -| ---------- | ------------------------------------- | -------------------------------------------------------- | -| **MODIFY** | `crates/warp-core/src/boaw/exec.rs` | Add `MixedModeWorkPlan`, `build_mixed_mode_work_units()` | -| **MODIFY** | `crates/warp-core/src/engine_impl.rs` | Implement `step_mixed_mode()` | - -**Tests**: - -- `test_live_warp_generates_work_units` -- `test_replay_warp_no_work_units` -- `test_paused_warp_no_work_units` - -### Phase 5: REPLAY Patch Application (1 commit) - -**Files to modify:** - -| Action | File | Changes | -| ---------- | --------------------------------------- | ---------------------------------------------------------- | -| **MODIFY** | `crates/warp-core/src/warp_timeline.rs` | Implement `WarpTimeline::replay_step()`, hash verification | -| **MODIFY** | `crates/warp-core/src/tick_patch.rs` | Add `apply_to_store()` (single-warp variant) | - -**Tests**: - -- `test_replay_applies_recorded_patches` -- `test_replay_detects_state_root_mismatch` -- `test_replay_detects_commit_hash_mismatch` - -### Phase 6: Per-Warp Commit (1 commit) - -**Files to modify:** - -| Action | File | Changes | -| ---------- | ------------------------------------- | ----------------------------------------------------- | -| **MODIFY** | `crates/warp-core/src/engine_impl.rs` | Implement `commit_warp()`, per-warp snapshot creation | -| **MODIFY** | `crates/warp-core/src/snapshot.rs` | Add `warp_id` to `Snapshot` (or make warp-scoped) | - -**Tests**: - -- `test_commit_advances_warp_timeline` -- `test_commit_hash_deterministic_per_warp` - -### Phase 7: Engine API Surface (1 commit) - -**Files to modify:** - -| Action | File | Changes | -| ---------- | ------------------------------------- | --------------------------------------------------------- | -| **MODIFY** | `crates/warp-core/src/engine_impl.rs` | Add `set_warp_mode()`, `get_warp_now()`, `start_replay()` | - -**Tests**: - -- `test_set_warp_mode_live_to_replay` -- `test_set_warp_mode_replay_to_paused` -- `test_start_replay_from_tick_zero` - -### Phase 8: Integration Tests (1 commit) - -**Files to create:** - -| Action | File | Changes | -| ------- | ------------------------------------------------- | --------------------------- | -| **NEW** | `crates/warp-core/tests/warp_time_sovereignty.rs` | Full integration test suite | - ---- - -## 5. Test Plan - -### File: `crates/warp-core/tests/warp_time_sovereignty.rs` - -| Test ID | Name | Description | -| ------- | ------------------------------------------- | ---------------------------------------------------------------------------------------- | -| **T1** | `test_live_and_replay_concurrent_isolation` | LIVE warp advances while REPLAY warp replays; neither affects the other | -| **T2** | `test_replay_hash_chain_identity` | REPLAY produces identical `commit_hash` chains to recorded history (100 ticks, 10 seeds) | -| **T3** | `test_live_worker_invariance_with_replay` | LIVE worker-count invariance holds during concurrent REPLAY | -| **T4** | `test_replay_rejects_intents` | REPLAY warp returns `Err(WarpNotAcceptingIntents)` on intent ingestion | -| **T5** | `test_mixed_mode_work_queue_determinism` | Mixed-mode execution deterministic across 50 shuffled ingress orderings | -| **T6** | `test_replay_tripwire_nondet_injection` | **Tripwire**: fails if any nondet input leaks into REPLAY mode | -| **T7** | `test_replay_completion_mode_transition` | REPLAY completion transitions mode to PAUSED (or LIVE if configured) | -| **T8** | `test_multiple_replay_warps_isolation` | Multiple REPLAY warps don't interfere with each other | -| **T9** | `test_cross_mode_commit_dag_independence` | LIVE and REPLAY warps have completely independent commit DAGs | -| **T10** | `test_paused_warp_state_immutable` | PAUSED warp state completely unchanged across 10 engine steps | - -### Test Implementation Details - -```rust -/// T1: LIVE warp advances while REPLAY warp replays - mutual isolation. -#[test] -fn test_live_and_replay_concurrent_isolation() { - // Setup: Engine with warp_a (LIVE) and warp_b (REPLAY) - // 1. Record 10 ticks for warp_b - // 2. Rewind warp_b to tick 0, set REPLAY mode targeting tick 5 - // 3. Set warp_a to LIVE - // 4. Execute 5 mixed-mode steps - // Assert: warp_a advanced 5 ticks, warp_b replayed to tick 5 - // Assert: warp_a's commit hashes are new, warp_b's match recorded history - // Assert: Neither warp's state was corrupted by the other -} - -/// T6: Tripwire - nondet leak into REPLAY fails. -#[test] -fn test_replay_tripwire_nondet_injection() { - // Setup: Custom rule that attempts to inject nondet (e.g., thread::current().id()) - // Record with clean rule, then replay - // Assert: If any nondet leaks, state_root mismatch detected - // This test FAILS if nondet enters replay path - that's the tripwire -} -``` - -### Additional Test Files - -| File | Purpose | -| ---------------------------------------------- | ------------------------------------------------------------- | -| `crates/warp-core/tests/replay_determinism.rs` | Permutation tests (100+ seeds), cross-platform verification | -| `crates/warp-core/tests/mode_transitions.rs` | Valid/invalid mode transitions, transition during active step | - ---- - -## 6. Engine API Surface - -### New Methods - -```rust -impl Engine { - /// Set the execution mode for a warp. - /// - /// # Errors - /// - `UnknownWarp` if warp_id not found - /// - `InvalidModeTransition` if transition not allowed (e.g., during active step) - pub fn set_warp_mode( - &mut self, - warp_id: WarpId, - mode: WarpRunMode - ) -> Result<(), EngineError>; - - /// Get the current temporal position of a warp. - pub fn get_warp_now(&self, warp_id: &WarpId) -> Option<&WarpNow>; - - /// Start replay for a warp from its current position to target_tick. - /// - /// This is a convenience wrapper that: - /// 1. Validates target_tick is in recorded history - /// 2. Sets mode to Replay { target_tick, source: LocalLedger } - pub fn start_replay( - &mut self, - warp_id: WarpId, - target_tick: u64 - ) -> Result<(), EngineError>; - - /// Start replay from external patches (e.g., received from network). - pub fn start_replay_external( - &mut self, - warp_id: WarpId, - patches: Vec, - ) -> Result<(), EngineError>; - - /// Execute one engine step with mixed-mode support. - pub fn step_mixed_mode(&mut self) -> Result; - - /// Get all warps and their current modes. - pub fn warp_modes(&self) -> impl Iterator; -} -``` - -### Result Types - -```rust -pub struct MixedModeStepResult { - /// Results for warps that were in LIVE mode. - pub live_results: BTreeMap, - /// Results for warps that were in REPLAY mode. - pub replay_results: BTreeMap, - /// Warps that were PAUSED (no-op). - pub paused: Vec, -} - -pub struct WarpCommitResult { - pub snapshot: Snapshot, - pub tick_index: u64, -} - -pub enum ReplayStepResult { - /// Advanced to the next tick. - Advanced { tick: u64 }, - /// Reached target_tick, replay complete. - ReplayComplete, -} -``` - ---- - -## 7. Scheduling Rules - -### Which Warps Run - -1. **LIVE warps**: Generate work units if they have pending rewrites -2. **REPLAY warps**: Apply one recorded patch per step (no work units) -3. **PAUSED warps**: Skipped entirely (no state change) - -### Mode Determination Per Step - -```text -For each warp in canonical order (BTreeMap iteration): - match warp.mode: - Live → - if has_pending_rewrites(warp): - generate WorkUnits for this warp - else: - no-op this step - - Replay { target_tick, source } → - if warp.tick_index < target_tick: - schedule replay_step for this warp - else: - replay complete, transition to PAUSED (or callback) - - Paused → - no-op -``` - -### Work Queue Execution Order - -1. All LIVE work units execute in parallel (existing `execute_work_queue`) -2. LIVE deltas merged and committed per-warp -3. REPLAY warps apply patches sequentially (per-warp, can be parallelized across warps) -4. PAUSED warps skipped - ---- - -## 8. Time Travel / Rewind / Replay Selection - -### Required Inputs for REPLAY - -| Input | Source | Required | -| ------------------ | ------------------------------------------------- | -------- | -| `warp_id` | Caller | Yes | -| `target_tick` | Caller | Yes | -| `ReplaySource` | Caller chooses | Yes | -| Recorded patches | `LocalLedger` or `External(Vec)` | Yes | -| Initial state (U0) | Stored in `WarpTimeline.initial_store` | Auto | - -### Rewind Mechanism - -```rust -impl WarpTimeline { - /// Rewind warp to tick 0 (U0 state) for replay. - pub fn rewind_to_origin(&mut self, store: &mut GraphStore) { - // Clone initial state back to active store - *store = self.initial_store.clone(); - self.now.tick_index = 0; - self.now.commit_hash = None; - // Note: tick_history preserved for replay source - } - - /// Rewind to specific tick (requires re-applying patches 0..tick). - pub fn rewind_to_tick( - &mut self, - store: &mut GraphStore, - tick: u64 - ) -> Result<(), ReplayError> { - self.rewind_to_origin(store); - for i in 0..tick { - let patch = self.recorded_patch(i) - .ok_or(ReplayError::MissingRecordedPatch { tick: i })?; - patch.apply_to_store(store)?; - } - self.now.tick_index = tick; - self.now.commit_hash = if tick == 0 { - None - } else { - self.tick_history.get(tick as usize - 1) - .map(|(s, _, _)| s.hash) - }; - Ok(()) - } -} -``` - ---- - -## 9. Risks & Mitigations - -| Risk | Severity | Mitigation | -| ----------------------------------------------------- | ------------ | ------------------------------------------------------------------------------------- | -| **Per-warp timeline storage increases memory** | Medium | Use structural sharing for `GraphStore` snapshots; only store deltas | -| **REPLAY hash mismatch debugging is hard** | Medium | Include tick index, expected vs actual hashes, and delta dump in `ReplayError` | -| **Mode transition race conditions** | Low | Mode changes only allowed between steps; enforce via `&mut Engine` | -| **Future forking complicates commit DAG** | Low (future) | Design `parents: Vec` now; collapse/merge is separate feature | -| **Cross-warp portal operations during mixed modes** | Medium | Portal creation in LIVE warp that targets REPLAY warp must be blocked; add validation | -| **Global `policy_id` shared across warps** | Low | Acceptable for now; future per-warp policy is out of scope | -| **REPLAY from external source has no chain of trust** | Medium | External `ReplaySource` should require signature verification (future enhancement) | - ---- - -## 10. Out of Scope (Future Work) - -1. **Per-warp forking/branching** - Commit DAG supports multiple parents but implementation deferred -2. **Collapse/merge across warps** - ADR-0007 Layer 7 specified but not implemented here -3. **Privacy mode per-warp** - Mind vs Diagnostics modes are engine-global currently -4. **Network-sourced REPLAY verification** - Signature verification for `ReplaySource::External` -5. **Per-warp policy_id** - All warps share engine's `policy_id` - ---- - -## 11. Summary - -Per-warp time sovereignty is achievable with **minimal, composable changes** because the existing architecture already enforces per-warp isolation via: - -- `WarpId`-scoped keys in footprints -- Per-unit `GraphView` resolution in `execute_work_queue()` -- Separate `GraphStore` per warp in `WarpState` - -The main additions are: - -1. **WarpRunMode enum** - explicit mode tracking per warp -2. **Per-warp timeline storage** - migrate global `tick_history` to per-warp -3. **Mode-aware scheduling** - filter work queue by mode -4. **REPLAY enforcement** - apply recorded patches instead of executing rules - -The design **preserves all existing determinism guarantees** and **does not block future forking/collapse features**. - ---- - -## Appendix A: File Change Summary - -| File | Action | LOC Estimate | -| ------------------------------------------------- | ------- | ------------ | -| `crates/warp-core/src/warp_timeline.rs` | **NEW** | ~300 | -| `crates/warp-core/src/lib.rs` | MODIFY | +5 | -| `crates/warp-core/src/engine_impl.rs` | MODIFY | +200 | -| `crates/warp-core/src/boaw/exec.rs` | MODIFY | +50 | -| `crates/warp-core/src/tick_patch.rs` | MODIFY | +30 | -| `crates/warp-core/src/snapshot.rs` | MODIFY | +10 | -| `crates/warp-core/src/warp_state.rs` | MODIFY | +20 | -| `crates/warp-core/tests/warp_time_sovereignty.rs` | **NEW** | ~400 | -| `crates/warp-core/tests/replay_determinism.rs` | **NEW** | ~200 | -| `crates/warp-core/tests/mode_transitions.rs` | **NEW** | ~150 | -| **Total** | | ~1365 | - ---- - -## Appendix B: Compile-Time vs Runtime Enforcement - -| Invariant | Enforcement | Mechanism | -| --------------------------- | ---------------- | ------------------------------------------- | -| REPLAY-001 (no intents) | **Runtime** | Check in `ingest_intent()` | -| REPLAY-002 (patches only) | **Runtime** | Mode branch in `step_mixed_mode()` | -| REPLAY-003 (hash match) | **Runtime** | Post-apply verification | -| REPLAY-004 (no nondet) | **Compile-time** | ADR-0006 ban list + `ban-nondeterminism.sh` | -| LIVE-003 (no cross-warp) | **Compile-time** | `WarpId` in all key types | -| ISOLATION-002 (no aliasing) | **Runtime** | Per-unit `GraphView` resolution | -| DETERMINISM-001 | **Runtime** | Canonical merge in `merge_deltas()` | diff --git a/docs/archive/release-criteria.md b/docs/archive/release-criteria.md deleted file mode 100644 index 817f6b67..00000000 --- a/docs/archive/release-criteria.md +++ /dev/null @@ -1,36 +0,0 @@ - - - -# Release Criteria — Phase 0.5 → Phase 1 - -Checklist for closing Phase 0.5 and starting Phase 1 implementation. - -## How to Use This Checklist - -- Treat each item as a gate: “done” means it is implemented **and** verified. -- Link evidence (tests, docs, or CI runs) in the Phase 0.5 tracking issue. -- If a requirement moves, update the checklist so it stays authoritative. - -## Required Criteria - -- [ ] Branch tree spec v0.5 implemented (roaring bitmaps, epochs, hashing). -- [ ] Codex’s Baby Phase 0.5 features implemented (event envelope, bridge, backpressure). -- [ ] Temporal bridge integrated with branch tree and CB. -- [ ] Serialization protocol implemented with content-addressed blocks. -- [ ] Replay CLI (`echo replay --verify`) passes golden hash suite. -- [ ] Entropy observers and inspector packets verified. -- [ ] Capability tokens and security envelopes enforced. -- [ ] Determinism test suite green on Node, Chromium, WebKit. -- [ ] Deterministic config loader produces `configHash`. -- [ ] Plugin manifest loader validates capabilities and records `pluginsManifestHash`. -- [ ] Inspector JSONL writer produces canonical frames. -- [ ] Documentation index current (spec map). - -## Evidence Expectations (Examples) - -- Determinism suite: CI logs or `echo-dind-harness` transcript. -- Replay CLI: golden hashes checked in `testdata/` with a reproducible runner. -- Protocol gates: a spec doc + a passing conformance test. -- Docs: `docs/meta/docs-index.md` updated with links to current specs. - -Once all items checked, open Phase 1 milestone and migrate outstanding tasks to implementation backlog. diff --git a/docs/archive/rfc/mat-bus-finish.md b/docs/archive/rfc/mat-bus-finish.md deleted file mode 100644 index 1202acd4..00000000 --- a/docs/archive/rfc/mat-bus-finish.md +++ /dev/null @@ -1,662 +0,0 @@ - - - - -# RFC: MaterializationBus Completion - -**Status:** Complete -**Date:** 2026-01-17 -**Branch:** `materialization-bus` -**Depends on:** ADR-0003-Materialization-Bus - -## Summary - -This RFC completes the MaterializationBus implementation with three deliverables: - -1. **EmissionPort trait** — Hexagonal boundary for rule emissions -2. **ReduceOp enum** — Built-in deterministic reduce operations (no user functions) -3. **Cross-platform determinism tests** — GitHub Actions + DIND harness - ---- - -## 1. EmissionPort Trait (Hexagonal Architecture) - -### Problem - -The current plan passes `&MaterializationBus` directly to rule executors. This: - -- Couples rules to concrete implementation -- Exposes internal `EmitKey` construction to callers -- Makes testing harder (can't mock the bus) -- Violates hexagonal/ports-and-adapters principles - -### Solution - -Introduce an `EmissionPort` trait as the driven port. Rules depend on the trait; the engine provides a scoped adapter. - -```rust -// crates/warp-core/src/materialization/emission_port.rs - -/// Driven port for rule emissions (what rules see). -/// -/// Rules emit to channels via this trait. The engine provides a scoped -/// implementation that automatically constructs EmitKeys from execution context. -pub trait EmissionPort { - /// Emit data to a channel. - /// - /// The implementation handles EmitKey construction. Callers only provide - /// channel and payload. - fn emit(&self, channel: ChannelId, data: Vec); - - /// Emit with explicit subkey (for multi-emission rules). - /// - /// Use when a single rule invocation needs to emit multiple values to - /// the same channel. The subkey disambiguates emissions. - fn emit_with_subkey(&self, channel: ChannelId, subkey: u32, data: Vec); -} -``` - -### Scoped Adapter - -The engine creates a `ScopedEmitter` for each rule execution: - -```rust -// crates/warp-core/src/materialization/scoped_emitter.rs - -/// Scoped adapter that auto-fills EmitKey from execution context. -/// -/// Created by the engine for each rule invocation. Captures the scope hash -/// and rule ID, preventing rules from forging keys. -pub struct ScopedEmitter<'a> { - bus: &'a MaterializationBus, - scope_hash: Hash, - rule_id: u32, -} - -impl<'a> ScopedEmitter<'a> { - /// Create a new scoped emitter for a rule execution. - pub fn new(bus: &'a MaterializationBus, scope_hash: Hash, rule_id: u32) -> Self { - Self { bus, scope_hash, rule_id } - } -} - -impl EmissionPort for ScopedEmitter<'_> { - fn emit(&self, channel: ChannelId, data: Vec) { - let key = EmitKey::new(self.scope_hash, self.rule_id); - self.bus.emit(channel, key, data); - } - - fn emit_with_subkey(&self, channel: ChannelId, subkey: u32, data: Vec) { - let key = EmitKey::with_subkey(self.scope_hash, self.rule_id, subkey); - self.bus.emit(channel, key, data); - } -} -``` - -### Engine Integration - -```rust -// In Engine::execute_rule() or similar - -let emitter = ScopedEmitter::new(&self.bus, scope_node.hash(), rule.id()); -rule.execute(context, &emitter)?; -``` - -### Testing - -Rules can be tested with a mock port: - -```rust -#[cfg(test)] -struct MockEmissionPort { - emissions: RefCell)>>, -} - -impl EmissionPort for MockEmissionPort { - fn emit(&self, channel: ChannelId, data: Vec) { - self.emissions.borrow_mut().push((channel, data)); - } - // ... -} -``` - -### Duplicate EmitKey Rejection - -**Policy: Reject duplicate (channel, EmitKey) pairs. Always.** - -If a rule emits twice to the same channel with the same EmitKey, the bus returns -`DuplicateEmission` error. This catches rules that iterate non-deterministic -sources (e.g., `HashMap`) without proper subkey differentiation. - -```rust -/// Error returned when the same (channel, EmitKey) is emitted twice. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct DuplicateEmission { - pub channel: ChannelId, - pub key: EmitKey, -} - -impl MaterializationBus { - /// Emit data to a channel. Returns error if key already exists. - pub fn emit( - &self, - channel: ChannelId, - key: EmitKey, - data: Vec, - ) -> Result<(), DuplicateEmission> { - use std::collections::btree_map::Entry; - - let mut pending = self.pending.borrow_mut(); - let channel_map = pending.entry(channel).or_default(); - - match channel_map.entry(key) { - Entry::Vacant(e) => { - e.insert(data); - Ok(()) - } - Entry::Occupied(_) => Err(DuplicateEmission { channel, key }), - } - } -} -``` - -**Why reject even if payloads are identical?** - -Allowing "identical payload = OK" encourages sloppy code that emits redundantly. -Then someone changes a field and tests fail mysteriously. Rejecting always forces -rule authors to think: "Am I iterating deterministically? Do I need unique subkeys?" - -### Files to Create/Modify - -| File | Action | -| -------------------------------------------------------- | ------------------------------------------------- | -| `crates/warp-core/src/materialization/emission_port.rs` | **Create** — trait definition | -| `crates/warp-core/src/materialization/scoped_emitter.rs` | **Create** — adapter implementation | -| `crates/warp-core/src/materialization/mod.rs` | **Modify** — export new types | -| `crates/warp-core/src/materialization/bus.rs` | **Modify** — add DuplicateEmission, update emit() | -| `crates/warp-core/src/engine.rs` (or equivalent) | **Modify** — create ScopedEmitter per rule | - ---- - -## 2. ReduceOp Enum (Built-in Deterministic Ops) - -### Problem - -The current `ChannelPolicy::Reduce { join_fn_id }` design assumes a join function registry where users register merge functions by ID. This is a determinism landmine: - -- User functions may not be commutative/associative -- Function lookup adds indirection and potential for error -- Can't verify correctness at compile time -- Opens door to non-deterministic user code - -### Solution - -Replace `join_fn_id` with a closed enum of built-in reduce operations. - -**IMPORTANT: Not all reduce ops are commutative.** They fall into two categories: - -| Category | Ops | Property | -| ----------------------- | -------------------------------------- | ------------------------------------------------ | -| **Commutative Monoids** | `Sum`, `Max`, `Min`, `BitOr`, `BitAnd` | Order doesn't matter: `a ⊕ b = b ⊕ a` | -| **Order-Dependent** | `First`, `Last`, `Concat` | Deterministic via EmitKey order, NOT commutative | - -Both categories are **deterministic** (same inputs → same output), but only commutative ops are **permutation-invariant** at the value level. Order-dependent ops rely on the canonical EmitKey ordering. - -```rust -// crates/warp-core/src/materialization/reduce_op.rs - -/// Built-in reduce operations for channel coalescing. -/// -/// # Algebraic Categories -/// -/// **Commutative monoids** (permutation-invariant): -/// - `Sum`, `Max`, `Min`, `BitOr`, `BitAnd` -/// - Result is identical regardless of emission order -/// -/// **Order-dependent** (deterministic via EmitKey order): -/// - `First`, `Last`, `Concat` -/// - Result depends on canonical EmitKey ordering -/// - NOT commutative — do not claim they are! -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum ReduceOp { - // ─── COMMUTATIVE MONOIDS ─────────────────────────────────────────── - - /// Sum all values as little-endian u64. - /// Empty input → `[0u8; 8]` (zero). - Sum, - - /// Take maximum value (lexicographic byte comparison). - /// Empty input → `[]` (empty vec). - Max, - - /// Take minimum value (lexicographic byte comparison). - /// Empty input → `[]` (empty vec). - Min, - - /// Bitwise OR all values. - /// Shorter values are zero-padded on the right. - /// Empty input → `[]` (empty vec). - BitOr, - - /// Bitwise AND all values. - /// Result length = minimum input length (intersection semantics). - /// Empty input → `[]` (empty vec). - BitAnd, - - // ─── ORDER-DEPENDENT (NOT COMMUTATIVE) ───────────────────────────── - - /// Take first value by EmitKey order. - /// Empty input → `[]` (empty vec). - /// WARNING: Not commutative. Depends on canonical key ordering. - First, - - /// Take last value by EmitKey order. - /// Empty input → `[]` (empty vec). - /// WARNING: Not commutative. Depends on canonical key ordering. - Last, - - /// Concatenate all values in EmitKey order. - /// Empty input → `[]` (empty vec). - /// WARNING: Not commutative. Order matters for result bytes. - Concat, -} - -impl ReduceOp { - /// Returns true if this op is a commutative monoid (permutation-invariant). - pub const fn is_commutative(&self) -> bool { - matches!(self, Self::Sum | Self::Max | Self::Min | Self::BitOr | Self::BitAnd) - } -} -``` - -### Updated ChannelPolicy - -```rust -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -pub enum ChannelPolicy { - /// All emissions in EmitKey order, length-prefixed. - #[default] - Log, - - /// Error if more than one emission. - StrictSingle, - - /// Reduce via built-in operation. - Reduce(ReduceOp), -} -``` - -### Implementation - -```rust -impl ReduceOp { - /// Apply this reduce operation to a set of values. - /// - /// Values are provided in EmitKey order (required for First/Last/Concat). - /// Returns the reduced result. - /// - /// # Empty Input Behavior - /// - /// All ops return `[]` (empty vec) on empty input, EXCEPT: - /// - `Sum` returns `[0u8; 8]` (zero as u64 LE) - /// - /// This is intentional: empty input means "nothing to reduce." - pub fn apply(self, values: impl IntoIterator>) -> Vec { - let mut iter = values.into_iter().peekable(); - - // Handle empty input uniformly (except Sum) - if iter.peek().is_none() { - return match self { - Self::Sum => vec![0u8; 8], // Identity for addition - _ => Vec::new(), // "Nothing to reduce" - }; - } - - match self { - // ─── COMMUTATIVE MONOIDS ─────────────────────────────────── - - Self::Sum => { - let sum: u64 = iter - .map(|v| { - let mut buf = [0u8; 8]; - let len = v.len().min(8); - buf[..len].copy_from_slice(&v[..len]); - u64::from_le_bytes(buf) - }) - .sum(); - sum.to_le_bytes().to_vec() - } - - Self::Max => iter.max().unwrap(), // unwrap safe: checked non-empty - - Self::Min => iter.min().unwrap(), // unwrap safe: checked non-empty - - Self::BitOr => { - iter.reduce(|acc, v| bitwise_or(&acc, &v)).unwrap() - } - - Self::BitAnd => { - iter.reduce(|acc, v| bitwise_and(&acc, &v)).unwrap() - } - - // ─── ORDER-DEPENDENT (EmitKey order matters) ─────────────── - - Self::First => iter.next().unwrap(), // unwrap safe: checked non-empty - - Self::Last => iter.last().unwrap(), // unwrap safe: checked non-empty - - Self::Concat => iter.flatten().collect(), - } - } -} - -/// Bitwise OR with zero-padding for shorter operand. -fn bitwise_or(a: &[u8], b: &[u8]) -> Vec { - let len = a.len().max(b.len()); - let mut result = vec![0u8; len]; - for (i, byte) in result.iter_mut().enumerate() { - let av = a.get(i).copied().unwrap_or(0); - let bv = b.get(i).copied().unwrap_or(0); - *byte = av | bv; - } - result -} - -/// Bitwise AND with truncation to shorter operand (intersection semantics). -fn bitwise_and(a: &[u8], b: &[u8]) -> Vec { - let len = a.len().min(b.len()); - (0..len).map(|i| a[i] & b[i]).collect() -} -``` - -### Files to Create/Modify - -| File | Action | -| ------------------------------------------------------- | ----------------------------------------------- | -| `crates/warp-core/src/materialization/reduce_op.rs` | **Create** — enum + apply() | -| `crates/warp-core/src/materialization/channel.rs` | **Modify** — update ChannelPolicy | -| `crates/warp-core/src/materialization/bus.rs` | **Modify** — call ReduceOp::apply() in finalize | -| `crates/warp-core/tests/materialization_determinism.rs` | **Add** — reduce op tests | - ---- - -## 3. Cross-Platform Determinism Tests - -### Problem - -MaterializationBus must produce identical output across: - -- macOS (dev machines) -- Linux (CI, production) -- WASM (browser runtime) - -Current tests run only on the host platform. - -### Solution - -Two-layer testing: - -| Layer | Environment | Trigger | Purpose | -| ------------------ | ---------------- | ------------------------------ | ------------------------------ | -| **DIND** | Docker-in-Docker | `cargo xtask dind-determinism` | Local dev, fast iteration | -| **GitHub Actions** | Native runners | Push/PR | Gate merges, real environments | - -### 3.1 DIND Harness Extension - -Extend existing DIND test harness to include materialization digest: - -```rust -// crates/echo-dind-tests/src/lib.rs - -/// Output from a determinism test run. -#[derive(Debug, Serialize, Deserialize)] -pub struct DeterminismOutput { - /// State hash after N ticks. - pub state_hash: String, - /// Tick receipt hashes. - pub receipt_hashes: Vec, - /// NEW: Materialization digest (hash of all finalized frames). - pub materialization_digest: String, -} -``` - -The test runs the same scenario on: - -1. Host (macOS/Linux) -2. Docker Linux container -3. WASM via wasm-pack - -All three must produce identical `materialization_digest`. - -### 3.2 GitHub Actions Workflow - -```yaml -# .github/workflows/determinism.yml - -name: Determinism - -on: - push: - branches: [main] - pull_request: - branches: [main] - -jobs: - determinism-matrix: - strategy: - matrix: - os: [ubuntu-latest, macos-latest] - include: - - os: ubuntu-latest - target: x86_64-unknown-linux-gnu - - os: macos-latest - target: x86_64-apple-darwin - - runs-on: ${{ matrix.os }} - - steps: - - uses: actions/checkout@v4 - - - name: Install Rust - uses: dtolnay/rust-action@stable - with: - targets: ${{ matrix.target }},wasm32-unknown-unknown - - - name: Install wasm-pack - run: cargo install wasm-pack - - - name: Run determinism tests - run: cargo test -p warp-core --test materialization_determinism - - - name: Run WASM determinism tests - run: wasm-pack test --node crates/warp-core - - - name: Capture materialization digest - id: digest - run: | - DIGEST=$(cargo run -p echo-dind-tests --bin capture-digest) - echo "digest=$DIGEST" >> $GITHUB_OUTPUT - echo "$DIGEST" > digest.txt - - - name: Upload digest artifact - uses: actions/upload-artifact@v4 - with: - name: digest-${{ matrix.os }} - path: digest.txt - - verify-cross-platform: - needs: determinism-matrix - runs-on: ubuntu-latest - steps: - - name: Download all digests - uses: actions/download-artifact@v4 - - - name: Compare digests - run: | - LINUX=$(cat digest-ubuntu-latest/digest.txt) - MACOS=$(cat digest-macos-latest/digest.txt) - - if [ "$LINUX" != "$MACOS" ]; then - echo "DETERMINISM FAILURE: Linux and macOS produced different digests" - echo "Linux: $LINUX" - echo "macOS: $MACOS" - exit 1 - fi - - echo "Cross-platform determinism verified: $LINUX" -``` - -### 3.3 Local DIND Command - -```bash -# Run locally before pushing -cargo xtask dind-determinism - -# Runs: -# 1. Native test → captures digest -# 2. Docker test → captures digest -# 3. WASM test → captures digest -# 4. Compares all three -``` - -### Files to Create/Modify - -| File | Action | -| -------------------------------------------------- | ----------------------------------------- | -| `.github/workflows/determinism.yml` | **Create** — CI workflow | -| `crates/echo-dind-tests/src/lib.rs` | **Modify** — add materialization_digest | -| `crates/echo-dind-tests/src/bin/capture-digest.rs` | **Create** — digest capture binary | -| `xtask/src/main.rs` | **Modify** — add dind-determinism command | - ---- - -## Implementation Order - -```text -Phase 1: EmissionPort (unblocks engine integration) -├── Create emission_port.rs -├── Create scoped_emitter.rs -├── Update mod.rs exports -└── Add unit tests - -Phase 2: ReduceOp (completes bus semantics) -├── Create reduce_op.rs -├── Update channel.rs (ChannelPolicy) -├── Update bus.rs (finalize with reduce) -└── Add reduce tests to determinism suite - -Phase 3: Cross-Platform Tests (gates merges) -├── Extend DIND harness -├── Create GitHub workflow -├── Add xtask command -└── Verify on first PR -``` - -## Test Plan: "SPEC is reSPECted" - -Comprehensive test suite ensuring the spec cannot lie. - -### Tier 1 — EmitKey Correctness + Wire Encoding - -| Test | What It Proves | -| ------------------------------------------------- | -------------------------------------------------------- | -| `emit_key_ord_is_lexicographic_scope_rule_subkey` | Ordering matches spec | -| `emit_key_wire_encoding_is_40_bytes_no_padding` | bytes[0..32]=scope, [32..36]=rule LE, [36..40]=subkey LE | -| `emit_key_roundtrip_wire` | encode → decode → equals | -| `emit_key_subkey_from_hash_is_deterministic` | Same input → same u32 | - -### Tier 2 — Bus Duplicate Rejection - -| Test | What It Proves | -| --------------------------------------------------- | -------------------------------------------------- | -| `bus_rejects_duplicate_key_same_channel` | (ch, key, A) then (ch, key, B) → DuplicateEmission | -| `bus_allows_same_key_different_channels` | (ch1, key) and (ch2, key) both OK | -| `bus_rejects_duplicate_key_even_if_bytes_identical` | No "identical payload = OK" loophole | - -### Tier 3 — Permutation Invariance ("SPEC Police") - -| Test | What It Proves | -| ----------------------------------------------- | ---------------------------------- | -| `log_finalize_is_permutation_invariant_small_n` | All N! orderings → identical bytes | -| `bus_channel_iteration_is_canonical` | Channels in BTreeMap order | -| `bus_log_preserves_all_emissions_no_drops` | count(output) == count(input) | - -### Tier 4 — ReduceOp Algebra - -**Commutative ops (must be permutation-invariant):** - -| Test | What It Proves | -| ------------------------------------------- | ------------------------------ | -| `reduce_sum_commutative_associative` | All permutations → same result | -| `reduce_max_min_are_commutative` | Byte-lex comparison is stable | -| `reduce_bitor_commutative_variable_length` | Zero-padding semantics correct | -| `reduce_bitand_commutative_variable_length` | Truncation semantics correct | - -**Order-dependent ops (NOT commutative, deterministic via EmitKey):** - -| Test | What It Proves | -| ------------------------------------------- | ------------------------------ | -| `reduce_first_picks_first_in_emitkey_order` | Smallest key wins | -| `reduce_last_picks_last_in_emitkey_order` | Largest key wins | -| `reduce_concat_matches_emitkey_order` | Output = concat(sorted by key) | - -**Truth serum:** - -| Test | What It Proves | -| ----------------------------------------------- | ---------------------------------- | -| `reduce_op_commutativity_table_is_honest` | `is_commutative()` matches reality | -| `reduce_empty_input_returns_specified_identity` | Sum→[0;8], others→[] | - -### Tier 5 — Engine Integration - -| Test | What It Proves | -| ------------------------------------------------ | -------------------------------- | -| `engine_log_emissions_stable_across_apply_order` | Rewrite order doesn't matter | -| `engine_strict_single_deterministic_failure` | Same error signature both orders | -| `engine_reduce_sum_stable_across_apply_order` | Reduced sum identical | -| `engine_emits_only_post_commit` | Port empty before commit | - -### Tier 6 — Cross-Platform Digest - -| Test | What It Proves | -| ---------------------------------------------------- | -------------------------------- | -| `determinism_output_includes_materialization_digest` | Harness writes digest | -| `cross_platform_digest_matches_linux_macOS_wasm` | All platforms identical | -| `scope_hash_is_content_hash_not_id_hash` | Equivalent stores → same EmitKey | - ---- - -## Open Questions - -1. **WASM target for CI** — `wasm32-unknown-unknown` or `wasm32-wasi`? Recommend `unknown-unknown` for browser purity. - -2. **Reduce op extensibility** — Should we ever allow user-defined reduce ops? **NO.** Use `Log` and reduce client-side. - -3. **Digest algorithm** — BLAKE3 of concatenated frame bytes. Simple, no Merkle tree needed. - ---- - -## Success Criteria - -- [x] Rules emit via `EmissionPort` trait, not direct bus access -- [x] Duplicate (channel, EmitKey) pairs rejected with `DuplicateEmission` -- [x] `ChannelPolicy::Reduce(ReduceOp)` replaces `join_fn_id` -- [x] All 8 `ReduceOp` variants implemented with `is_commutative()` classification -- [x] Empty-input behavior: Sum→[0;8], all others→[] -- [x] All Tier 1-5 tests passing -- [x] GitHub Actions workflow passes on PR -- [x] `cargo xtask dind` passes locally -- [x] Cross-platform digest match verified in CI (weekly schedule) - ---- - -## Revision History - -| Date | Change | -| ---------- | --------------------------------------------------------------------- | -| 2026-01-17 | Initial draft | -| 2026-01-17 | Fixed ReduceOp algebra claims (First/Last/Concat are NOT commutative) | -| 2026-01-17 | Added duplicate EmitKey rejection policy | -| 2026-01-17 | Specified empty-input behavior (Sum→[0;8], others→[]) | -| 2026-01-17 | Added comprehensive "SPEC is reSPECted" test plan | -| 2026-01-17 | Phase 3 complete: 127 tests, CI workflow, xtask dind command | diff --git a/docs/archive/roadmap-mwmr-mini-epic.md b/docs/archive/roadmap-mwmr-mini-epic.md deleted file mode 100644 index ac71dc68..00000000 --- a/docs/archive/roadmap-mwmr-mini-epic.md +++ /dev/null @@ -1,84 +0,0 @@ - - - -# MWMR Concurrency Mini‑Epic Roadmap (Footprints, Reserve Gate, Telemetry) - -Status: Active • Owner: warp-core • Created: 2025-10-27 - -## Outcomes - -- Enforce MWMR determinism via independence checks (footprints + ports + factor masks). -- Keep the hot path zero‑overhead (compact u32 rule ids; domain‑separated family ids only at boundaries). -- Prove commutation with property tests (N‑permutation) and add basic telemetry for conflict rates. - ---- - -## Phase 0.5 — Foundations (Done / In‑Progress) - -- [x] Footprint type with ports and factor mask (IdSet/PortSet; deterministic intersects) -- [x] RewriteRule surface extended with `compute_footprint`, `factor_mask`, `ConflictPolicy` -- [x] PendingRewrite carries `footprint` + `phase` -- [x] Property test: 2 independent motion rewrites commute (equal snapshot hash) -- [x] Spec doc: `docs/spec-mwmr-concurrency.md` - ---- - -## Phase 1 — Reservation Gate & Compact IDs - -- [x] CompactRuleId(u32) and rule table mapping family_id → compact id (in Engine) -- [x] DeterministicScheduler::reserve(tx, &mut PendingRewrite) → bool (active frontier per tx) -- [x] Engine commit() wires the reserve gate (execute only Reserved rewrites) -- [x] Feature‑gated JSONL telemetry (reserved/conflict) with timestamp, tx_id, short rule id -- [ ] Use CompactRuleId in PendingRewrite and internal execution paths (leave family id for ordering/disk/wire) - ---- - -## Phase 2 — Proof & Performance - -- [ ] Property test: N‑permutation commutation (N = 3..6 independent rewrites) -- [ ] Reserve gate smoke tests (same PortKey ⇒ conflict; disjoint ports ⇒ reserve) -- [ ] Criterion bench: independence checks (10/100/1k rewrites) — target < 1 ms @ 100 -- [ ] Telemetry counters per tick (conflict_rate, retry_count, reservation_latency_ms, epoch_flip_ms) -- [ ] Add Retry with randomized backoff (behind flag) once telemetry lands; keep default Abort - ---- - -## Phase 3 — Rule Identity & Hot‑Load - -- [x] build.rs generates const family id for `rule:motion/update` (domain‑separated) -- [ ] Generalize generator (src/gen/rule_ids.rs) and runtime assert test to catch drift -- [ ] Rhai rule registration: `register_rule{name, match, exec, ?id, ?revision}`; engine computes if omitted -- [ ] Revision ID = `blake3("rule-rev::canon-ast-v1" || canonical AST bytes)` - ---- - -## Phase 4 — Storage & Epochs (Scoping/Design) - -- [ ] Offset‑graph arena + mmap view (zero‑copy snapshots) -- [ ] Double‑buffered planes (attachments/skeleton), lazy epoch flips, grace‑period reclamation -- [ ] Optional Merkle overlays for partial verification - ---- - -## Guardrails & Invariants - -- Deterministic planning key = (scope_hash, family_id); execution may be parallel, ordering stays stable. -- Footprint independence order: factor_mask → ports → edges → nodes; fail fast on ports. -- Keep |L| ≤ 5–10; split rules or seed from rare types if larger. -- Never serialize CompactRuleId; boundary formats carry family id + (optional) revision id. - ---- - -## Telemetry (dev feature) - -- Events: `reserved`, `conflict` (ts_micros, tx_id, rule_id_short) -- Counters per tick: conflict_rate, retry_count, reservation_latency_ms, epoch_flip_ms, bitmap_blocks_checked - ---- - -## Links - -- Spec: `docs/spec-mwmr-concurrency.md` -- Tests: `crates/warp-core/tests/footprint_independence_tests.rs`, `crates/warp-core/tests/property_commute_tests.rs` -- Engine: `crates/warp-core/src/engine_impl.rs`, `crates/warp-core/src/scheduler.rs` -- Build: `crates/warp-core/build.rs` diff --git a/docs/archive/runtime-diagnostics-plan.md b/docs/archive/runtime-diagnostics-plan.md deleted file mode 100644 index 15106fa9..00000000 --- a/docs/archive/runtime-diagnostics-plan.md +++ /dev/null @@ -1,64 +0,0 @@ - - - -# Runtime Diagnostics Plan (Phase 0.5) - -Outlines logging, tracing, crash recovery, and inspector data streams for Echo runtime. - ---- - -## Logging Levels - -- `TRACE` – verbose diagnostics (disabled in production). -- `DEBUG` – subsystem insights (branch tree, Codex’s Baby). -- `INFO` – major lifecycle events (fork, merge, replay start). -- `WARN` – recoverable anomalies (drop records, entropy spikes). -- `ERROR` – determinism faults (capability denial, PRNG mismatch). - -Logs are structured JSON: `{ timestamp?, tick, branch, level, event, data }`. Timestamps optional and excluded from hashes. - ---- - -## Crash Recovery - -- On `ERROR`, emit synthetic timeline node with `errorCode`, `nodeId`, `diffId`. -- Persist crash report (JSON) including last inspector frames and capability state. -- Provide CLI `echo diagnostics --last-crash` to display report. - ---- - -## Tracing - -- Optional per-phase tracing (`TRACE` level) capturing start/end of scheduler phases, system durations. -- Output to separate trace buffer for tooling (`trace.jsonl`). - ---- - -## Inspector Streams - -- `InspectorFrame` (core metrics) -- `CBInspectorFrame` (Codex’s Baby) -- `BridgeInspectorFrame` (Temporal Bridge) -- `CapabilityInspectorFrame` - -Frames emitted each tick after `timeline_flush`, appended to ring buffer (configurable size). Debug tools subscribe over IPC/WebSocket. - ---- - -## Diagnostic CLI - -- `echo inspect --tick ` – dump inspector frames. -- `echo entropy --branch ` – show entropy history. -- `echo diff ` – print diff summary. -- `echo replay --verify` – reuse replay contract. - ---- - -## CI Integration - -- Pipeline collects inspector frames for failing tests, attaches to artifacts. -- Warnings escalate to failures when thresholds exceeded (entropy > threshold without observer, repeated paradox quarantine). - ---- - -This plan provides consistent observability without compromising determinism. diff --git a/docs/archive/rust-rhai-ts-division.md b/docs/archive/rust-rhai-ts-division.md deleted file mode 100644 index 77b301c3..00000000 --- a/docs/archive/rust-rhai-ts-division.md +++ /dev/null @@ -1,87 +0,0 @@ - - - -# Language & Responsibility Map (Phase 1) - -Echo’s runtime stack is intentionally stratified. Rust owns the deterministic graph engine; Rhai sits on top for gameplay scripting; TypeScript powers the tooling layer via WebAssembly bindings. This document captures what lives where as we enter Phase 1 (Core Ignition). - ---- - -## Rust (warp-core, wasm, cli) - -### Responsibilities - -- WARP engine: GraphStore, PatternGraph, RewriteRule, DeterministicScheduler, commit/Snapshot APIs. -- ECS foundations: Worlds, Systems, Components expressed as rewrite rules. -- Timeline & Branch tree: rewrite transactions, snapshot hashing, concurrency guard rails. -- Math/PRNG: deterministic float32 / fixed32 modules shared with gameplay. -- Netcode: lockstep / rollback / authority modes using rewrite transactions. -- Asset pipeline: import/export graphs, payload storage, zero-copy access. -- Confluence: distributed synchronization of rewrite transactions. -- Rhai engine hosting: embed Rhai with deterministic module set; expose WARP bindings. -- CLI tools: `echo-cli` with `verify`, `bench`, and `inspect` subcommands. - -### Key Crates - -- `warp-core` – core engine; Rhai binds directly in-process -- `warp-wasm` – WASM build for tooling/editor -- `warp-cli` – CLI utilities (`echo-cli` binary: verify, bench, inspect) - ---- - -## Rhai (gameplay authoring layer) - -### Rhai Responsibilities - -- Gameplay systems & components (e.g., AI state machines, quests, input handling). -- Component registration, entity creation/destruction via exposed APIs. -- Scripting for deterministic “async” (scheduled events through Codex’s Baby). -- Editor lenses and inspector overlays written in Rhai for rapid iteration. - -### Constraints - -- Single-threaded per branch; no OS threads. -- Engine budgeted deterministically per tick. -- Mutations occur through rewrite intents (`warp.apply(...)`), not raw memory access. - -### Bindings - -- `warp` Rhai module providing: - - `apply(rule_name, scope, params)` - - `delay(seconds, fn)` (schedules replay-safe events) - - Query helpers (read components, iterate entities) - - Capability-guarded operations (world:rewrite, asset:import, etc.) - ---- - -## TypeScript / Web Tooling - -### TypeScript Responsibilities - -- Echo Studio (graph IDE) – visualizes world graph, rewrites, branch tree. -- Inspector dashboards – display Codex, entropy, paradox frames. -- Replay/rollback visualizers, network debugging tools. -- Plugin builders and determinism test harness UI. - -### Integration - -- Uses `warp-wasm` to call into WARP engine from the browser. -- IPC/WebSocket for live inspector feeds (`InspectorEnvelope`). -- Works with JSONL logs for offline analysis. -- All mutations go through bindings; tooling never mutates state outside WARP APIs. - -### Tech - -- Frontend frameworks: React/Svelte/Vanilla as needed. -- WebGPU/WebGL for graph visualization. -- TypeScript ensures type safety for tooling code. - ---- - -## Summary - -- Rust: core deterministic runtime + binding layers. -- Rhai: gameplay logic, editor lenses, deterministic script-level behavior. -- TypeScript: visualization and tooling on top of WASM/IPC. - -This division keeps determinism and performance anchored in Rust while giving designers and tooling engineers approachable layers tailored for their workflows. diff --git a/docs/archive/scheduler-benchmarks.md b/docs/archive/scheduler-benchmarks.md deleted file mode 100644 index d9a7a551..00000000 --- a/docs/archive/scheduler-benchmarks.md +++ /dev/null @@ -1,25 +0,0 @@ - - - -# Scheduler Benchmark Plan (Phase 0) - -This document has been **split** to reduce drift and make scope explicit. - -Doc map: - -- [docs/scheduler.md](./scheduler.md) - -Current (implemented) benchmarks: - -- [docs/scheduler-performance-warp-core.md](./scheduler-performance-warp-core.md) - -Future (planned) system-scheduler benchmarks: - -- [docs/spec-scheduler.md](./spec-scheduler.md) (planned benchmark scenarios; spec-only today) - ---- - -The detailed benchmark plan content now lives in: - -- [docs/scheduler-performance-warp-core.md](./scheduler-performance-warp-core.md) (warp-core) -- [docs/spec-scheduler.md](./spec-scheduler.md) (planned system scheduler scenarios) diff --git a/docs/archive/scheduler-reserve-complexity.md b/docs/archive/scheduler-reserve-complexity.md deleted file mode 100644 index f17bf1d1..00000000 --- a/docs/archive/scheduler-reserve-complexity.md +++ /dev/null @@ -1,12 +0,0 @@ - - - -# Scheduler `reserve()` Time Complexity Analysis - -This document has been **merged** into the canonical warp-core scheduler doc: - -- [docs/scheduler-warp-core.md](./scheduler-warp-core.md) - -It remains as a stable link target for older references. - -The full analysis now lives in [docs/scheduler-warp-core.md](./scheduler-warp-core.md). diff --git a/docs/archive/scheduler-reserve-validation.md b/docs/archive/scheduler-reserve-validation.md deleted file mode 100644 index 37a3197c..00000000 --- a/docs/archive/scheduler-reserve-validation.md +++ /dev/null @@ -1,23 +0,0 @@ - - - -# Scheduler `reserve()` Implementation Validation - -This document has been **merged** into the canonical warp-core scheduler doc: - -- [docs/scheduler-warp-core.md](./scheduler-warp-core.md) - -It remains as a stable link target for older references. - -## Questions Answered - -1. ✅ **Atomic Reservation**: No partial marking on conflict -2. ✅ **Determinism Preserved**: Same inputs → same outputs -3. ✅ **Time Complexity**: Detailed analysis with ALL loops counted -4. ✅ **Performance Claims**: Measured, not just theoretical - ---- - -If you’re here for evidence details (atomicity/determinism/complexity), read: - -- [docs/scheduler-warp-core.md](./scheduler-warp-core.md) diff --git a/docs/archive/spec-deterministic-math.md b/docs/archive/spec-deterministic-math.md deleted file mode 100644 index 514bcc69..00000000 --- a/docs/archive/spec-deterministic-math.md +++ /dev/null @@ -1,213 +0,0 @@ - - - -# Deterministic Math Module Specification (Phase 0) - -> **Background:** For a gentler introduction, see [WARP Primer](/guide/warp-primer). - -Echo’s math module underpins every deterministic system: physics proxies, animation, AI, and branch reconciliation. - -**Status (2026-01-02): legacy draft + partial reality.** - -- This document started life as a JS/TypeScript-oriented Phase 0 draft. -- The canonical implementation today is Rust `warp-core` (`crates/warp-core/src/math/*`). -- The normative determinism policy is `docs/SPEC_DETERMINISTIC_MATH.md`. -- Validation and CI lanes are tracked in `docs/math-validation-plan.md`. - -Treat this spec as a **design sketch for future bindings** (TS/WASM/FFI) and an inventory of desired API shape, not as a statement that the JS implementation exists. - ---- - -## Goals - -- Provide deterministic vector/matrix/quaternion operations across platforms (at minimum: Linux/macOS, and eventually WASM/JS bindings). -- Support dual numeric modes via scalar backends: - - float lane (`F32Scalar`, default) - - fixed-point lane (`DFix64`, feature-gated today) -- Expose seeded PRNG services suitable for replay and branching. -- Offer allocation-aware APIs (avoid heap churn) for hot loops. -- Surface profiling hooks (NaN guards, range checks) in development builds. - ---- - -## Numeric Modes - -### Float32 Mode (default) - -- **Rust source of truth:** `F32Scalar` wraps `f32` and enforces canonicalization invariants (NaNs, signed zero, subnormals) at construction and after operations. -- **Transcendentals:** `sin`/`cos` are provided via a deterministic software backend (`warp_core::math::trig`), not platform/libm. -- **Bindings note:** if/when we ship TS/WASM bindings, they must match Rust’s outputs and invariants; “just `Math.fround`” is not sufficient to guarantee cross-engine determinism for transcendentals or NaN payload behavior. - -### Fixed-Point Mode (opt-in) - -- **Rust source of truth:** `DFix64` is Q32.32 fixed-point stored in `i64` and is currently feature-gated behind `det_fixed` so we can evolve it without destabilizing the default lane. -- **Non-finite mapping:** conversions from float inputs must be deterministic (e.g., NaN → 0, ±∞ saturate) and are covered by tests. -- **Bindings note:** future TS bindings should treat Rust fixtures as canonical; JS `BigInt` fixed-point is a possible implementation strategy, but not a correctness authority. - -Mode should be chosen at engine init (or build feature selection), with a clear policy for serialization/hashing so deterministic replay remains stable. - ---- - -## Core Types - -### Vec2 / Vec3 / Vec4 - -```ts -interface Vec2 { - readonly x: number; - readonly y: number; -} - -type VecLike = Float32Array | number[]; -``` - -- Backed by `Float32Array` of length 2/3/4. -- Methods: `create`, `clone`, `set`, `add`, `sub`, `scale`, `dot`, `length`, `normalize`, `lerp`, `equals`. -- All mutating functions accept `out` parameter for in-place updates to reduce allocations. -- Deterministic clamps: every operation ends with `fround` (float mode) or `fixed` operations. -- Rust parity: `warp_core::math::Vec3` currently implements add/sub/scale/dot/cross/length/normalize; `Vec2`/`Vec4` remain TODO. - -### Mat3 / Mat4 - -- Column-major storage (`Float32Array(9)` / `Float32Array(16)`). -- Methods: `identity`, `fromRotation`, `fromTranslation`, `multiply`, `invert`, `transformVec`. -- Deterministic inversion: use well-defined algorithm with guard against singular matrices (records failure and returns identity or throws based on config). -- Rust parity: `warp_core::math::Mat4` exposes `multiply` and `transform_point`; identity/fromRotation/invert are pending. - -### Quat - -- Represented as `[x, y, z, w]`. -- Functions: `identity`, `fromAxisAngle`, `multiply`, `slerp`, `normalize`, `toMat4`. -- `slerp` uses deterministic interpolation with clamped range. -- Rust parity: `warp_core::math::Quat` implements identity/fromAxisAngle/multiply/normalize/to_mat4; `slerp` remains TBD. - -### Transform - -- Struct bundling position (Vec3), rotation (Quat), scale (Vec3). -- Helper for constructing Mat4; ensures consistent order of operations. -- Rust parity: transform helpers are still tracked for Phase 1 (not implemented yet). - -### Bounds / AABB - -- Useful for physics collision; stores min/max Vec3. -- Provides deterministic union/intersection operations. - ---- - -## PRNG Services - -### Engine PRNG - -- Based on counter-based generator (e.g., Philox or Xoroshiro128+). -- Implementation in TypeScript with optional WebAssembly acceleration later. -- Interface: - -```ts -interface PRNG { - next(): number; // returns float in [0,1) - nextInt(min: number, max: number): number; - nextFloat(min: number, max: number): number; - state(): PRNGState; - jump(): PRNG; // independent stream -} -``` - -- `state` serializable for replay. -- `jump` used for branch forking: clone generator with deterministic offset. -- `seed` derived from combination of world seed + branch ID + optional subsystem tag. -- Rust parity: `warp_core::math::Prng` implements seeding, `next_f32`, and `next_int`; state/jump APIs are follow-up work. - -### Deterministic Hashing - -- Provide `hash64` function (e.g., SplitMix64) for converting strings/IDs into seeds. -- Ensure stable across platforms; implement in TypeScript to avoid native differences. - -### Integration Points - -- Scheduler passes `math.prng` on `TickContext`. -- Codex’s Baby `CommandContext` exposes `prng.spawn(scope)` for per-handler streams. -- Timeline branch creation clones PRNG state to maintain deterministic divergence. - ---- - -## Utility Functions - -- `clamp(value, min, max)` – deterministic clamp using `Math.min/Math.max` once (avoid multiple rounding). -- `approximatelyEqual(a, b, epsilon)` – uses configured epsilon (float32 ~1e-6). -- `degToRad`, `radToDeg` – using float32 rounding. -- `wrapAngle(angle)` – ensure deterministic wrap [-π, π]. -- `bezier`, `catmullRom` – deterministic interpolation functions for animation. - ---- - -## Memory Strategy - -- Provide pool of reusable vectors/matrices for temporary calculations (`MathStack`). -- `MathStack` uses deterministic LIFO behavior: `pushVec3()`, `pushMat4()`, `pop()`. -- Guard misuse in dev builds (stack underflow/overflow assertions). - ---- - -## Diagnostics - -- Optional `math.enableDeterminismChecks()` toggles NaN/Infinity detection; throws descriptive error with stack trace. -- `math.traceEnabled` allows capturing sequence of operations for debugging (recorded in inspector overlay). -- Stats counters: operations per frame, PRNG usage frequency. - ---- - -## API Surface (draft) - -```ts -interface EchoMath { - mode: "float32" | "fixed32"; - vec2: Vec2Module; - vec3: Vec3Module; - vec4: Vec4Module; - mat3: Mat3Module; - mat4: Mat4Module; - quat: QuatModule; - transform: TransformModule; - prng: PRNGFactory; - stack: MathStack; - constants: { - epsilon: number; - tau: number; - }; - utils: { - clamp(value: number, min: number, max: number): number; - approx(a: number, b: number, epsilon?: number): boolean; - degToRad(deg: number): number; - radToDeg(rad: number): number; - }; -} -``` - -`PRNGFactory`: - -```ts -interface PRNGFactory { - create(seed: PRNGSeed): PRNG; - fromTimeline(fingerprint: TimelineFingerprint, scope?: string): PRNG; -} -``` - ---- - -## Determinism Notes - -- Avoid `Math.random`; all randomness flows through PRNG. -- `Math.sin/cos` may vary across engines; implement polynomial approximations or wrap to enforce float32 rounding (test across browsers). -- Fixed-point mode may skip trig functions initially; provide lookup tables or polynomial approximations. -- Ensure order of operations consistent; avoid relying on JS evaluation order quirks. - ---- - -## Open Questions - -- Should fixed-point mode support quaternions (costly) or restrict to 2D contexts? -- How to expose SIMD acceleration where available without breaking determinism (e.g., WebAssembly fallback). -- Do we allow user-defined math extensions (custom vector sizes) via plugin system? -- Integration with physics adapters: how to synchronize with Box2D/Rapier numeric expectations (float32). - -Future work: add unit tests validating cross-environment determinism, micro-benchmarks for operations, and sample usage in the playground. diff --git a/docs/archive/spec-geom-collision.md b/docs/archive/spec-geom-collision.md deleted file mode 100644 index d6b83b6b..00000000 --- a/docs/archive/spec-geom-collision.md +++ /dev/null @@ -1,42 +0,0 @@ - - - -# Geometry & Collision (Spec Stub) - -> **Background:** For a gentler introduction, see [WARP Primer](/guide/warp-primer). - -**Status: not yet re-specified.** This repo currently carries an interactive DPO tour and diagram assets, but the full written spec for Echo’s geometry/collision subsystem is pending re-homing into the Rust-first era. - -## Scope (Intended) - -- Deterministic broad phase and narrow phase modeled as graph rewrites. -- Canonical identifiers for bodies, shapes, and contacts. -- Collision events emitted as deterministic graph deltas. -- CCD as a deterministic, replayable sequence of rewrite steps. - -## Non-Goals (For Now) - -- Physics engine replacement (Box2D/Rapier integrations remain adapters). -- GPU-accelerated collision or platform-specific broad-phase shortcuts. -- Real-time authoring tools (tracked separately in editor/inspector specs). - -What exists today: - -- Interactive tour: `/collision-dpo-tour.html` (source: `docs/public/collision-dpo-tour.html`) -- Guide entrypoint: `docs/guide/collision-tour.md` -- Diagram assets: `docs/public/assets/collision/` - -What this spec should eventually cover: - -- Deterministic broad phase + narrow phase modeled as graph rewrites (DPO). -- Canonical IDs, stable ordering, and hashing inputs/outputs for replay. -- Temporal proxies, CCD workflow, and event emission in a timeline-aware world. -- See [spec-deterministic-math.md](../spec-deterministic-math.md) for the normative deterministic math policy. - -## Near-Term Deliverables - -- Solidify the wire format for collision-related view ops (if any). -- Define the minimal node/edge schema for bodies, shapes, and contacts. -- Specify the canonical ordering for resolving contact sets. - -Until the full spec is written, treat the tour as an **illustrative artifact**, not a normative contract. diff --git a/docs/archive/study/aion.cls b/docs/archive/study/aion.cls deleted file mode 100644 index 4f2d2f67..00000000 --- a/docs/archive/study/aion.cls +++ /dev/null @@ -1,175 +0,0 @@ -% SPDX-License-Identifier: Apache-2.0 OR LicenseRef-MIND-UCAL-1.0 -% © James Ross Ω FLYING•ROBOTS -\NeedsTeXFormat{LaTeX2e} -\ProvidesClass{aion}[2025/12/07 AIΩN Foundations Series Class] - -\LoadClass[11pt]{article} - -% ------------------------------------------------------------ -% Packages -% ------------------------------------------------------------ -\RequirePackage[T1]{fontenc} -\RequirePackage{lmodern} -\RequirePackage{amsmath, amssymb, amsfonts, amsthm} -\RequirePackage{microtype} -\RequirePackage{geometry} -\RequirePackage{xcolor} -\RequirePackage{graphicx} -\RequirePackage{titlesec} -\RequirePackage{tocloft} -\RequirePackage{enumitem} -\RequirePackage{booktabs} -\RequirePackage{chngcntr} -\RequirePackage{hyperref} - -% ------------------------------------------------------------ -% Geometry -% ------------------------------------------------------------ -\geometry{ - margin=1in, -} - -% ------------------------------------------------------------ -% Colors (brand) -% ------------------------------------------------------------ -\definecolor{AIONBlue}{RGB}{20, 60, 120} -\definecolor{AIONAccent}{RGB}{120, 20, 120} - -% ------------------------------------------------------------ -% Hyperref -% ------------------------------------------------------------ -\hypersetup{ - colorlinks=true, - linkcolor=AIONBlue, - citecolor=AIONBlue, - urlcolor=AIONAccent, - hypertexnames=false -} - -% ------------------------------------------------------------ -% Section Titles -% ------------------------------------------------------------ -\titleformat{\section} - {\large\bfseries\color{AIONBlue!85!black}} - {\thesection}{0.5em}{} - -\titleformat{\subsection} - {\normalsize\bfseries\color{AIONBlue!85!black}} - {\thesubsection}{0.5em}{} - -% Reset figure numbering by section for cleaner hyperlinks -\counterwithin{figure}{section} -\renewcommand{\theHfigure}{\thesection.\arabic{figure}} - -% ------------------------------------------------------------ -% Theorem Environments -% ------------------------------------------------------------ -\theoremstyle{definition} -\newtheorem{definition}{Definition}[section] -\newtheorem{assumption}[definition]{Assumption} - -\theoremstyle{plain} -\newtheorem{proposition}[definition]{Proposition} -\newtheorem{theorem}[definition]{Theorem} -\newtheorem{lemma}[definition]{Lemma} -\newtheorem{corollary}[definition]{Corollary} - -\theoremstyle{remark} -\newtheorem{example}[definition]{Example} -\newtheorem{remark}[definition]{Remark} - -% ------------------------------------------------------------ -% Metadata Commands -% ------------------------------------------------------------ -% Internal storage macros (initialized to \@empty for robust checking) -\makeatletter -\newcommand{\AION@papertitle}{\@empty} -\newcommand{\AION@papernumber}{\@empty} -\newcommand{\AION@paperversion}{\@empty} -\newcommand{\AION@paperdate}{\@empty} -\newcommand{\AION@paperauthor}{\@empty} -\newcommand{\AION@paperaffiliation}{\@empty} -\newcommand{\AION@paperorcid}{\@empty} -\newcommand{\AION@paperdoi}{\@empty} - -% User-facing setter commands -\newcommand{\papertitle}[1]{\gdef\AION@papertitle{#1}} -\newcommand{\papernumber}[1]{\gdef\AION@papernumber{#1}} -\newcommand{\paperversion}[1]{\gdef\AION@paperversion{#1}} -\newcommand{\paperdate}[1]{\gdef\AION@paperdate{#1}} -\newcommand{\paperauthor}[1]{\gdef\AION@paperauthor{#1}} -\newcommand{\paperaffiliation}[1]{\gdef\AION@paperaffiliation{#1}} -\newcommand{\paperorcid}[1]{\gdef\AION@paperorcid{#1}} -\newcommand{\paperdoi}[1]{\gdef\AION@paperdoi{#1}} - -% Robust emptiness check using \@empty -\newcommand{\AION@require}[2]{% - \ifx#1\@empty - \ClassError{aion}{#2 not set}{You must call #2 before \string\AIONTitlePage} - \fi -} -\makeatother - -\makeatletter -\newcommand{\AIONTitlePage}{% - \AION@require{\AION@papertitle}{\string\papertitle}% - \AION@require{\AION@papernumber}{\string\papernumber}% - \AION@require{\AION@paperauthor}{\string\paperauthor}% - \AION@require{\AION@paperdate}{\string\paperdate}% - - \thispagestyle{empty} - \begin{center} - - % Nudge the block slightly downward for visual gravity - \vspace*{1.5cm} - - % Title (primary anchor) - {\Huge\bfseries \AION@papertitle \par} - \vspace{14pt} - - % Series / paper number (subtitle energy) - {\normalsize\scshape\color{AIONBlue} - AI$\Omega$N Foundations Series — \AION@papernumber \par} - - \vspace{16pt} - - % Author block (confident, quiet) - {\large - \AION@paperauthor \par} - \vspace{4pt} - - % Only show affiliation/ORCID if defined - \ifx\AION@paperaffiliation\@empty\else - {\normalsize \AION@paperaffiliation \par} - \fi - \ifx\AION@paperorcid\@empty\else - {\normalsize ORCID: \AION@paperorcid \par} - \fi - \ifx\AION@paperdoi\@empty\else - {\normalsize DOI: \AION@paperdoi \par} - \fi - - \vspace{10pt} - - {\normalsize - \AION@paperdate \par} - - \end{center} - \vspace{2cm} -} -\makeatother - -\newcommand{\AIONFrontMatter}[1]{% - \begin{center} - \small - #1 - \end{center} - \vspace{1em} -} - -% ------------------------------------------------------------ -% Table of Contents formatting -% TODO: implement custom TOC styling if needed -% ------------------------------------------------------------ - -\endinput diff --git a/docs/archive/study/build-tour.py b/docs/archive/study/build-tour.py deleted file mode 100644 index a4c1e10a..00000000 --- a/docs/archive/study/build-tour.py +++ /dev/null @@ -1,260 +0,0 @@ -#!/usr/bin/env python3 -# SPDX-License-Identifier: Apache-2.0 -# © James Ross Ω FLYING•ROBOTS -""" -Build the 'What Makes Echo Tick' tour document with: -1. Claude's commentary in red-outlined boxes with RED TEXT -2. PDF diagrams with embedded fonts -3. Letter-size paper with small margins -""" - -import re -import subprocess -import sys -from pathlib import Path - -STUDY_DIR = Path(__file__).parent -DIAGRAMS_DIR = STUDY_DIR / "diagrams" - -INPUT_MD = STUDY_DIR / "what-makes-echo-tick.md" -PROCESSED_MD = STUDY_DIR / "what-makes-echo-tick-processed.md" -OUTPUT_TEX = STUDY_DIR / "what-makes-echo-tick.tex" -OUTPUT_PDF = STUDY_DIR / "what-makes-echo-tick.pdf" - - -def escape_latex(text: str) -> str: - """Escape LaTeX special characters in text.""" - # Use placeholder to avoid double-escaping braces in \textbackslash{} - BACKSLASH_PLACEHOLDER = "\x00BACKSLASH\x00" - text = text.replace('\\', BACKSLASH_PLACEHOLDER) - - replacements = [ - ('&', r'\&'), - ('%', r'\%'), - ('$', r'\$'), - ('#', r'\#'), - ('_', r'\_'), - ('{', r'\{'), - ('}', r'\}'), - ('~', r'\textasciitilde{}'), - ('^', r'\textasciicircum{}'), - ] - for char, replacement in replacements: - text = text.replace(char, replacement) - - return text.replace(BACKSLASH_PLACEHOLDER, r'\textbackslash{}') - - -def convert_commentary_to_latex(md_content: str) -> str: - """Convert CLAUDE_COMMENTARY markers to LaTeX red boxes.""" - - def replace_commentary(match: re.Match[str]) -> str: - inner = match.group(1).strip() - # Escape LaTeX special chars in the commentary content - escaped = escape_latex(inner) - return f'\n\n\\begin{{claudecommentary}}\n{escaped}\n\\end{{claudecommentary}}\n\n' - - # Replace ... - pattern = r'\s*(.*?)\s*' - md_content = re.sub(pattern, replace_commentary, md_content, flags=re.DOTALL) - - return md_content - - -def convert_svg_to_pdf_refs(md_content: str) -> str: - """Convert SVG image references to PDF for LaTeX.""" - md_content = re.sub( - r'\!\[([^\]]*)\]\(diagrams/([^)]+)\.svg\)', - r'![\1](diagrams/\2.pdf)', - md_content - ) - return md_content - - -def run_pandoc(md_file: Path, tex_file: Path) -> bool: - """Run pandoc to convert markdown to LaTeX.""" - try: - result = subprocess.run( - [ - "pandoc", - str(md_file), - "-o", str(tex_file), - "--standalone", - "-f", "markdown+raw_tex", - "--top-level-division=chapter", - "-V", "geometry:margin=0.75in", - "-V", "geometry:letterpaper", - "-V", "fontsize=11pt", - ], - capture_output=True, - text=True, - timeout=60 - ) - if result.returncode != 0: - print(f"pandoc failed: {result.stderr}", file=sys.stderr) - return False - return True - except (subprocess.TimeoutExpired, FileNotFoundError) as e: - print(f"pandoc error: {e}", file=sys.stderr) - return False - - -def postprocess_tex(tex_file: Path) -> None: - """Post-process the LaTeX file.""" - content = tex_file.read_text() - - # Add required packages and styling - # Note: graphicx and geometry are already loaded by Pandoc, so we only add - # adjustbox, tcolorbox, and fvextra here - packages = r""" -\usepackage[export]{adjustbox} -\usepackage{tcolorbox} -\tcbuselibrary{breakable,skins} - -% Make code blocks smaller to fit -\usepackage{fvextra} -\DefineVerbatimEnvironment{Highlighting}{Verbatim}{ - commandchars=\\\{\}, - fontsize=\small, - breaklines=true, - breakanywhere=true -} - -% Define the Claude commentary box style - RED OUTLINE + RED TEXT -\newtcolorbox{claudecommentary}{ - enhanced, - breakable, - colback=red!5, - colframe=red!75!black, - coltext=red!70!black, - boxrule=3pt, - arc=5pt, - left=12pt, - right=12pt, - top=12pt, - bottom=12pt, - before skip=15pt, - after skip=15pt, - fontupper=\color{red!70!black}, - fonttitle=\bfseries\Large\color{red!75!black}, - title={\raisebox{-0.1em}{\Large$\blacktriangleright$} Claude's Commentary}, - attach boxed title to top left={yshift=-4mm,xshift=10mm}, - boxed title style={ - colback=white, - colframe=red!75!black, - boxrule=2pt, - arc=3pt - } -} -""" - - # Insert packages after \documentclass - if r'\usepackage{amsmath' in content: - content = content.replace( - r'\usepackage{amsmath', - packages + r'\usepackage{amsmath' - ) - elif r'\begin{document}' in content: - content = content.replace( - r'\begin{document}', - packages + r'\begin{document}' - ) - - # Fix image includes - make them fit with max width/height - content = re.sub( - r'\\pandocbounded\{\\includegraphics\{([^}]+)\}\}', - r'\\begin{center}\\includegraphics[max width=0.95\\textwidth,max height=0.4\\textheight,keepaspectratio]{\1}\\end{center}', - content - ) - - # Also handle bare includegraphics - content = re.sub( - r'\\includegraphics\{(diagrams/[^}]+)\}', - r'\\begin{center}\\includegraphics[max width=0.95\\textwidth,max height=0.4\\textheight,keepaspectratio]{\1}\\end{center}', - content - ) - - tex_file.write_text(content) - - -def run_xelatex(tex_file: Path) -> bool: - """Run xelatex to produce PDF.""" - try: - for run in [1, 2]: - print(f" xelatex pass {run}...", end=" ", flush=True) - result = subprocess.run( - [ - "xelatex", - "-interaction=nonstopmode", - "-output-directory", str(tex_file.parent), - str(tex_file) - ], - capture_output=True, - text=True, - timeout=120, - cwd=tex_file.parent - ) - success = result.returncode == 0 - print("OK" if success else "warnings") - - pdf_file = tex_file.with_suffix('.pdf') - if not pdf_file.exists(): - print("PDF not generated!", file=sys.stderr) - return False - if not success: - print("xelatex failed on final pass", file=sys.stderr) - return False - return True - - except (subprocess.TimeoutExpired, FileNotFoundError) as e: - print(f"xelatex error: {e}", file=sys.stderr) - return False - - -def main() -> None: - print("=== Building What Makes Echo Tick ===\n") - - if not INPUT_MD.exists(): - print(f"Error: {INPUT_MD} not found", file=sys.stderr) - sys.exit(1) - - # Read the markdown - print(f"1. Reading {INPUT_MD.name}...") - md_content = INPUT_MD.read_text() - - # Convert commentary markers to LaTeX - print("2. Converting Claude commentary to LaTeX red boxes...") - md_content = convert_commentary_to_latex(md_content) - - # Convert SVG refs to PDF - print("3. Converting image references to PDF...") - md_content = convert_svg_to_pdf_refs(md_content) - - # Write processed markdown - PROCESSED_MD.write_text(md_content) - print(f" Wrote {PROCESSED_MD.name}") - - # Run pandoc - print("4. Running pandoc...") - if not run_pandoc(PROCESSED_MD, OUTPUT_TEX): - print(" Pandoc failed!") - sys.exit(1) - print(f" Generated {OUTPUT_TEX.name}") - - # Post-process the LaTeX - print("5. Post-processing LaTeX...") - postprocess_tex(OUTPUT_TEX) - print(" Added red boxes, small margins, fitted graphics") - - # Run xelatex - print("6. Running xelatex...") - if run_xelatex(OUTPUT_TEX): - print("\n=== Success! ===") - print(f"Output: {OUTPUT_PDF}") - else: - print("\n PDF generation may have issues, check .log file") - sys.exit(1) - - -if __name__ == "__main__": - main() diff --git a/docs/archive/study/diagrams/tour-01.mmd b/docs/archive/study/diagrams/tour-01.mmd deleted file mode 100644 index c32c34c1..00000000 --- a/docs/archive/study/diagrams/tour-01.mmd +++ /dev/null @@ -1,39 +0,0 @@ -graph TB - subgraph "Layer 5: Tools & Viewers" - V[warp-viewer] - T[External Tools] - end - - subgraph "Layer 4: Session Protocol" - SS[echo-session-service] - SC[echo-session-client] - WS[WebSocket Gateway] - end - - subgraph "Layer 3: Wire Format" - EG[echo-graph] - SP[echo-session-proto] - end - - subgraph "Layer 2: Storage" - WSC[WSC Format] - CAS[Content-Addressed Store] - end - - subgraph "Layer 1: Core Engine" - E[warp-core Engine] - S[Scheduler] - B[BOAW Executor] - end - - V --> SC - T --> WS - WS --> SS - SC --> SS - SS --> EG - EG --> SP - SP --> E - E --> S - S --> B - B --> WSC - WSC --> CAS \ No newline at end of file diff --git a/docs/archive/study/diagrams/tour-01.pdf b/docs/archive/study/diagrams/tour-01.pdf deleted file mode 100644 index 376eed91aa1b68731847ef480771caa02bd416a9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28280 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 diff --git a/docs/archive/study/diagrams/tour-01.svg b/docs/archive/study/diagrams/tour-01.svg deleted file mode 100644 index 43dfa4e6..00000000 --- a/docs/archive/study/diagrams/tour-01.svg +++ /dev/null @@ -1 +0,0 @@ -

Layer 1: Core Engine

Layer 2: Storage

Layer 3: Wire Format

Layer 4: Session Protocol

Layer 5: Tools & Viewers

warp-viewer

External Tools

echo-session-service

echo-session-client

WebSocket Gateway

echo-graph

echo-session-proto

WSC Format

Content-Addressed Store

warp-core Engine

Scheduler

BOAW Executor

\ No newline at end of file diff --git a/docs/archive/study/diagrams/tour-02.mmd b/docs/archive/study/diagrams/tour-02.mmd deleted file mode 100644 index 65b14c01..00000000 --- a/docs/archive/study/diagrams/tour-02.mmd +++ /dev/null @@ -1,24 +0,0 @@ -sequenceDiagram - participant User - participant Tool as Tool/Viewer - participant Hub as Session Hub - participant Engine as warp-core Engine - participant Store as Graph Store - - User->>Tool: Click link - Tool->>Hub: ingest_intent(bytes) - Hub->>Engine: forward intent - Engine->>Engine: begin() transaction - Engine->>Engine: apply() rules - Engine->>Store: read via GraphView - Engine->>Engine: compute footprints - - rect rgb(240, 248, 255) - Note over Engine,Store: commit() internals - Engine->>Store: apply delta - Engine->>Engine: compute hashes - Engine->>Hub: emit snapshot/diff - end - - Hub->>Tool: WarpFrame - Tool->>User: render new state \ No newline at end of file diff --git a/docs/archive/study/diagrams/tour-02.pdf b/docs/archive/study/diagrams/tour-02.pdf deleted file mode 100644 index 6266c617d17d1b7a21b82a5809091b9e7cde3384..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 35626 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�FrZLA=Pt67Y0vN8?3yvqH<`VZ7X|M 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 Vcl=j!hnmT-^C8pFNGeGo|6fjjq9yGraph Storewarp-core EngineSession HubTool/ViewerUserGraph Storewarp-core EngineSession HubTool/ViewerUserClick linkingest_intent(bytes)forward intentbegin() transactionapply() rulesread via GraphViewcompute footprintscommit()apply deltacompute hashesemit snapshot/diffWarpFramerender new state \ No newline at end of file diff --git a/docs/archive/study/diagrams/tour-03.mmd b/docs/archive/study/diagrams/tour-03.mmd deleted file mode 100644 index 0b045cc4..00000000 --- a/docs/archive/study/diagrams/tour-03.mmd +++ /dev/null @@ -1,20 +0,0 @@ -graph LR - subgraph "WARP Graph Structure" - N1[Node A
id: 0x1234...] - N2[Node B
id: 0x5678...] - N3[Node C
id: 0x9ABC...] - - N1 -->|edge:link| N2 - N1 -->|edge:child| N3 - N2 -->|edge:ref| N3 - end - - subgraph "Attachments (α plane)" - A1[title: 'Home'] - A2[url: '/page/b'] - A3[content: '...'] - end - - N1 -.- A1 - N2 -.- A2 - N3 -.- A3 diff --git a/docs/archive/study/diagrams/tour-03.pdf b/docs/archive/study/diagrams/tour-03.pdf deleted file mode 100644 index 81befb0697d8d30f87ad717e3936f8ca6bcc9a53..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 33507 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

Attachments (α plane)

WARP Graph Structure

edge:link

edge:child

edge:ref

Node A
id: 0x1234...

Node B
id: 0x5678...

Node C
id: 0x9ABC...

title: 'Home'

url: '/page/b'

content: '...'

\ No newline at end of file diff --git a/docs/archive/study/diagrams/tour-04.mmd b/docs/archive/study/diagrams/tour-04.mmd deleted file mode 100644 index d2d04747..00000000 --- a/docs/archive/study/diagrams/tour-04.mmd +++ /dev/null @@ -1,16 +0,0 @@ -graph TB - subgraph "Root Instance (warp_id: 'root')" - R[Root Node] - P1[Page 1] - P2[Page 2] - R --> P1 - R --> P2 - end - - subgraph "Child Instance (warp_id: 'child-abc')" - C1[Child Root] - C2[Child Node] - C1 --> C2 - end - - P2 -.->|"α[portal] = Descend('child-abc')"| C1 diff --git a/docs/archive/study/diagrams/tour-04.pdf b/docs/archive/study/diagrams/tour-04.pdf deleted file mode 100644 index 708fb43904e2e27185dcdc018be9291990b1073c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20119 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 diff --git a/docs/archive/study/diagrams/tour-04.svg b/docs/archive/study/diagrams/tour-04.svg deleted file mode 100644 index 2ea2bd65..00000000 --- a/docs/archive/study/diagrams/tour-04.svg +++ /dev/null @@ -1 +0,0 @@ -

Child Instance (warp_id: 'child-abc')

Root Instance (warp_id: 'root')

α[portal] = Descend('child-abc')

Root Node

Page 1

Page 2

Child Root

Child Node

\ No newline at end of file diff --git a/docs/archive/study/diagrams/tour-05.mmd b/docs/archive/study/diagrams/tour-05.mmd deleted file mode 100644 index d194cb8b..00000000 --- a/docs/archive/study/diagrams/tour-05.mmd +++ /dev/null @@ -1,8 +0,0 @@ -flowchart TD - A[Create GraphStore] --> B[Create WarpState] - B --> C[Create root WarpInstance] - C --> D[Initialize DeterministicScheduler] - D --> E[Create empty rules HashMap] - E --> F[Initialize MaterializationBus] - F --> G[Preserve U0 state for replay] - G --> H[Engine ready] \ No newline at end of file diff --git a/docs/archive/study/diagrams/tour-05.pdf b/docs/archive/study/diagrams/tour-05.pdf deleted file mode 100644 index 65ed032756a2f05947afd685d5381335b9c49f6c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20590 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 M(J~ktD=p3k-&B%hj{pDw diff --git a/docs/archive/study/diagrams/tour-05.svg b/docs/archive/study/diagrams/tour-05.svg deleted file mode 100644 index 2dede56d..00000000 --- a/docs/archive/study/diagrams/tour-05.svg +++ /dev/null @@ -1 +0,0 @@ -

Create GraphStore

Create WarpState

Create root WarpInstance

Initialize DeterministicScheduler

Create empty rules HashMap

Initialize MaterializationBus

Preserve U0 state for replay

Engine ready

\ No newline at end of file diff --git a/docs/archive/study/diagrams/tour-06.mmd b/docs/archive/study/diagrams/tour-06.mmd deleted file mode 100644 index 7bb5c256..00000000 --- a/docs/archive/study/diagrams/tour-06.mmd +++ /dev/null @@ -1,29 +0,0 @@ -flowchart LR - subgraph "1. Begin" - B[begin] - end - - subgraph "2. Apply" - A1[apply rule 1] - A2[apply rule 2] - A3[apply rule N] - end - - subgraph "3. Commit" - C1[Drain] - C2[Reserve] - C3[Execute] - C4[Merge] - C5[Finalize] - end - - subgraph "4. Hash" - H1[State Root] - H2[Commit Hash] - end - - subgraph "5. Record" - R[Append to History] - end - - B --> A1 --> A2 --> A3 --> C1 --> C2 --> C3 --> C4 --> C5 --> H1 --> H2 --> R \ No newline at end of file diff --git a/docs/archive/study/diagrams/tour-06.pdf b/docs/archive/study/diagrams/tour-06.pdf deleted file mode 100644 index 8158912a91f8c583e4b29e7b52f62ad09816bf0e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24688 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

&N?dVmP6+<3gQAjT0O?F;2$$8<2F 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 aH`lo^nOG_tZ>SiK^HEg;18XN6)&Bu&xEFB% diff --git a/docs/archive/study/diagrams/tour-06.svg b/docs/archive/study/diagrams/tour-06.svg deleted file mode 100644 index 1f9e5f03..00000000 --- a/docs/archive/study/diagrams/tour-06.svg +++ /dev/null @@ -1 +0,0 @@ -
5. Record
4. Hash
3. Commit
2. Apply
1. Begin

begin

apply rule 1

apply rule 2

apply rule N

Drain

Reserve

Execute

Merge

Finalize

State Root

Commit Hash

Append to History

\ No newline at end of file diff --git a/docs/archive/study/diagrams/tour-07.mmd b/docs/archive/study/diagrams/tour-07.mmd deleted file mode 100644 index 4752500c..00000000 --- a/docs/archive/study/diagrams/tour-07.mmd +++ /dev/null @@ -1,7 +0,0 @@ -flowchart TD - A[apply called] --> B{Matcher returns true?} - B -->|No| C[Return NoMatch] - B -->|Yes| D[Compute Footprint] - D --> E[Create PendingRewrite] - E --> F[Enqueue to Scheduler] - F --> G[Return Matched] \ No newline at end of file diff --git a/docs/archive/study/diagrams/tour-07.pdf b/docs/archive/study/diagrams/tour-07.pdf deleted file mode 100644 index 2b77302c9a19780c37606d7b1c0bd076812ab056..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20041 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