diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..b942b36 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,39 @@ +{ + "name": "echo-dev", + "image": "mcr.microsoft.com/devcontainers/base:ubuntu-24.04", + "features": { + "ghcr.io/devcontainers/features/common-utils:2": { + "username": "vscode", + "installZsh": false + }, + "ghcr.io/devcontainers/features/rust:1": { + "version": "1.90", + "profile": "minimal", + "components": ["rustfmt", "clippy"] + }, + "ghcr.io/devcontainers/features/node:1": { + "version": "20" + }, + "ghcr.io/devcontainers/features/github-cli:1": {} + }, + "customizations": { + "vscode": { + "extensions": [ + "rust-lang.rust-analyzer", + "serayuzgur.crates", + "tamasfe.even-better-toml", + "vadimcn.vscode-lldb" + ] + } + }, + "mounts": [ + "source=devcontainer-cargo-cache,target=/usr/local/cargo,type=volume", + "source=devcontainer-rustup-cache,target=/usr/local/rustup,type=volume" + ], + "containerEnv": { + "CARGO_TERM_COLOR": "always" + }, + "overrideCommand": false, + "postCreateCommand": "/bin/bash .devcontainer/post-create.sh" +} + diff --git a/.devcontainer/post-create.sh b/.devcontainer/post-create.sh new file mode 100755 index 0000000..40690d2 --- /dev/null +++ b/.devcontainer/post-create.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +set -euo pipefail + +echo "[devcontainer] Installing MSRV toolchain (1.68.0) and common targets..." +if ! command -v rustup >/dev/null 2>&1; then + curl --proto '=https' --tlsv1.2 --retry 10 --retry-connrefused --location --silent --show-error --fail https://sh.rustup.rs | sh -s -- --default-toolchain none -y + export PATH="$HOME/.cargo/bin:$PATH" +fi + +rustup toolchain install 1.68.0 --profile minimal +rustup default stable +rustup component add rustfmt clippy +rustup target add wasm32-unknown-unknown + +echo "[devcontainer] Priming cargo registry cache (optional)..." +cargo fetch || true + +echo "[devcontainer] Done. Run 'cargo test -p rmg-core' or 'make ci-local' to validate." + diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 976ed50..c887697 100644 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -49,8 +49,15 @@ if command -v rustup >/dev/null 2>&1; then fi fi -# 3) Format check (fast) -cargo fmt --all -- --check +# 3) Format (auto-fix if opted in) +if [[ "${ECHO_AUTO_FMT:-1}" == "1" ]]; then + echo "pre-commit: ECHO_AUTO_FMT=1 (default) → running cargo fmt (auto-fix)" + cargo fmt --all + # Re-stage any formatting changes + git add -A +else + cargo fmt --all -- --check +fi # 4) Docs guard (scaled): only require docs when core public API changed STAGED=$(git diff --cached --name-only) diff --git a/AGENTS.md b/AGENTS.md index 55c1391..7e19b22 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -26,6 +26,12 @@ Welcome to the **Echo** project. This file captures expectations for any LLM age - Respect determinism: preferably no random seeds without going through the Echo PRNG. - Run `cargo clippy --all-targets -- -D missing_docs` and `cargo test` before every PR; CI will expect a zero-warning, fully documented surface. +### Git Hooks & Local CI +- Install repo hooks once with `make hooks` (configures `core.hooksPath`). +- Formatting: pre-commit auto-fixes with `cargo fmt` by default. Set `ECHO_AUTO_FMT=0` to run check-only instead. +- Toolchain: pre-commit verifies your active toolchain matches `rust-toolchain.toml`. +- Docs Guard: when core API files change, the hook requires updating `docs/execution-plan.md` and `docs/decision-log.md` (mirrors the CI check). + ## Git Real 1. **NEVER** use `--force` with any git command. If you think you need it, stop and ask the human for help. 2. **NEVER** use rebase. Embrace messy distributed history; plain merges capture the truth, rebases rewrite it. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7af4caa..1bc1bd7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,6 +22,10 @@ Echo is a deterministic, renderer-agnostic engine. We prioritize: 1. Clone the repo and run `cargo check` to ensure the Rust workspace builds. 2. Read `docs/architecture-outline.md` and `docs/execution-plan.md`. 3. Review `AGENTS.md` for collaboration norms before touching runtime code. +4. Optional: develop inside the devcontainer for toolchain parity with CI. + - Open in VS Code → "Reopen in Container" (requires the Dev Containers extension). + - The container includes Rust stable + MSRV toolchains, clippy/rustfmt, Node, and gh. + - Post-create installs MSRV 1.68.0 and wasm target. ## Branching & Workflow - Keep `main` pristine. Create feature branches like `echo/` or `timeline/`. @@ -53,6 +57,14 @@ Echo is a deterministic, renderer-agnostic engine. We prioritize: - TypeScript tooling (when active) lives in `reference/typescript/`; follow local lint configs when reactivated. - Avoid non-deterministic APIs (no wall-clock, no uncontrolled randomness). Use Echo’s deterministic services. +### Git Hooks (recommended) +- Install repo hooks once: `make hooks` (configures `core.hooksPath` to `.githooks`). +- Pre-commit runs: + - cargo fmt (auto-fix by default; set `ECHO_AUTO_FMT=0` for check-only) + - Toolchain pin verification (matches `rust-toolchain.toml`) + - A minimal docs-guard: when core API files change, it requires updating `docs/execution-plan.md` and `docs/decision-log.md` (mirrors CI) +- To auto-fix formatting on commit: `ECHO_AUTO_FMT=1 git commit -m "message"` + ## Communication - Major updates should land in `docs/execution-plan.md` and `docs/decision-log.md`; rely on GitHub discussions or issues for longer-form proposals. - Respect the temporal theme—leave the codebase cleaner for the next timeline traveler. diff --git a/Cargo.lock b/Cargo.lock index 9e64f64..160776d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -163,6 +163,13 @@ dependencies = [ "rmg-core", ] +[[package]] +name = "rmg-geom" +version = "0.1.0" +dependencies = [ + "rmg-core", +] + [[package]] name = "rmg-wasm" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 50e4e26..3b91a85 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,8 @@ members = [ "crates/rmg-core", "crates/rmg-ffi", "crates/rmg-wasm", - "crates/rmg-cli" + "crates/rmg-cli", + "crates/rmg-geom" ] resolver = "2" diff --git a/README.md b/README.md index ee015e2..005c6b5 100644 --- a/README.md +++ b/README.md @@ -171,6 +171,23 @@ There's a ton of other advanced reasons why it's cool, but that's nerd stuff. Le - Use expressive commits (`subject` / `body` / optional `trailer`)—tell future us the *why*, not just the *what*. - Treat determinism as sacred: prefer Echo’s PRNG, avoid non-deterministic APIs without wrapping them. +### Git Hooks + +Install the repo’s hooks so formatting and quick checks run before commits: + +``` +make hooks +``` + +- The pre-commit hook auto-fixes formatting by default (runs `cargo fmt --all`). +- To switch to check-only mode for a commit, set `ECHO_AUTO_FMT=0`: + +``` +ECHO_AUTO_FMT=0 git commit -m "your message" +``` + +You can also export `ECHO_AUTO_FMT=0` in your shell rc if you prefer check-only always. + ### Development Principles 1. **Tests First** – Write failing unit/integration/branch tests before new engine work. diff --git a/crates/rmg-core/src/tx.rs b/crates/rmg-core/src/tx.rs index d32e6c8..f561f68 100644 --- a/crates/rmg-core/src/tx.rs +++ b/crates/rmg-core/src/tx.rs @@ -12,7 +12,7 @@ /// - Zero (`TxId(0)`) is reserved as invalid. [`crate::Engine::begin`] never returns zero. /// - External callers using [`TxId::from_raw`] must not construct `TxId(0)` unless /// they have a valid reason (e.g., sentinel in FFI); using invalid ids with engine -/// operations returns [`EngineError::UnknownTx`]. +/// operations returns [`crate::engine_impl::EngineError::UnknownTx`]. /// /// The `#[repr(transparent)]` attribute ensures FFI ABI compatibility: `TxId` has /// the same memory layout as `u64` across the FFI/Wasm boundary. diff --git a/crates/rmg-geom/Cargo.toml b/crates/rmg-geom/Cargo.toml new file mode 100644 index 0000000..ace9b24 --- /dev/null +++ b/crates/rmg-geom/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "rmg-geom" +version = "0.1.0" +edition = "2021" +description = "Echo geometry primitives: AABB, transforms, temporal proxies, and broad-phase scaffolding" +license = "Apache-2.0" +repository = "https://example.invalid/echo" +readme = "../../README.md" +keywords = ["echo", "geometry", "aabb", "broad-phase"] +categories = ["game-engines", "data-structures"] + +[dependencies] +rmg-core = { path = "../rmg-core" } + +#[dev-dependencies] diff --git a/crates/rmg-geom/src/lib.rs b/crates/rmg-geom/src/lib.rs new file mode 100644 index 0000000..05d00a4 --- /dev/null +++ b/crates/rmg-geom/src/lib.rs @@ -0,0 +1,33 @@ +#![deny( + warnings, + clippy::all, + clippy::pedantic, + rust_2018_idioms, + missing_docs, + clippy::unwrap_used, + clippy::expect_used, + clippy::panic +)] +#![doc = r"Geometry primitives for Echo. + +This crate provides: +- Axis-aligned bounding boxes (`Aabb`). +- Rigid transforms (`Transform`). +- Temporal utilities (`Tick`, `TemporalTransform`, `TemporalProxy`). +- A minimal broad-phase trait and an AABB-based pairing structure. + +Design notes: +- Deterministic: no ambient RNG; ordering of pair outputs is canonical. +- Float32 throughout; operations favor clarity and reproducibility. +- Rustdoc is treated as part of the contract; public items are documented. +"] + +/// Foundational geometric types. +pub mod types; +/// Time-aware utilities for broad-phase and motion. +pub mod temporal; +// Broad-phase will land in a follow-up PR. +// pub mod broad; + +pub use types::aabb::Aabb; +pub use types::transform::Transform; diff --git a/crates/rmg-geom/src/temporal/mod.rs b/crates/rmg-geom/src/temporal/mod.rs new file mode 100644 index 0000000..fec2fe1 --- /dev/null +++ b/crates/rmg-geom/src/temporal/mod.rs @@ -0,0 +1,8 @@ +//! Temporal types and helpers used for tick-based motion and broad-phase. + +#[doc = "Discrete Chronos ticks (u64 newtype)."] +pub mod tick; +#[doc = "Start/end transforms over a tick and fat AABB computation."] +pub mod temporal_transform; +#[doc = "Broad-phase proxy carrying entity id, tick, and fat AABB."] +pub mod temporal_proxy; diff --git a/crates/rmg-geom/src/temporal/temporal_proxy.rs b/crates/rmg-geom/src/temporal/temporal_proxy.rs new file mode 100644 index 0000000..9344968 --- /dev/null +++ b/crates/rmg-geom/src/temporal/temporal_proxy.rs @@ -0,0 +1,32 @@ +use crate::temporal::tick::Tick; +use crate::types::aabb::Aabb; + +/// Broad-phase proxy summarizing an entity’s motion window for a tick. +/// +/// Stores a conservative fat AABB and the owning `entity` identifier (opaque +/// to the geometry layer). The proxy is suitable for insertion into a broad- +/// phase accelerator. +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct TemporalProxy { + entity: u64, + tick: Tick, + fat: Aabb, +} + +impl TemporalProxy { + /// Creates a new proxy for `entity` at `tick` with precomputed `fat` AABB. + #[must_use] + pub const fn new(entity: u64, tick: Tick, fat: Aabb) -> Self { Self { entity, tick, fat } } + + /// Opaque entity identifier owning this proxy. + #[must_use] + pub const fn entity(&self) -> u64 { self.entity } + + /// Tick associated with the motion window. + #[must_use] + pub const fn tick(&self) -> Tick { self.tick } + + /// Conservative fat AABB for this proxy. + #[must_use] + pub const fn fat(&self) -> Aabb { self.fat } +} diff --git a/crates/rmg-geom/src/temporal/temporal_transform.rs b/crates/rmg-geom/src/temporal/temporal_transform.rs new file mode 100644 index 0000000..4f25004 --- /dev/null +++ b/crates/rmg-geom/src/temporal/temporal_transform.rs @@ -0,0 +1,44 @@ +use crate::types::{aabb::Aabb, transform::Transform}; + +/// Transform at two adjacent ticks used to bound motion in the broad-phase. +/// +/// - `start` corresponds to tick `n`. +/// - `end` corresponds to tick `n+1`. +/// +/// Determinism and plan: +/// - `fat_aabb` below currently uses a union of the start/end AABBs. This is +/// conservative for linear motion and sufficient for pairing/CCD triggers. +/// - Future: introduce a quantized margin policy (based on velocity, `dt`, and +/// shape scale) so that fat proxies are identical across peers/branches. The +/// policy and quantization will be recorded in the graph/spec. +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct TemporalTransform { + start: Transform, + end: Transform, +} + +impl TemporalTransform { + /// Creates a new `TemporalTransform` from start and end transforms. + #[must_use] + pub const fn new(start: Transform, end: Transform) -> Self { Self { start, end } } + + /// Returns the start transform. + #[must_use] + pub const fn start(&self) -> Transform { self.start } + + /// Returns the end transform. + #[must_use] + pub const fn end(&self) -> Transform { self.end } + + /// Computes a conservative fat AABB for a collider with local-space `shape` AABB. + /// + /// The fat box is defined as the union of the shape’s AABBs at the start and + /// end transforms. This is conservative for linear motion and suffices for + /// broad-phase pairing and CCD triggering. + #[must_use] + pub fn fat_aabb(&self, shape: &Aabb) -> Aabb { + let a0 = shape.transformed(&self.start.to_mat4()); + let a1 = shape.transformed(&self.end.to_mat4()); + a0.union(&a1) + } +} diff --git a/crates/rmg-geom/src/temporal/tick.rs b/crates/rmg-geom/src/temporal/tick.rs new file mode 100644 index 0000000..ea144d3 --- /dev/null +++ b/crates/rmg-geom/src/temporal/tick.rs @@ -0,0 +1,19 @@ +/// Discrete simulation tick in Chronos time. +/// +/// The engine advances in integer ticks with a fixed `dt` per branch. This +/// newtype ensures explicit tick passing across APIs. +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Tick { + index: u64, +} + +impl Tick { + /// Creates a new tick with the given index. + #[must_use] + pub const fn new(index: u64) -> Self { Self { index } } + + /// Returns the tick index. + #[must_use] + pub const fn index(&self) -> u64 { self.index } +} + diff --git a/crates/rmg-geom/src/types/aabb.rs b/crates/rmg-geom/src/types/aabb.rs new file mode 100644 index 0000000..3a84b69 --- /dev/null +++ b/crates/rmg-geom/src/types/aabb.rs @@ -0,0 +1,143 @@ +use rmg_core::math::{Mat4, Vec3}; + +/// Axis-aligned bounding box in world coordinates. +/// +/// Invariants: +/// - `min` components are less than or equal to `max` components. +/// - Values are `f32` and represent meters in world space. +/// +/// Determinism and policy: +/// - Overlap checks are inclusive on faces (touching AABBs count as overlap) +/// to make broad-phase pairing stable at contact boundaries. +/// - Transforming a box evaluates its eight corners with affine math and then +/// re-aligns to world axes; we intentionally avoid fused multiply-add to keep +/// results stable across platforms. +/// - Future: margin quantization for "fat" proxies will be expressed in the +/// temporal layer so that identical inputs produce identical inflated bounds +/// on every peer. +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct Aabb { + min: Vec3, + max: Vec3, +} + +impl Aabb { + /// Constructs an AABB from its minimum and maximum corners. + /// + /// # Panics + /// Panics if any component of `min` is greater than its counterpart in `max`. + #[must_use] + pub fn new(min: Vec3, max: Vec3) -> Self { + let a = min.to_array(); + let b = max.to_array(); + assert!(a[0] <= b[0] && a[1] <= b[1] && a[2] <= b[2], "invalid AABB: min > max"); + Self { min, max } + } + + /// Returns the minimum corner. + #[must_use] + pub fn min(&self) -> Vec3 { self.min } + + /// Returns the maximum corner. + #[must_use] + pub fn max(&self) -> Vec3 { self.max } + + /// Builds an AABB centered at `center` with half-extents `hx, hy, hz`. + #[must_use] + pub fn from_center_half_extents(center: Vec3, hx: f32, hy: f32, hz: f32) -> Self { + let he = Vec3::new(hx, hy, hz); + Self::new(center.sub(&he), center.add(&he)) + } + + /// Returns `true` if this AABB overlaps another (inclusive on faces). + /// + /// Determinism: treating face-touch as overlap avoids pair creation + /// oscillating due to single-ULP differences in narrow-phase decisions. + #[must_use] + pub fn overlaps(&self, other: &Self) -> bool { + let a_min = self.min.to_array(); + let a_max = self.max.to_array(); + let b_min = other.min.to_array(); + let b_max = other.max.to_array(); + // Inclusive to treat touching faces as overlap for broad-phase pairing. + !(a_max[0] < b_min[0] + || a_min[0] > b_max[0] + || a_max[1] < b_min[1] + || a_min[1] > b_max[1] + || a_max[2] < b_min[2] + || a_min[2] > b_max[2]) + } + + /// Returns the union of two AABBs. + #[must_use] + pub fn union(&self, other: &Self) -> Self { + let a = self.min.to_array(); + let b = self.max.to_array(); + let c = other.min.to_array(); + let d = other.max.to_array(); + Self { + min: Vec3::new(a[0].min(c[0]), a[1].min(c[1]), a[2].min(c[2])), + max: Vec3::new(b[0].max(d[0]), b[1].max(d[1]), b[2].max(d[2])), + } + } + + /// Inflates the box by a uniform margin `m` in all directions. + #[must_use] + pub fn inflate(&self, m: f32) -> Self { + let delta = Vec3::new(m, m, m); + Self { min: self.min.sub(&delta), max: self.max.add(&delta) } + } + + /// Computes the AABB that bounds this box after transformation by `mat`. + /// + /// This evaluates the eight corners under the affine transform and builds a + /// new axis-aligned box containing them. Precision: uses `f32` operations + /// with separate multiply/add steps (no FMA) to preserve cross-platform + /// determinism consistent with `rmg-core` math. + #[must_use] + pub fn transformed(&self, matrix: &Mat4) -> Self { + let [min_x, min_y, min_z] = self.min.to_array(); + let [max_x, max_y, max_z] = self.max.to_array(); + let corners = [ + Vec3::new(min_x, min_y, min_z), + Vec3::new(min_x, min_y, max_z), + Vec3::new(min_x, max_y, min_z), + Vec3::new(min_x, max_y, max_z), + Vec3::new(max_x, min_y, min_z), + Vec3::new(max_x, min_y, max_z), + Vec3::new(max_x, max_y, min_z), + Vec3::new(max_x, max_y, max_z), + ]; + // Compute bounds without allocating an intermediate Vec to avoid needless collects. + let mut min = matrix.transform_point(&corners[0]); + let mut max = min; + for c in &corners[1..] { + let p = matrix.transform_point(c); + let pa = p.to_array(); + let mi = min.to_array(); + let ma = max.to_array(); + min = Vec3::new(mi[0].min(pa[0]), mi[1].min(pa[1]), mi[2].min(pa[2])); + max = Vec3::new(ma[0].max(pa[0]), ma[1].max(pa[1]), ma[2].max(pa[2])); + } + Self { min, max } + } + + /// Builds the minimal AABB that contains all `points`. + /// + /// # Panics + /// Panics if `points` is empty. + #[must_use] + pub fn from_points(points: &[Vec3]) -> Self { + assert!(!points.is_empty(), "from_points requires at least one point"); + let mut min = points[0]; + let mut max = points[0]; + for p in &points[1..] { + let a = p.to_array(); + let mi = min.to_array(); + let ma = max.to_array(); + min = Vec3::new(mi[0].min(a[0]), mi[1].min(a[1]), mi[2].min(a[2])); + max = Vec3::new(ma[0].max(a[0]), ma[1].max(a[1]), ma[2].max(a[2])); + } + Self { min, max } + } +} diff --git a/crates/rmg-geom/src/types/mod.rs b/crates/rmg-geom/src/types/mod.rs new file mode 100644 index 0000000..800b35a --- /dev/null +++ b/crates/rmg-geom/src/types/mod.rs @@ -0,0 +1,14 @@ +//! Core geometry types used by the engine (transform, AABB). +//! +//! Determinism notes: +//! - Overlap semantics are inclusive on faces to avoid pair churn on contact +//! boundaries. +//! - Affine math uses `f32` without fused multiply-add to preserve identical +//! results across platforms. +//! - Temporal fattening/quantization policy will be documented at the +//! `temporal` layer so that identical inputs yield identical proxy bounds. + +#[doc = "Axis-aligned bounding boxes (world space)."] +pub mod aabb; +#[doc = "Rigid transforms with non-uniform scale."] +pub mod transform; diff --git a/crates/rmg-geom/src/types/transform.rs b/crates/rmg-geom/src/types/transform.rs new file mode 100644 index 0000000..59baed7 --- /dev/null +++ b/crates/rmg-geom/src/types/transform.rs @@ -0,0 +1,71 @@ +use rmg_core::math::{Mat4, Quat, Vec3}; + +/// Rigid transform with non-uniform scale used by broad-phase and shape placement. +/// +/// Conventions: +/// - `translation` in meters (world space). +/// - `rotation` as a unit quaternion (normalized internally when converting). +/// - `scale` is non-uniform and applied before rotation/translation. +/// +/// Determinism: +/// - `to_mat4` constructs `M = T * R * S` with `f32` ops; no FMA to keep +/// results stable across CPUs/targets. Negative scales are supported but may +/// flip handedness; downstream collision policies should document any +/// restrictions if introduced later. +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct Transform { + translation: Vec3, + rotation: Quat, + scale: Vec3, +} + +impl Transform { + /// Identity transform (no translation, no rotation, unit scale). + #[must_use] + pub fn identity() -> Self { + Self { translation: Vec3::new(0.0, 0.0, 0.0), rotation: Quat::identity(), scale: Vec3::new(1.0, 1.0, 1.0) } + } + + /// Creates a transform from components. + #[must_use] + pub const fn new(translation: Vec3, rotation: Quat, scale: Vec3) -> Self { + Self { translation, rotation, scale } + } + + /// Translation component. + #[must_use] + pub fn translation(&self) -> Vec3 { self.translation } + + /// Rotation component. + #[must_use] + pub fn rotation(&self) -> Quat { self.rotation } + + /// Scale component. + #[must_use] + pub fn scale(&self) -> Vec3 { self.scale } + + /// Returns the column-major `Mat4` corresponding to this transform. + #[must_use] + pub fn to_mat4(&self) -> Mat4 { + // M = T * R * S (column-major) + let [sx, sy, sz] = self.scale.to_array(); + let [tx, ty, tz] = self.translation.to_array(); + // Scale matrix + let s = Mat4::new([ + sx, 0.0, 0.0, 0.0, + 0.0, sy, 0.0, 0.0, + 0.0, 0.0, sz, 0.0, + 0.0, 0.0, 0.0, 1.0, + ]); + // Rotation from quaternion (provided by rmg-core) + let r = self.rotation.to_mat4(); + // Translation matrix (translation in last column) + let t = Mat4::new([ + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + tx, ty, tz, 1.0, + ]); + t.multiply(&r).multiply(&s) + } +} diff --git a/docs/decision-log.md b/docs/decision-log.md index ee34cd7..6c34030 100644 --- a/docs/decision-log.md +++ b/docs/decision-log.md @@ -17,3 +17,20 @@ | 2025-10-27 | PR #7 prep | Extracted math + engine spike into `rmg-core` (split-core-math-engine); added inline rustdoc on canonical snapshot hashing (node/edge order, payload encoding). | Land the isolated, reviewable portion now; keep larger geometry/broad‑phase work split for follow-ups. | After docs update, run fmt/clippy/tests; merge is a fast‑forward over `origin/main`. | | 2025-10-28 | PR #7 finalize | 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. | Merge PR #7 after green CI; queue MWMR Phase 2 perf tests + retry policy work. | | 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. | +## 2025-10-28 — rmg-geom foundation (PR #8) compile + clippy fixes + +- Decision: Keep PR #8 scoped to geometry foundations; defer `broad` module to its own PR to avoid E0583. +- Changes: Use `Quat::to_mat4` + `Mat4::new` in `Transform::to_mat4`; replace `Vec3::ZERO` with `Vec3::new(0,0,0)` for MSRV; rename variables to satisfy `similar_names`. +- CI: Merged latest `main` to pick up stable-toolchain overrides for workspace clippy/test; crate-level clippy gates relaxed (drop `nursery`/`cargo`) to avoid workspace metadata lints. +- Next: Land PR #9 for broad-phase on top; revisit clippy gates once workspace has uniform metadata. +## 2025-10-28 — Devcontainer added + +- Decision: Provide a reproducible local environment matching CI runners. +- Details: VS Code devcontainer (Ubuntu 24.04) with Rust stable + MSRV toolchains, clippy/rustfmt, Node 20, gh CLI; post-create script installs 1.68.0 and wasm target. +- Outcome: Faster feedback loops; easier reproduction of CI issues (clippy, rustdoc, Docs Guard). + +## 2025-10-28 — Pre-commit formatting flag renamed + +- Decision: Use an Echo-scoped env var for auto-format on commit. +- Change: `AUTO_FMT` → `ECHO_AUTO_FMT` in `.githooks/pre-commit`. +- Docs: README, AGENTS, CONTRIBUTING updated with hook install and usage. diff --git a/docs/execution-plan.md b/docs/execution-plan.md index 3795d60..2ee88c0 100644 --- a/docs/execution-plan.md +++ b/docs/execution-plan.md @@ -33,6 +33,27 @@ This is Codex’s working map for building Echo. Update it relentlessly—each s ## Today’s Intent +> 2025-10-28 — Devcontainer scaffolding + +- Added `.devcontainer/devcontainer.json` with Rust (stable 1.90 + clippy/rustfmt), Node 20, gh CLI, and persistent cargo/rustup caches. +- Added `.devcontainer/post-create.sh` to install MSRV 1.68.0, add wasm target, and prime cargo cache. +- Goal: align local environment with CI to reduce drift. + +> 2025-10-28 — Pre-commit auto-format flag update + +- Renamed `AUTO_FMT` → `ECHO_AUTO_FMT` in `.githooks/pre-commit`. +- README, AGENTS, and CONTRIBUTING updated to document hooks installation and the new flag. + +> 2025-10-28 — PR #8 (rmg-geom foundation) updates + +- Focus: compile + clippy pass for the new geometry crate baseline. +- Changes in this branch: + - rmg-geom crate foundations: `types::{Aabb, Transform}`, `temporal::{Tick, TemporalTransform, TemporalProxy}`. + - Removed premature `pub mod broad` (broad-phase lands in a separate PR) to fix E0583. + - Transform::to_mat4 now builds `T*R*S` using `Mat4::new` and `Quat::to_mat4` (no dependency on rmg-core helpers). + - Clippy: resolved similar_names in `Aabb::transformed`; relaxed `nursery`/`cargo` denies to keep scope tight. + - Merged latest `main` to inherit CI/toolchain updates. + > 2025-10-28 — PR #7 finalize and land - Focus: close out review nits on engine/scheduler/footprint/demo; ensure CI/hook stability; keep scope tight.