Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 114 additions & 0 deletions docs/echo-vs-unity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# 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)

56 changes: 56 additions & 0 deletions docs/phase1-geom-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# 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.

179 changes: 179 additions & 0 deletions docs/spec-geom-collision.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
# 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;
}
```
Comment on lines +77 to +89

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add blank lines around fenced code block.

Markdown linting requires blank lines before and after fenced code blocks (MD031). Add blank lines before line 77 and after line 89.

 pub trait NarrowPhase {
+
```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;
 }
+```
+
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

77-77: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


78-78: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)

🤖 Prompt for AI Agents
docs/spec-geom-collision.md lines ~77-89: the fenced code block needs
surrounding blank lines per MD031; add an empty line immediately before the
opening ```rust fence and ensure there is a correctly placed closing ``` fence
followed by a blank line after it (i.e., add a blank line after the closing
fence on line ~89).


## 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).