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: <short description>" +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: <short description>" +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: <short description>" +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: <type(scope): short summary> + +Summary +- What does this change do in one or two sentences? + +Scope +- [ ] Tests only +- [ ] Docs only +- [ ] Runtime code + +Links +- Closes #<issue> / Refs #<issue> + +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<MotionCase>, -} - -static FIXTURES: Lazy<MotionFixtures> = - 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<Hash>` 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<PotentialPair>); -} - -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<f32>, normal: Vec3, point: Vec3 } -- `ContactEvent` { kind: Begin|Persist|End, tick: TickId, pair: PairId, toi_s: Quantized<f32> } -- `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).