diff --git a/.github/DISCUSSION_TEMPLATE/rfc.yml b/.github/DISCUSSION_TEMPLATE/rfc.yml
new file mode 100644
index 0000000..b346ec6
--- /dev/null
+++ b/.github/DISCUSSION_TEMPLATE/rfc.yml
@@ -0,0 +1,32 @@
+title: "RFC:
"
+labels: []
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Use this template to propose a change. Keep it concise and focused.
+ - type: textarea
+ id: summary
+ attributes:
+ label: Summary
+ description: One paragraph overview of the proposal
+ - type: textarea
+ id: motivation
+ attributes:
+ label: Motivation / Problem Statement
+ - type: textarea
+ id: design
+ attributes:
+ label: Proposed Design
+ - type: textarea
+ id: alternatives
+ attributes:
+ label: Alternatives Considered
+ - type: textarea
+ id: impact
+ attributes:
+ label: Risks / Impact
+ - type: textarea
+ id: rollout
+ attributes:
+ label: Rollout Plan
diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml
new file mode 100644
index 0000000..d39a437
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug.yml
@@ -0,0 +1,50 @@
+name: Bug Report
+description: Report a runtime or tooling bug
+title: "bug: "
+labels: [bug]
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Thanks for filing a bug. Please include clear steps to reproduce and expected vs actual behavior.
+ - type: input
+ id: area
+ attributes:
+ label: Area
+ description: Subsystem impacted (runtime, ffi, wasm, geom, cli, docs)
+ placeholder: e.g., runtime/snapshot
+ - type: textarea
+ id: repro
+ attributes:
+ label: Steps to Reproduce
+ description: Provide a minimal reproduction (code, commands, inputs)
+ placeholder: |
+ 1. ...
+ 2. ...
+ - type: textarea
+ id: expected
+ attributes:
+ label: Expected Behavior
+ - type: textarea
+ id: actual
+ attributes:
+ label: Actual Behavior
+ - type: textarea
+ id: stack_trace
+ attributes:
+ label: Stack Trace / Error Logs
+ description: paste full stack trace or logs
+ - type: input
+ id: version
+ attributes:
+ label: Version / Commit
+ description: git SHA or package version
+ - type: checkboxes
+ id: env
+ attributes:
+ label: Environment
+ options:
+ - label: Linux (glibc)
+ - label: Linux (musl)
+ - label: macOS (local)
+ - label: Other
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 0000000..7a379b1
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,5 @@
+blank_issues_enabled: false
+contact_links:
+ - name: RFC (open a discussion)
+ url: https://github.com/flyingrobots/echo/discussions/new?category=ideas
+ about: Propose an RFC via Discussions (Ideas category)
diff --git a/.github/ISSUE_TEMPLATE/feature.yml b/.github/ISSUE_TEMPLATE/feature.yml
new file mode 100644
index 0000000..d0ed2e1
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature.yml
@@ -0,0 +1,22 @@
+name: Feature
+description: Propose a new feature (umbrella)
+title: "feat: "
+labels: [feature]
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Describe the feature at a high level. Keep implementation details minimal; child tasks will capture the steps.
+ - type: textarea
+ id: problem
+ attributes:
+ label: Problem / Motivation
+ - type: textarea
+ id: proposal
+ attributes:
+ label: Proposal / Scope
+ description: Boundaries, non-goals, and acceptance criteria
+ - type: textarea
+ id: links
+ attributes:
+ label: Related Docs/Issues/PRs
diff --git a/.github/ISSUE_TEMPLATE/task.yml b/.github/ISSUE_TEMPLATE/task.yml
new file mode 100644
index 0000000..afa8427
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/task.yml
@@ -0,0 +1,23 @@
+name: Task
+description: Small, bite-sized task (1–3 hours, max 5 per feature)
+title: "task: "
+labels: [task]
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Tasks should be specific and testable. Link to the parent feature issue in the description.
+ - type: input
+ id: parent
+ attributes:
+ label: Parent Feature Issue
+ placeholder: "e.g., #22"
+ - type: textarea
+ id: steps
+ attributes:
+ label: Steps
+ description: Concrete steps to complete this task
+ - type: textarea
+ id: acceptance
+ attributes:
+ label: Acceptance Criteria
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..df2f914
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,18 @@
+Title:
+
+Summary
+- What does this change do in one or two sentences?
+
+Scope
+- [ ] Tests only
+- [ ] Docs only
+- [ ] Runtime code
+
+Links
+- Closes # / Refs #
+
+Checklist
+- [ ] Docs Guard satisfied (updated docs/execution-plan.md and docs/decision-log.md when non-doc files changed)
+- [ ] CI green (fmt, clippy, tests, rustdoc, audit/deny)
+- [ ] Kept PR small and focused (one thing)
+
diff --git a/crates/rmg-core/tests/fixtures/motion-fixtures.json b/crates/rmg-core/tests/fixtures/motion-fixtures.json
deleted file mode 100644
index 7256402..0000000
--- a/crates/rmg-core/tests/fixtures/motion-fixtures.json
+++ /dev/null
@@ -1,167 +0,0 @@
-{
- "cases": [
- {
- "label": "motion-fixture-1",
- "pos": [
- 1.0,
- 2.0,
- 3.0
- ],
- "vel": [
- 0.5,
- -1.0,
- 0.25
- ],
- "expected_pos": [
- 1.5,
- 1.0,
- 3.25
- ]
- },
- {
- "label": "motion-fixture-2",
- "pos": [
- 10.0,
- -2.0,
- 3.5
- ],
- "vel": [
- 0.125,
- 2.0,
- -1.5
- ],
- "expected_pos": [
- 10.125,
- 0.0,
- 2.0
- ]
- },
- {
- "label": "motion-fixture-3",
- "pos": [
- 0.0,
- 0.0,
- 0.0
- ],
- "vel": [
- 0.0,
- 0.0,
- 0.0
- ],
- "expected_pos": [
- 0.0,
- 0.0,
- 0.0
- ]
- },
- {
- "label": "motion-neg-pos",
- "pos": [
- -1.0,
- -2.0,
- -3.0
- ],
- "vel": [
- 0.5,
- -1.0,
- 0.25
- ],
- "expected_pos": [
- -0.5,
- -3.0,
- -2.75
- ]
- },
- {
- "label": "motion-large",
- "pos": [
- 1000000.0,
- -1000000.0,
- 1000000.0
- ],
- "vel": [
- 1000000.0,
- 1000000.0,
- -1000000.0
- ],
- "expected_pos": [
- 2000000.0,
- 0.0,
- 0.0
- ]
- },
- {
- "label": "motion-small",
- "pos": [
- 1e-06,
- -1e-06,
- 1e-06
- ],
- "vel": [
- 1e-06,
- 1e-06,
- -1e-06
- ],
- "expected_pos": [
- 2e-06,
- 0.0,
- 0.0
- ]
- },
- {
- "label": "motion-large+tiny",
- "pos": [
- 1000000.0,
- 1000000.0,
- 1000000.0
- ],
- "vel": [
- 1e-06,
- -1e-06,
- 1e-06
- ],
- "expected_pos": [
- 1000000.0,
- 1000000.0,
- 1000000.0
- ]
- },
- {
- "label": "motion-subnormal",
- "pos": [
- 1.4012985e-45,
- 0.0,
- -1.4012985e-45
- ],
- "vel": [
- 1.4012985e-45,
- 1.4012985e-45,
- 1.4012985e-45
- ],
- "expected_pos": [
- 2.802597e-45,
- 1.4012985e-45,
- 0.0
- ]
- },
- {
- "label": "motion-near-zero-vel",
- "pos": [
- 3.0,
- -2.0,
- 5.0
- ],
- "vel": [
- 1e-12,
- -1e-12,
- 1e-12
- ],
- "expected_pos": [
- 3.0,
- -2.0,
- 5.0
- ]
- }
- ],
- "dt": 1.0
-}
\ No newline at end of file
diff --git a/crates/rmg-core/tests/motion_golden_fixtures.rs b/crates/rmg-core/tests/motion_golden_fixtures.rs
deleted file mode 100644
index 34d5bf6..0000000
--- a/crates/rmg-core/tests/motion_golden_fixtures.rs
+++ /dev/null
@@ -1,113 +0,0 @@
-#![allow(missing_docs)]
-use bytes::Bytes;
-use once_cell::sync::Lazy;
-use serde::Deserialize;
-
-use rmg_core::{
- build_motion_demo_engine, decode_motion_payload, encode_motion_payload, make_node_id,
- make_type_id, ApplyResult, Engine, NodeRecord, MOTION_RULE_NAME,
-};
-
-static RAW: &str = include_str!("fixtures/motion-fixtures.json");
-
-#[derive(Debug, Deserialize)]
-struct MotionCase {
- label: String,
- pos: [f32; 3],
- vel: [f32; 3],
- expected_pos: [f32; 3],
-}
-
-#[derive(Debug, Deserialize)]
-struct MotionFixtures {
- cases: Vec,
-}
-
-static FIXTURES: Lazy =
- Lazy::new(|| serde_json::from_str(RAW).expect("parse motion fixtures"));
-
-#[test]
-fn motion_golden_fixtures_apply_as_expected() {
- let entity_ty = make_type_id("entity");
- let mut engine: Engine = build_motion_demo_engine();
-
- for case in &FIXTURES.cases {
- let ent = make_node_id(&case.label);
- let payload = encode_motion_payload(case.pos, case.vel);
- engine.insert_node(
- ent,
- NodeRecord {
- ty: entity_ty,
- payload: Some(payload),
- },
- );
-
- let tx = engine.begin();
- let res = engine
- .apply(tx, MOTION_RULE_NAME, &ent)
- .unwrap_or_else(|_| panic!("apply motion rule failed for case: {}", case.label));
- assert!(matches!(res, ApplyResult::Applied));
- engine.commit(tx).expect("commit");
-
- let node = engine.node(&ent).expect("node exists");
- let (pos, vel) =
- decode_motion_payload(node.payload.as_ref().expect("payload")).expect("decode");
- for (i, v) in vel.iter().enumerate() {
- assert_eq!(
- v.to_bits(),
- case.vel[i].to_bits(),
- "[{}] velocity[{}] mismatch: got {:?}, expected {:?}",
- case.label,
- i,
- v,
- case.vel[i]
- );
- }
- for (i, p) in pos.iter().enumerate() {
- assert_eq!(
- p.to_bits(),
- case.expected_pos[i].to_bits(),
- "[{}] position[{}] mismatch: got {:?}, expected {:?}",
- case.label,
- i,
- p,
- case.expected_pos[i]
- );
- }
- }
-}
-
-#[test]
-fn motion_apply_no_payload_returns_nomatch() {
- let entity_ty = make_type_id("entity");
- let ent = make_node_id("no-payload");
- let mut engine = build_motion_demo_engine();
- engine.insert_node(
- ent,
- NodeRecord {
- ty: entity_ty,
- payload: None,
- },
- );
- let tx = engine.begin();
- let res = engine.apply(tx, MOTION_RULE_NAME, &ent).expect("apply");
- assert!(matches!(res, ApplyResult::NoMatch));
-}
-
-#[test]
-fn motion_apply_invalid_payload_size_returns_nomatch() {
- let entity_ty = make_type_id("entity");
- let ent = make_node_id("bad-payload");
- let mut engine = build_motion_demo_engine();
- let bad = Bytes::from(vec![0u8; 10]);
- engine.insert_node(
- ent,
- NodeRecord {
- ty: entity_ty,
- payload: Some(bad),
- },
- );
- let tx = engine.begin();
- let res = engine.apply(tx, MOTION_RULE_NAME, &ent).expect("apply");
- assert!(matches!(res, ApplyResult::NoMatch));
-}
diff --git a/docs/blake3-audit-checklist.md b/docs/blake3-audit-checklist.md
deleted file mode 100644
index d80cb12..0000000
--- a/docs/blake3-audit-checklist.md
+++ /dev/null
@@ -1,61 +0,0 @@
-# BLAKE3 Audit Checklist
-
-Use this checklist when modifying hashing- or snapshot-related code. The goal is to preserve Echo’s determinism contract while keeping cryptographic usage correct.
-
-- Hash function
- - [ ] Use BLAKE3 (crate `blake3`) only; do not mix algorithms in snapshot/state hashing.
- - [ ] No keyed hashing in snapshot pipeline (we compute public, verifiable IDs).
-
-- Domain separation and inputs
- - [ ] For typed IDs (node/type/edge), use explicit prefixes (`b"node:", b"type:", b"edge:"`).
- - [ ] For rule IDs, construct by hashing the ASCII name with the `b"rule:"` domain prefix:
- `hasher.update(b"rule:"); hasher.update(name.as_bytes()); let id: [u8;32] = hasher.finalize().into();`
- Reference: crates/rmg-core/build.rs (motion rule family id generation).
- - [ ] For commit header digests (commit_id), include exactly these fields in this order:
- 1) `version: u16 = 1`
- 2) `parents: Vec` encoded as length (u64 LE) then each 32‑byte parent hash
- 3) `state_root: [u8;32]`
- 4) `plan_digest: [u8;32]`
- 5) `decision_digest: [u8;32]`
- 6) `rewrites_digest: [u8;32]`
- 7) `policy_id: u32`
- See docs/spec-merkle-commit.md for the canonical spec.
-
-- Byte order and encoding
- - [ ] All length prefixes are u64 little-endian; IDs are raw 32 bytes.
- - [ ] Deterministic reachability and ordering:
- • Compute the reachable set from the designated root using a deterministic traversal (Echo uses BFS) while tracking a visited set to avoid cycles/duplicates.
- • Include an edge only if both endpoints are in the reachable set.
- • Hash nodes in ascending NodeId order; for each source, hash outgoing edges sorted by ascending EdgeId.
- Note: traversal determines inclusion; ordering is defined by sorted IDs, not traversal order.
-
-- Snapshot invariants
- - [ ] Root ID is included first in the snapshot stream.
- - [ ] Exclude unreachable nodes/edges; filter edges whose destination is unreachable.
- - [ ] For payloads, write length (u64 LE) then exact payload bytes (or 0 if None).
-
-- Header invariants (commit_id)
- - [ ] Version tag is u16 = 1.
- - [ ] Parents: write length (u64 LE) then each 32-byte hash in order.
- - [ ] Append state_root, plan_digest, decision_digest, rewrites_digest, policy_id.
-
-- Tests and fixtures
- - [ ] Add/update golden fixtures for representative graphs when changing encoding.
- - [ ] Update unit tests that assert ordering and reachability behavior.
- - [ ] Record the change and rationale in docs/decision-log.md.
-
-- Tooling
- - [ ] Consider adding a one-off verification script for historical snapshots during migrations.
-
-## Verification and enforcement
-
-- Unit tests (templates)
- - [ ] Golden snapshot fixture: serialize a tiny graph and assert the commit header byte layout (field order, endianness) and hash match expected values.
- - [ ] Reachability tests: assert that unreachable nodes/edges do not affect the hash, and that edges to unreachable targets are excluded (see crates/rmg-core/tests/snapshot_reachability_tests.rs).
- - [ ] Ordering tests: create nodes/edges in shuffled order and assert that the computed hash matches the sorted‑order baseline.
-
-- Assertion helpers (optional)
- - [ ] Add test‑only helpers/macros to validate u64 LE length prefixes, field sequences, and ID sizes at encode time.
-
-- Integration guidance
- - [ ] On format changes, regenerate golden fixtures and add a migration note in docs/decision-log.md. Verify that historical snapshots continue to verify or are migrated with a one‑off script.
diff --git a/docs/decision-log.md b/docs/decision-log.md
index 04110f5..6bfb7f8 100644
--- a/docs/decision-log.md
+++ b/docs/decision-log.md
@@ -21,6 +21,9 @@
The following entries use a heading + bullets format for richer context.
| 2025-10-30 | rmg-core determinism hardening | Added reachability-only snapshot hashing; closed tx lifecycle; duplicate rule detection; deterministic scheduler drain order; expanded motion payload docs; tests for duplicate rule name/id and no‑op commit. | Locks determinism contract and surfaces API invariants; prepares PR #7 for a safe merge train. | Clippy clean for rmg-core; workspace push withheld pending further feedback. |
| 2025-10-30 | Tests | Add golden motion fixtures (JSON) + minimal harness validating motion rule bytes/values | Establishes deterministic test baseline for motion; supports future benches and tooling | No runtime impact; PR-01 linked to umbrella and milestone |
+| 2025-10-30 | Templates PR scope | Clean `echo/pr-templates-and-project` to contain only templates + docs notes; remove unrelated files pulled in by merge; fix YAML lint (trailing blanks; quote placeholder) | Keep PRs reviewable and single-purpose; satisfy CI Docs Guard | Easier review; no runtime impact |
+| 2025-10-30 | Docs lint | Fix MD022 (blank line after headings) in `docs/spec-deterministic-math.md` on branch `echo/docs-math-harness-notes` | Keep markdown lint clean; improve readability | No content change; unblock future docs PRs |
+| 2025-10-30 | Bug template triage | Add optional `stack_trace` and `version` fields to `.github/ISSUE_TEMPLATE/bug.yml` | Capture logs and version/SHA up front to speed debugging | Better triage signal without burdening reporters |
| 2025-10-28 | PR #7 merged | Reachability-only snapshot hashing; ports demo registers rule; guarded ports footprint; scheduler `finalize_tx()` clears `pending`; `PortKey` u30 mask; hooks+CI hardened (toolchain pin, rustdoc fixes). | Determinism + memory hygiene; remove test footguns; pass CI with stable toolchain while keeping rmg-core MSRV=1.68. | Queued follow-ups: #13 (Mat4 canonical zero + MulAssign), #14 (geom train), #15 (devcontainer). |
| 2025-10-27 | MWMR reserve gate | Engine calls `scheduler.finalize_tx()` at commit; compact rule id used on execute path; per‑tx telemetry summary behind feature. | Enforce independence and clear active frontier deterministically; keep ordering stable with `(scope_hash, family_id)`. | Toolchain pinned to Rust 1.68; add design note for telemetry graph snapshot replay. |
@@ -131,3 +134,4 @@ The following entries use a heading + bullets format for richer context.
| 2025-10-30 | CI toolchain simplification | Standardize on Rust `@stable` across CI (fmt, clippy, tests, security audit); remove MSRV job; set `rust-toolchain.toml` to `stable`. | Reduce toolchain drift and recurring audit/MSRV mismatches. | Future MSRV tracking can move to release notes when needed. |
| 2025-10-30 | Rustdoc pedantic cleanup | Snapshot docs clarify `state_root` with code formatting to satisfy `clippy::doc_markdown`. | Keep strict lint gates green; no behavior change. | None. |
| 2025-10-30 | Spec + lint hygiene | Removed duplicate `clippy::module_name_repetitions` allow in `rmg-core/src/lib.rs`. Clarified `docs/spec-merkle-commit.md`: `edge_count` is u64 LE and may be 0; genesis commits have length=0 parents; “empty digest” explicitly defined as `blake3(b"")`; v1 mandates empty `decision_digest` until Aion lands. | Codifies intent; prevents ambiguity for implementers. | No code behavior changes; spec is clearer. |
+| 2025-10-30 | Templates & Project | Added issue/PR/RFC templates and configured Echo Project (Status: Blocked/Ready/Done); fixed YAML lint nits | Streamlines review process and Kanban tracking | No runtime impact; CI docs guard satisfied |
diff --git a/docs/echo-vs-unity.md b/docs/echo-vs-unity.md
deleted file mode 100644
index d391df3..0000000
--- a/docs/echo-vs-unity.md
+++ /dev/null
@@ -1,114 +0,0 @@
-# Echo vs. Unity — Why Echo Is Fundamentally Different
-
-> TL;DR: Unity is an object‑oriented, imperative frame loop over a mutable GameObject tree. Echo is a deterministic, typed graph‑rewriting engine (RMG) where state changes are explicit rewrite rules applied as atomic transactions along a fixed‑tick timeline. Echo treats time, branching, and merging as first‑class citizens.
-
-## One‑Screen Summary
-
-- Data Model
- - Unity: GameObject hierarchy with Components; behavior lives in scripts.
- - Echo: Recursive Meta Graph (RMG). Everything (entities, components, systems, events, proxies, contacts) is a typed graph node or edge.
-- State Change
- - Unity: imperative mutation in `Update()`/`FixedUpdate()`; side effects are common.
- - Echo: Double‑Pushout deterministic rewrites (DPOi). Rules transform subgraphs under snapshot isolation; no hidden mutation.
-- Scheduling
- - Unity: per‑frame script execution order (configurable, but often non‑deterministic in practice).
- - Echo: deterministic local scheduler orders rewrites by `(rule_id, scope_hash)`; disjoint scopes commute.
-- Time (Chronos/Kairos/Aion)
- - Unity: `Time.deltaTime` and frame/update loops; time travel/branching are external.
- - Echo: fixed Chronos ticks; Kairos branches for “what‑ifs”; Aion tags to rank event significance; sub‑tick TOI for CCD.
-- Persistence
- - Unity: scenes/prefabs + runtime state; save systems are custom.
- - Echo: content‑addressed snapshots; identical inputs ⇒ identical snapshot hashes; easy replay/diff.
-- Networking
- - Unity: sync transforms/commands; engine/stack dependent; determinism optional.
- - Echo: Confluence replication — peers apply the same ordered rewrites and converge by construction.
-- Tooling
- - Unity: Inspector reflects live object state; Profiler/Timeline optional.
- - Echo: Inspector reflects the live graph, rewrite stream, determinism hashes, and collision/CCD events.
-- Determinism & Testing
- - Unity: cross‑platform determinism is hard; physics often divergent by platform.
- - Echo: determinism is a design constraint; math, scheduling, PRNG, and collision policies are deterministic and quantized.
-
-## What “Everything Is a Graph” Means
-
-- Typed Nodes: `Transform`, `Collider`, `TemporalProxy`, `PotentialPair`, `Contact`, `ContactEvent`, `System`, `Tick`, etc.
-- Typed Edges: `has_component(entity→transform)`, `produced_in(x→tick)`, `pair_of(pair→a,b)`, `event_of(evt→contact)`.
-- Rewrites: each frame phase is a set of DPO rules (BuildTemporalProxy, BroadPhasePairing, NarrowPhaseDiscrete/CCD, ContactEvents, GC). Rules are pure functions of host graph scope; they emit a new snapshot.
-- Confluence: identical inputs + rule order ⇒ identical graph. Branch merges work because each branch does deterministic rewrites to the same canonical form.
-
-## How “Move an Entity” Differs
-
-- Unity (conceptual):
-```csharp
-// MonoBehaviour script
-void Update() {
- transform.position += velocity * Time.deltaTime;
-}
-```
-- Echo (conceptual):
-```rust
-// A rewrite rule with matcher + executor (see rmg-core demo motion rule)
-// LHS: node with Motion payload; RHS: update position deterministically.
-engine.register_rule(motion_rule());
-let tx = engine.begin();
-engine.apply(tx, "motion/update", &entity_id)?; // enqueues when LHS matches
-let snap = engine.commit(tx)?; // atomic commit + snapshot hash
-```
-Key differences: Echo’s update is a named, scoped rewrite with auditability and a stable hash; the engine controls ordering and applies the rule atomically.
-
-## Time as a First‑Class Concept
-
-- Chronos: fixed‑tick clock; sub‑tick Time‑of‑Impact (TOI) for CCD is quantized to eliminate drift.
-- Kairos: branching points (e.g., alternate inputs or CCD substeps) create speculative timelines; merges are deterministic.
-- Aion: significance decides logging/detail budgets (e.g., bullets get CCD and higher precision; dust particles do not).
-
-## Collision/CCD (Why It Fits Graph Rewrites)
-
-- Broad phase adds `TemporalProxy` and `PotentialPair` nodes; narrow phase adds `Contact` and `Toi` nodes; events add `ContactEvent` nodes. All are created through deterministic rewrite rules with canonical IDs.
-- See: `docs/spec-geom-collision.md` for the rules, IDs, and scheduler mapping.
-
-## Persistence & Networking
-
-- Snapshots are content‑addressed; deterministic rewrites guarantee convergent hashes.
-- Replication: send ordered rewrite packets `{tx_id, rule_id, scope_hash, snapshot_hash}`; peers apply locally; no “state drift by replication schedule”.
-
-## Authoring & Extensibility
-
-- Replace ad‑hoc side effects with explicit rewrite rules.
-- Ports/Adapters isolate non‑deterministic systems (render, input, OS clocks) from the core graph.
-- Tools reason about intent (rules) rather than incidental effects.
-
-## When (Not) to Use Echo
-
-- Use Echo if you need: deterministic multiplayer, time‑travel debugging, massive branching simulations, reproducible simulations, precise merges.
-- Use Unity if you need: integrated editor + full renderer stack today, rich asset pipelines, large plugin ecosystem, rapid prototyping without determinism constraints.
-- Bridge: Echo can power headless sims and feed renderers/tooling via adapters.
-
-## Migration Sketch
-
-- Start by modeling gameplay state as graph nodes/edges.
-- Port side‑effectful logic into rewrite rules with explicit match/replace.
-- Introduce fixed Chronos ticks; route randomness through Echo’s PRNG.
-- Adopt time‑aware collision: fat AABBs, deterministic pairs, quantized TOI.
-- Add Confluence replication; verify identical snapshots across nodes.
-
-## Appendix: Concept Mapping
-
-| Unity Concept | Echo Equivalent |
-| --- | --- |
-| GameObject/Component | Typed nodes in RMG (entity + components as nodes/edges) |
-| Scene | Snapshot (content‑addressed) + graph of assets |
-| Update/FixedUpdate | Deterministic rule scheduler phases |
-| Physics collision callbacks | Contact/ContactEvent nodes via rewrite rules |
-| Prefab | Graph template; rewrite rules to instantiate variants |
-| Undo/Redo | Timeline replay; branch/merge via snapshots |
-| Netcode | Confluence replication of rewrites |
-
----
-
-See also:
-- `docs/architecture-outline.md` (core principles & loop)
-- `docs/spec-rmg-core.md` (RMG, snapshots, confluence)
-- `docs/spec-geom-collision.md` (time‑aware collision & CCD)
-- `docs/phase1-geom-plan.md` (delivery milestones)
-
diff --git a/docs/execution-plan.md b/docs/execution-plan.md
index fd42734..8c0a895 100644
--- a/docs/execution-plan.md
+++ b/docs/execution-plan.md
@@ -39,6 +39,27 @@ This is Codex’s working map for building Echo. Update it relentlessly—each s
- Scope: tests-only; no runtime changes.
- Links: PR-01 and tracking issue are associated for visibility.
+> 2025-10-30 — Templates + Project board (PR: templates)
+
+- Added GitHub templates (Bug, Feature, Task), PR template, and RFC discussion template.
+- Configured Echo Project (Projects v2) Status options to include Blocked/Ready/Done.
+- YAML lint nits fixed (no trailing blank lines; quoted placeholders).
+
+> 2025-10-30 — Templates PR cleanup (scope hygiene)
+
+- Cleaned branch `echo/pr-templates-and-project` to keep "one thing" policy: restored unrelated files to match `origin/main` so this PR only contains templates and the minimal Docs Guard notes.
+- Verified YAML lint feedback: removed trailing blank lines and quoted the `#22` placeholder in Task template.
+- Updated `docs/execution-plan.md` and `docs/decision-log.md` to satisfy Docs Guard for non-doc file changes.
+
+> 2025-10-30 — Deterministic math spec (MD022)
+
+- On branch `echo/docs-math-harness-notes`, fixed Markdown lint MD022 by inserting a blank line after subheadings (e.g., `### Mat3 / Mat4`, `### Quat`, `### Vec2 / Vec3 / Vec4`). No content changes.
+
+> 2025-10-30 — Bug template triage fields
+
+- Enhanced `.github/ISSUE_TEMPLATE/bug.yml` with optional fields for `Stack Trace / Error Logs` and `Version / Commit` to improve first‑pass triage quality.
+
+
> 2025-10-29 — Geom fat AABB midpoint sampling (merge-train)
- Update `rmg-geom::temporal::Timespan::fat_aabb` to union AABBs at start, mid (t=0.5), and end to conservatively bound rotations about off‑centre pivots.
diff --git a/docs/phase1-geom-plan.md b/docs/phase1-geom-plan.md
deleted file mode 100644
index 0c5bfe4..0000000
--- a/docs/phase1-geom-plan.md
+++ /dev/null
@@ -1,56 +0,0 @@
-# Phase 1 Geometry/Collision Plan (Chronos/Kairos/Aion)
-
-## Scope
-- Time‑aware collision pipeline with deterministic broad/narrow phases and CCD.
-- Shapes: AABB, Sphere, Capsule, OBB, Convex Hull (narrow later), Static Mesh BVH.
-- No dynamics/solver in this phase (contacts only); rigid body adapter later.
-
-## Milestones
-- M0 — Scaffolding
- - Crate `rmg-geom` with `types/{transform,aabb,sphere,capsule,obb}.rs`.
- - Temporal types (`TickId`, `TimeSpan`, `TemporalTransform`, `TemporalProxy`).
- - Tests: encoding/decoding, basic overlaps, determinism hashes.
-- M1 — Broad Phase v1
- - Dynamic AABB Tree; deterministic updates and pair emission.
- - Benchmarks vs grid/sap (micro); property tests (pairs invariant).
-- M2 — Narrow Phase v1
- - Sphere/sphere, sphere/AABB, capsule/capsule, OBB/OBB (SAT).
- - Manifold builder (2–4 pts) with stable ordering.
-- M3 — GJK + EPA
- - Convex/convex intersection and penetration; warm‑start cache.
- - Robust tolerances and fallbacks; golden tests for degenerate configs.
-- M4 — CCD v1
- - Conservative advancement; swept sphere/capsule; quantized `toi_s`.
- - Policy thresholds; budget accounting.
-- M5 — Static Mesh BVH
- - Offline/online build; dynamic–static queries; determinism tests.
-- M6 — ECS Integration
- - Components (`Transform`, `Velocity`, `Collider`, `CcdPolicy`), systems (`broad`, `narrow`, `events`).
- - Inspector packet with `ContactEvent`s and hashes.
-- M7 — Hardening
- - Fuzz/property tests; perf passes; docs completion; CI gates and coverage.
-
-## Determinism & Time Contracts
-- Fixed `dt`; CCD substeps bounded and recorded.
-- Stable sorts/tie‑breakers by ids; centralized tolerances in one module.
-- Quantization for `toi_s` and manifold points to avoid drift.
-- Derived state only; contacts/pairs are recomputed per tick.
-
-## Risks & Mitigations
-- Numerical robustness (GJK/EPA/SAT):
- - Use tolerances/quantization; fallback paths; golden tests for degenerate configs.
-- Performance regressions:
- - Bench suites; fat‑AABB sizing tuned; policy‑guided CCD only for fast movers.
-- Determinism under parallelism:
- - Single‑thread by default; island partition + sorted merges for parallel mode.
-
-## Test Strategy
-- Unit tests per module; property tests for overlap symmetry/transitivity.
-- Determinism tests: hash(pairs,contacts) stable across runs/platforms.
-- CCD tests: bullets vs thin walls; quantized `toi_s` repeatability.
-
-## Deliverables
-- `docs/spec-geom-collision.md` (this spec) + API docs in code.
-- `rmg-geom` crate with 90%+ coverage gate; CI lints identical to core.
-- Inspector hooks and example scene demonstrating CCD and Aion tagging.
-
diff --git a/docs/spec-geom-collision.md b/docs/spec-geom-collision.md
deleted file mode 100644
index 73ba122..0000000
--- a/docs/spec-geom-collision.md
+++ /dev/null
@@ -1,179 +0,0 @@
-# Time‑Aware Geometry, Collision, and CCD (Deterministic v1)
-
-> Chronos (sequence) drives simulation, Kairos (possibility) enables branching, Aion (significance) informs precision and logging. This spec defines Echo’s deterministic, time‑aware collision pipeline and APIs.
-
-This module is a graph‑first design: every artifact (proxies, pairs, contacts,
-events, policies) is a typed node/edge within the world’s Recursive Meta Graph
-and created via deterministic rewrite rules.
-
-## Goals
-- Deterministic across platforms and runs; branchable and mergeable.
-- Minimal, composable primitives: geometry types, broad/narrow phases, CCD, and contact events.
-- ECS‑friendly API; one concept per file; tests separate from code.
-- Document tolerances and quantization to eliminate drift.
-
-## Terminology
-- Chronos: fixed timestep driving the engine (`dt`, `TickId`).
-- Kairos: branching timelines/speculative substeps.
-- Aion: event significance; which effects persist across branches and are surfaced to tools.
-
-## Temporal Types
-- `TickId(u64)`: discrete tick index.
-- `TimeSpan { tick: TickId, dt: f32 }`: tick duration.
-- `TemporalTransform { start: Transform, end: Transform }`: pose over a tick.
-- `TemporalProxy { aabb_fat: Aabb, id: ProxyId, layers: u64 }`: broad‑phase proxy fattened to cover motion over `[start,end]`.
-- `ContactId(Hash)`: stable hash of (ProxyId A, ProxyId B, feature ids, branch id).
-- `Toi { s: f32, normal: Vec3, point: Vec3 }`: time‑of‑impact in `[0,1]` with contact info.
-
-## Determinism Invariants
-1. Fixed `dt` in core; substeps only through CCD with capped, recorded iterations.
-2. Stable sorts everywhere (proxies, pairs, features, events); ties break by ids.
-3. Tolerances centralized and quantized: overlap epsilon, TOI epsilon, manifold reduction.
-4. Contacts, pairs, and manifolds are derived state each tick; not authoritative.
-5. Identical inputs + rules ⇒ identical `Contact` and `ContactEvent` sequences and hashes.
-
-## Geometry Types
-- `Transform` (position `Vec3`, rotation `Quat`, scale `Vec3`).
-- Primitive volumes: `Aabb`, `Sphere`, `Capsule`, `Obb`, `Hull` (convex).
-- Static mesh: triangle lists + immutable BVH (for environment colliders).
-
-## Broad Phase (Chronos‑aware)
-- Dynamic AABB Tree (default)
- - Update proxies with fat AABBs based on velocity and angular bounds: `pad = v_max*dt + rot_margin` (quantized).
- - Deterministic updates: sorted insert/remove by `ProxyId`.
- - Outputs deterministic `PotentialPair { a, b }[]` sorted by (min_id, max_id).
-- Sweep and Prune (optional)
- - Stable sort by endpoints, then by (id, axis). Scan for overlaps on `[start,end]`.
-- Spatial Hash Grid (optional)
- - Canonical neighbor iteration order; fixed cell hashing.
-- Static BVH for meshes
- - Prebuilt at load; deterministic traversal order and culling stats.
-
-## Narrow Phase (Precise)
-- Fast paths: sphere/sphere, sphere/AABB, capsule/capsule, OBB/OBB (SAT).
-- Convex–convex: GJK for intersection/closest, EPA for penetration depth/direction.
-- Manifold builder: clip and reduce to 2–4 points; stable point ordering by feature ids; quantized outputs.
-
-## CCD
-- Strategy: Conservative Advancement (CA) for general convex shapes; swept tests for spheres/capsules.
-- Output: `Toi { s, normal, point }` with `s` quantized to bins in `[0,1]`.
-- Policy (deterministic): CCD when any holds: `|v|*dt > size_thresh`, `ang*dt > angle_thresh`, or material requires CCD.
-
-## Events (Temporal Bridge)
-- `ContactEvent` kinds: `Begin { toi_s }`, `Persist`, `End`.
-- Emission order per tick: sort by `(toi_s, ContactId)`; include Aion tags (materials/layers).
-- Inspector packets include hashes of pair order and contact manifolds for divergence checks.
-
-## ECS Integration (Phase 1 scope)
-- Components
- - `Transform`, `Velocity` (for CCD), `Collider { shape, layer_mask, material, aabb_cache }`.
- - Internal staging: `PotentialPairs`, `Contacts`.
- - Policy: `CcdPolicy { thresholds, quant_bins }`.
-- Systems
- - `broad_phase_system`: builds/updates proxies → emits deterministic `PotentialPairs`.
- - `narrow_phase_system`: consumes pairs → writes `Contacts` with manifolds/TOI.
- - `events_system`: compares last vs current contacts → emits `ContactEvent`s with Aion tagging.
-
-## Public Traits (sketch)
-```rust
-pub trait BroadPhase {
- fn update(&mut self, tick: TickId, proxies: &[TemporalProxy]);
- fn find_pairs(&self, out: &mut Vec);
-}
-
-pub trait NarrowPhase {
- fn collide(&mut self, a: &Collider, ta: &TemporalTransform,
- b: &Collider, tb: &TemporalTransform,
- policy: &CcdPolicy) -> ContactOutcome;
-}
-```
-
-## Instrumentation
-- Per tick: proxies, pairs, CCD count, average `toi_s`, substeps, temporal budget usage.
-- Hashes: `hash_pairs`, `hash_contacts` for quick determinism checks.
-
-## Open Questions
-- Exact quantization bins for `toi_s` (power‑of‑two vs decimal) and impact on merging.
-- Obb/Obb preference: SAT vs GJK (choose based on shape type or heuristics?).
-- Mesh collider scope in v1 (static only vs limited dynamic with compound shapes).
-- Parallelization strategy and island sorting without violating determinism.
--
-
-## Graph Mapping (Everything is a Graph)
-
-Typed nodes (with indicative fields):
-- `Tick` { id: TickId, dt: f32 }
-- `Transform` { entity: NodeId, pos: Vec3, rot: Quat, scale: Vec3 }
-- `Velocity` { entity: NodeId, lin: Vec3, ang: Vec3 }
-- `Collider` { entity: NodeId, shape: ShapeRef, layer_mask: u64, material: Hash }
-- `TemporalProxy` { id: TemporalProxyId, entity: NodeId, tick: TickId, aabb_fat: Aabb }
-- `PotentialPair` { id: PairId, a: NodeId, b: NodeId, tick: TickId }
-- `Contact` { id: ContactId, pair: PairId, tick: TickId, manifold: Manifold }
-- `Toi` { pair: PairId, tick: TickId, s: Quantized, normal: Vec3, point: Vec3 }
-- `ContactEvent` { kind: Begin|Persist|End, tick: TickId, pair: PairId, toi_s: Quantized }
-- `CcdPolicy` { thresholds, quant_bins }
-- `Material`, `Layer` (Aion tagging sources)
-
-Typed edges (examples):
-- `has_component(entity → Transform|Velocity|Collider)`
-- `produced_in(x → Tick)` for all temporal artifacts
-- `has_proxy(entity → TemporalProxy)`
-- `pair_of(PotentialPair → a,b)`
-- `contact_of(Contact → PotentialPair)`
-- `event_of(ContactEvent → Contact)`
-- `policy_for(CcdPolicy → Layer|Material)`
-
-Deterministic ID recipes:
-- `PairId = H(min(entityA,entityB) || max(entityA,entityB) || branch_id)`
-- `ContactId = H(PairId || feature_ids || branch_id)`
-- `TemporalProxyId = H(entity_id || tick_id || branch_id)`
-
-## Rewrite Rules (DPOi sketches)
-
-BuildTemporalProxy (pre_update):
-- LHS: `Collider(e)`, `Transform(e)`, optional `Velocity(e)`, `Tick(n)`; no `TemporalProxy(e,n)`
-- K: `Collider(e)`, `Transform(e)`, `Tick(n)`
-- RHS: add `TemporalProxy(e,n)` with fat AABB; `has_proxy(e→proxy)`, `produced_in(proxy→Tick n)`
-
-BroadPhasePairing (update):
-- LHS: `TemporalProxy(a,n)`, `TemporalProxy(b,n)` with overlapping fat AABBs; no `PotentialPair(a,b,n)`
-- K: both proxies
-- RHS: add `PotentialPair(a,b,n)`; `pair_of(pair→a,b)`, `produced_in(pair→Tick n)`
-
-NarrowPhaseDiscrete (update):
-- LHS: `PotentialPair(a,b,n)`; discrete test says overlap at end pose; no `Contact(pair,n)`
-- K: pair
-- RHS: add `Contact(pair,n)` with `Manifold`; `contact_of(contact→pair)`, `produced_in(contact→Tick n)`
-
-NarrowPhaseCCD (update):
-- LHS: `PotentialPair(a,b,n)`; CCD policy says “run CA/swept”
-- K: pair
-- RHS: if intersect at `toi_s<1`: add/update `Toi(pair,n)`, `Contact(pair,n)`; else ensure `Contact` absent
-
-ContactEvents (post_update):
-- LHS: `Contact(pair,n−1)` and `Contact(pair,n)` (or absence)
-- K: pair
-- RHS: add `ContactEvent(kind, pair, tick=n, toi_s)`; link to `Contact` and `Tick`
-
-GC Ephemeral (timeline_flush):
-- LHS: `TemporalProxy|PotentialPair|Toi|Contact` older than retention and unreferenced
-- RHS: delete node (deterministic order by id)
-
-## Scheduler Phase Mapping
-- `pre_update`: BuildTemporalProxy
-- `update`: BroadPhasePairing → NarrowPhaseDiscrete/CCD (sorted scopes)
-- `post_update`: ContactEvents (sort by `(toi_s, ContactId)`)
-- `timeline_flush`: GC Ephemeral; persist Aion‑worthy events/metrics
-
-## Diagrams (SVG)
-
-- BuildTemporalProxy: docs/assets/collision/dpo_build_temporal_proxy.svg
-- BroadPhasePairing: docs/assets/collision/dpo_broad_phase_pairing.svg
-- NarrowPhaseDiscrete: docs/assets/collision/dpo_narrow_phase_discrete.svg
-- NarrowPhaseCCD: docs/assets/collision/dpo_narrow_phase_ccd.svg
-- ContactEvents: docs/assets/collision/dpo_contact_events.svg
-- GC Ephemeral: docs/assets/collision/dpo_gc_ephemeral.svg
-- Phase Mapping: docs/assets/collision/scheduler_phase_mapping.svg
-
-These SVGs carry semantic classes (node, edge, interfaceK, added, removed, scope) and optional animation hooks (pulse-add/pulse-remove). You can style/animate them via CSS using docs/assets/collision/diagrams.css.
-- See the visual tour for step‑by‑step DPO rules and world views: [`collision-dpo-tour.html`](./collision-dpo-tour.html).