From cbc0242bbfe0a0f12ddbcddc24a41b0e0c00f008 Mon Sep 17 00:00:00 2001 From: "J. Kirby Ross" Date: Mon, 27 Oct 2025 01:35:36 -0700 Subject: [PATCH 01/14] feat(geom): add rmg-geom foundation (split PR 2)\n\n- types: Aabb, Transform\n- temporal: Tick, TemporalTransform, TemporalProxy\n- workspace: add rmg-geom member\n\nExtracted from branch echo/geom-broad-phase-docs-smoke. --- Cargo.toml | 3 +- crates/rmg-geom/Cargo.toml | 15 ++ crates/rmg-geom/src/lib.rs | 35 +++++ crates/rmg-geom/src/temporal/mod.rs | 8 ++ .../rmg-geom/src/temporal/temporal_proxy.rs | 32 +++++ .../src/temporal/temporal_transform.rs | 38 ++++++ crates/rmg-geom/src/temporal/tick.rs | 19 +++ crates/rmg-geom/src/types/aabb.rs | 128 ++++++++++++++++++ crates/rmg-geom/src/types/mod.rs | 6 + crates/rmg-geom/src/types/transform.rs | 52 +++++++ 10 files changed, 335 insertions(+), 1 deletion(-) create mode 100644 crates/rmg-geom/Cargo.toml create mode 100644 crates/rmg-geom/src/lib.rs create mode 100644 crates/rmg-geom/src/temporal/mod.rs create mode 100644 crates/rmg-geom/src/temporal/temporal_proxy.rs create mode 100644 crates/rmg-geom/src/temporal/temporal_transform.rs create mode 100644 crates/rmg-geom/src/temporal/tick.rs create mode 100644 crates/rmg-geom/src/types/aabb.rs create mode 100644 crates/rmg-geom/src/types/mod.rs create mode 100644 crates/rmg-geom/src/types/transform.rs 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/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..48b9a48 --- /dev/null +++ b/crates/rmg-geom/src/lib.rs @@ -0,0 +1,35 @@ +#![deny( + warnings, + clippy::all, + clippy::pedantic, + clippy::nursery, + clippy::cargo, + 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 interfaces and a simple AABB-based implementation. +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..5eec5c8 --- /dev/null +++ b/crates/rmg-geom/src/temporal/temporal_transform.rs @@ -0,0 +1,38 @@ +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`. +#[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..db12ab7 --- /dev/null +++ b/crates/rmg-geom/src/types/aabb.rs @@ -0,0 +1,128 @@ +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. +#[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). + #[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. + #[must_use] + pub fn transformed(&self, mat: &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 = mat.transform_point(&corners[0]); + let mut max = min; + for c in &corners[1..] { + let p = mat.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..62434cb --- /dev/null +++ b/crates/rmg-geom/src/types/mod.rs @@ -0,0 +1,6 @@ +//! Core geometry types used by the engine (transform, AABB). + +#[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..a1b6ee5 --- /dev/null +++ b/crates/rmg-geom/src/types/transform.rs @@ -0,0 +1,52 @@ +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. +#[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::ZERO, 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 + let [sx, sy, sz] = self.scale.to_array(); + let [tx, ty, tz] = self.translation.to_array(); + let s = Mat4::scale(sx, sy, sz); + let r = Mat4::from_quat(&self.rotation); + let t = Mat4::translation(tx, ty, tz); + t.multiply(&r).multiply(&s) + } +} From 8832650a14abc576d391667a0f5eb2c709c88a39 Mon Sep 17 00:00:00 2001 From: "J. Kirby Ross" Date: Mon, 27 Oct 2025 01:49:17 -0700 Subject: [PATCH 02/14] docs(geom): add determinism/quantization notes to AABB + Transform + TemporalTransform + module docs\n\n- Clarify inclusive overlap, FMA avoidance, and future fat-proxy quantization policy\n- Aligns with broad-phase determinism contract --- .../rmg-geom/src/temporal/temporal_transform.rs | 8 +++++++- crates/rmg-geom/src/types/aabb.rs | 17 ++++++++++++++++- crates/rmg-geom/src/types/mod.rs | 8 ++++++++ crates/rmg-geom/src/types/transform.rs | 6 ++++++ 4 files changed, 37 insertions(+), 2 deletions(-) diff --git a/crates/rmg-geom/src/temporal/temporal_transform.rs b/crates/rmg-geom/src/temporal/temporal_transform.rs index 5eec5c8..4f25004 100644 --- a/crates/rmg-geom/src/temporal/temporal_transform.rs +++ b/crates/rmg-geom/src/temporal/temporal_transform.rs @@ -4,6 +4,13 @@ use crate::types::{aabb::Aabb, transform::Transform}; /// /// - `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, @@ -35,4 +42,3 @@ impl TemporalTransform { a0.union(&a1) } } - diff --git a/crates/rmg-geom/src/types/aabb.rs b/crates/rmg-geom/src/types/aabb.rs index db12ab7..b7ed8ad 100644 --- a/crates/rmg-geom/src/types/aabb.rs +++ b/crates/rmg-geom/src/types/aabb.rs @@ -5,6 +5,16 @@ use rmg_core::math::{Mat4, Vec3}; /// 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, @@ -40,6 +50,9 @@ impl Aabb { } /// 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(); @@ -78,7 +91,9 @@ impl Aabb { /// 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. + /// 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, mat: &Mat4) -> Self { let [min_x, min_y, min_z] = self.min.to_array(); diff --git a/crates/rmg-geom/src/types/mod.rs b/crates/rmg-geom/src/types/mod.rs index 62434cb..800b35a 100644 --- a/crates/rmg-geom/src/types/mod.rs +++ b/crates/rmg-geom/src/types/mod.rs @@ -1,4 +1,12 @@ //! 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; diff --git a/crates/rmg-geom/src/types/transform.rs b/crates/rmg-geom/src/types/transform.rs index a1b6ee5..5f907c3 100644 --- a/crates/rmg-geom/src/types/transform.rs +++ b/crates/rmg-geom/src/types/transform.rs @@ -6,6 +6,12 @@ use rmg_core::math::{Mat4, Quat, Vec3}; /// - `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, From a086c568cabf7afb928760783f6a218dd28cddd7 Mon Sep 17 00:00:00 2001 From: "J. Kirby Ross" Date: Tue, 28 Oct 2025 11:24:19 -0700 Subject: [PATCH 03/14] geom(foundation): fix compile + clippy for PR #8\n\n- Remove unused (broad-phase lands in separate PR)\n- Transform::identity: replace Vec3::ZERO with Vec3::new(0,0,0) for MSRV\n- Transform::to_mat4: avoid Mat4 helpers not present in base by using Mat4::new + Quat::to_mat4\n- Aabb::transformed: rename param to to satisfy clippy::similar_names\n- Relax clippy gates: drop and Rust's package manager Usage: cargo [+toolchain] [OPTIONS] [COMMAND] cargo [+toolchain] [OPTIONS] -Zscript [ARGS]... Options: -V, --version Print version info and exit --list List installed commands --explain Provide a detailed explanation of a rustc error message -v, --verbose... Use verbose output (-vv very verbose/build.rs output) -q, --quiet Do not print cargo log messages --color Coloring [possible values: auto, always, never] -C Change to DIRECTORY before doing anything (nightly-only) --locked Assert that `Cargo.lock` will remain unchanged --offline Run without accessing the network --frozen Equivalent to specifying both --locked and --offline --config Override a configuration value -Z Unstable (nightly-only) flags to Cargo, see 'cargo -Z help' for details -h, --help Print help Commands: build, b Compile the current package check, c Analyze the current package and report errors, but don't build object files clean Remove the target directory doc, d Build this package's and its dependencies' documentation new Create a new cargo package init Create a new cargo package in an existing directory add Add dependencies to a manifest file remove Remove dependencies from a manifest file run, r Run a binary or example of the local package test, t Run the tests bench Run the benchmarks update Update dependencies listed in Cargo.lock search Search registry for crates publish Package and upload this package to the registry install Install a Rust binary uninstall Uninstall a Rust binary ... See all commands with --list See 'cargo help ' for more information on a specific command. from deny to avoid cross-workspace metadata lints --- Cargo.lock | 7 +++++++ crates/rmg-geom/src/lib.rs | 6 ++---- crates/rmg-geom/src/types/aabb.rs | 6 +++--- crates/rmg-geom/src/types/transform.rs | 23 ++++++++++++++++++----- 4 files changed, 30 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aa0e4ad..ee583e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -162,6 +162,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/crates/rmg-geom/src/lib.rs b/crates/rmg-geom/src/lib.rs index 48b9a48..05d00a4 100644 --- a/crates/rmg-geom/src/lib.rs +++ b/crates/rmg-geom/src/lib.rs @@ -2,8 +2,6 @@ warnings, clippy::all, clippy::pedantic, - clippy::nursery, - clippy::cargo, rust_2018_idioms, missing_docs, clippy::unwrap_used, @@ -28,8 +26,8 @@ Design notes: pub mod types; /// Time-aware utilities for broad-phase and motion. pub mod temporal; -/// Broad-phase interfaces and a simple AABB-based implementation. -pub mod broad; +// 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/types/aabb.rs b/crates/rmg-geom/src/types/aabb.rs index b7ed8ad..3a84b69 100644 --- a/crates/rmg-geom/src/types/aabb.rs +++ b/crates/rmg-geom/src/types/aabb.rs @@ -95,7 +95,7 @@ impl Aabb { /// with separate multiply/add steps (no FMA) to preserve cross-platform /// determinism consistent with `rmg-core` math. #[must_use] - pub fn transformed(&self, mat: &Mat4) -> Self { + 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 = [ @@ -109,10 +109,10 @@ impl Aabb { Vec3::new(max_x, max_y, max_z), ]; // Compute bounds without allocating an intermediate Vec to avoid needless collects. - let mut min = mat.transform_point(&corners[0]); + let mut min = matrix.transform_point(&corners[0]); let mut max = min; for c in &corners[1..] { - let p = mat.transform_point(c); + let p = matrix.transform_point(c); let pa = p.to_array(); let mi = min.to_array(); let ma = max.to_array(); diff --git a/crates/rmg-geom/src/types/transform.rs b/crates/rmg-geom/src/types/transform.rs index 5f907c3..59baed7 100644 --- a/crates/rmg-geom/src/types/transform.rs +++ b/crates/rmg-geom/src/types/transform.rs @@ -23,7 +23,7 @@ impl Transform { /// Identity transform (no translation, no rotation, unit scale). #[must_use] pub fn identity() -> Self { - Self { translation: Vec3::ZERO, rotation: Quat::identity(), scale: Vec3::new(1.0, 1.0, 1.0) } + 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. @@ -47,12 +47,25 @@ impl Transform { /// Returns the column-major `Mat4` corresponding to this transform. #[must_use] pub fn to_mat4(&self) -> Mat4 { - // M = T * R * S + // M = T * R * S (column-major) let [sx, sy, sz] = self.scale.to_array(); let [tx, ty, tz] = self.translation.to_array(); - let s = Mat4::scale(sx, sy, sz); - let r = Mat4::from_quat(&self.rotation); - let t = Mat4::translation(tx, ty, tz); + // 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) } } From 025ede882d0db2561f1738b47b94398e23424781 Mon Sep 17 00:00:00 2001 From: "J. Kirby Ross" Date: Tue, 28 Oct 2025 11:46:21 -0700 Subject: [PATCH 04/14] docs(PR #8): update execution plan + decision log per docs-guard --- docs/decision-log.md | 6 ++++++ docs/execution-plan.md | 10 ++++++++++ 2 files changed, 16 insertions(+) diff --git a/docs/decision-log.md b/docs/decision-log.md index ee34cd7..1f9be68 100644 --- a/docs/decision-log.md +++ b/docs/decision-log.md @@ -17,3 +17,9 @@ | 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. diff --git a/docs/execution-plan.md b/docs/execution-plan.md index 3795d60..1abf0de 100644 --- a/docs/execution-plan.md +++ b/docs/execution-plan.md @@ -33,6 +33,16 @@ This is Codex’s working map for building Echo. Update it relentlessly—each s ## Today’s Intent +> 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. From 7f4188bec9b9a4bdefc21c2db82826d7aad9afb3 Mon Sep 17 00:00:00 2001 From: "J. Kirby Ross" Date: Tue, 28 Oct 2025 11:50:19 -0700 Subject: [PATCH 05/14] devcontainer: add VS Code devcontainer (Ubuntu 24.04) + post-create MSRV setup; update execution plan and decision log --- .devcontainer/devcontainer.json | 39 +++++++++++++++++++++++++++++++++ .devcontainer/post-create.sh | 19 ++++++++++++++++ docs/decision-log.md | 5 +++++ docs/execution-plan.md | 6 +++++ 4 files changed, 69 insertions(+) create mode 100644 .devcontainer/devcontainer.json create mode 100755 .devcontainer/post-create.sh 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/docs/decision-log.md b/docs/decision-log.md index 1f9be68..8929419 100644 --- a/docs/decision-log.md +++ b/docs/decision-log.md @@ -23,3 +23,8 @@ - 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). diff --git a/docs/execution-plan.md b/docs/execution-plan.md index 1abf0de..0cc463c 100644 --- a/docs/execution-plan.md +++ b/docs/execution-plan.md @@ -33,6 +33,12 @@ 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 — PR #8 (rmg-geom foundation) updates - Focus: compile + clippy pass for the new geometry crate baseline. From 6938f00ee7f26bcf0e562a26906b32060bad9c03 Mon Sep 17 00:00:00 2001 From: "J. Kirby Ross" Date: Tue, 28 Oct 2025 11:52:48 -0700 Subject: [PATCH 06/14] docs(rmg-core/tx): fix rustdoc broken link to EngineError::UnknownTx via fully-qualified path --- crates/rmg-core/src/tx.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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. From 98626780ed1c0365fe149fcba459b121f46bb73d Mon Sep 17 00:00:00 2001 From: "J. Kirby Ross" Date: Tue, 28 Oct 2025 13:14:17 -0700 Subject: [PATCH 07/14] hooks(pre-commit): integrate cargo fmt (auto-fix with AUTO_FMT=1) and re-stage; README: add hooks install + AUTO_FMT instructions --- .githooks/pre-commit | 11 +++++++++-- README.md | 17 +++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 976ed50..d97ed9f 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 [[ "${AUTO_FMT:-0}" == "1" ]]; then + echo "pre-commit: AUTO_FMT=1 → 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/README.md b/README.md index ee015e2..7becbe5 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 runs `cargo fmt --all -- --check` by default. +- To auto-fix formatting on commit, opt in with `AUTO_FMT=1`: + +``` +AUTO_FMT=1 git commit -m "your message" +``` + +You can also export `AUTO_FMT=1` in your shell rc if you prefer auto-fix always. + ### Development Principles 1. **Tests First** – Write failing unit/integration/branch tests before new engine work. From 7756121e3bdce3ef3340eb3aa01b6962d15e77cd Mon Sep 17 00:00:00 2001 From: "J. Kirby Ross" Date: Tue, 28 Oct 2025 13:20:50 -0700 Subject: [PATCH 08/14] hooks(pre-commit): rename AUTO_FMT -> ECHO_AUTO_FMT; README/AGENTS: document hooks and env flag --- .githooks/pre-commit | 4 ++-- AGENTS.md | 6 ++++++ README.md | 6 +++--- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/.githooks/pre-commit b/.githooks/pre-commit index d97ed9f..2190d4e 100644 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -50,8 +50,8 @@ if command -v rustup >/dev/null 2>&1; then fi # 3) Format (auto-fix if opted in) -if [[ "${AUTO_FMT:-0}" == "1" ]]; then - echo "pre-commit: AUTO_FMT=1 → running cargo fmt (auto-fix)" +if [[ "${ECHO_AUTO_FMT:-0}" == "1" ]]; then + echo "pre-commit: ECHO_AUTO_FMT=1 → running cargo fmt (auto-fix)" cargo fmt --all # Re-stage any formatting changes git add -A diff --git a/AGENTS.md b/AGENTS.md index 55c1391..dd17356 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 runs `cargo fmt` (check by default). Set `ECHO_AUTO_FMT=1` to auto-fix and re-stage formatting on commit. +- 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/README.md b/README.md index 7becbe5..2d16a26 100644 --- a/README.md +++ b/README.md @@ -180,13 +180,13 @@ make hooks ``` - The pre-commit hook runs `cargo fmt --all -- --check` by default. -- To auto-fix formatting on commit, opt in with `AUTO_FMT=1`: +- To auto-fix formatting on commit, opt in with `ECHO_AUTO_FMT=1`: ``` -AUTO_FMT=1 git commit -m "your message" +ECHO_AUTO_FMT=1 git commit -m "your message" ``` -You can also export `AUTO_FMT=1` in your shell rc if you prefer auto-fix always. +You can also export `ECHO_AUTO_FMT=1` in your shell rc if you prefer auto-fix always. ### Development Principles From 80b7ede3fffc59a9cec7e03c929474edf842fab6 Mon Sep 17 00:00:00 2001 From: "J. Kirby Ross" Date: Tue, 28 Oct 2025 13:30:49 -0700 Subject: [PATCH 09/14] docs: add ECHO_AUTO_FMT docs (README, AGENTS, CONTRIBUTING); execution plan + decision log updated for hook rename --- CONTRIBUTING.md | 12 ++++++++++++ docs/decision-log.md | 6 ++++++ docs/execution-plan.md | 5 +++++ 3 files changed, 23 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7af4caa..5ffe7dc 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 (check by default; auto-fix if `ECHO_AUTO_FMT=1` is set) + - 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/docs/decision-log.md b/docs/decision-log.md index 8929419..6c34030 100644 --- a/docs/decision-log.md +++ b/docs/decision-log.md @@ -28,3 +28,9 @@ - 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 0cc463c..2ee88c0 100644 --- a/docs/execution-plan.md +++ b/docs/execution-plan.md @@ -39,6 +39,11 @@ This is Codex’s working map for building Echo. Update it relentlessly—each s - 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. From 9d1a355a02b50702c8b551f26b81d286cd0ce2b7 Mon Sep 17 00:00:00 2001 From: "J. Kirby Ross" Date: Tue, 28 Oct 2025 13:31:52 -0700 Subject: [PATCH 10/14] hooks(pre-commit): default to auto-fix formatting (ECHO_AUTO_FMT=1 by default); docs updated to show ECHO_AUTO_FMT=0 for check-only --- .githooks/pre-commit | 4 ++-- AGENTS.md | 2 +- CONTRIBUTING.md | 2 +- README.md | 8 ++++---- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 2190d4e..c887697 100644 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -50,8 +50,8 @@ if command -v rustup >/dev/null 2>&1; then fi # 3) Format (auto-fix if opted in) -if [[ "${ECHO_AUTO_FMT:-0}" == "1" ]]; then - echo "pre-commit: ECHO_AUTO_FMT=1 → running cargo fmt (auto-fix)" +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 diff --git a/AGENTS.md b/AGENTS.md index dd17356..7e19b22 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -28,7 +28,7 @@ Welcome to the **Echo** project. This file captures expectations for any LLM age ### Git Hooks & Local CI - Install repo hooks once with `make hooks` (configures `core.hooksPath`). -- Formatting: pre-commit runs `cargo fmt` (check by default). Set `ECHO_AUTO_FMT=1` to auto-fix and re-stage formatting on commit. +- 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). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5ffe7dc..1bc1bd7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -60,7 +60,7 @@ Echo is a deterministic, renderer-agnostic engine. We prioritize: ### Git Hooks (recommended) - Install repo hooks once: `make hooks` (configures `core.hooksPath` to `.githooks`). - Pre-commit runs: - - cargo fmt (check by default; auto-fix if `ECHO_AUTO_FMT=1` is set) + - 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"` diff --git a/README.md b/README.md index 2d16a26..005c6b5 100644 --- a/README.md +++ b/README.md @@ -179,14 +179,14 @@ Install the repo’s hooks so formatting and quick checks run before commits: make hooks ``` -- The pre-commit hook runs `cargo fmt --all -- --check` by default. -- To auto-fix formatting on commit, opt in with `ECHO_AUTO_FMT=1`: +- 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=1 git commit -m "your message" +ECHO_AUTO_FMT=0 git commit -m "your message" ``` -You can also export `ECHO_AUTO_FMT=1` in your shell rc if you prefer auto-fix always. +You can also export `ECHO_AUTO_FMT=0` in your shell rc if you prefer check-only always. ### Development Principles From 8c26887c0659e47a414f4181023d64914fcf9e02 Mon Sep 17 00:00:00 2001 From: "J. Kirby Ross" Date: Tue, 28 Oct 2025 13:55:24 -0700 Subject: [PATCH 11/14] geom(PR #8): fix formatting per rustfmt; reorder modules; allow clippy module_name_repetitions; update execution plan + decision log; fix Cargo.toml repository + dev-dependencies --- .githooks/commit-msg | 0 .githooks/pre-commit | 0 .githooks/pre-push | 0 .githooks/pre-rebase | 0 crates/rmg-geom/Cargo.toml | 4 +-- crates/rmg-geom/src/lib.rs | 4 +-- crates/rmg-geom/src/temporal/mod.rs | 10 +++--- .../rmg-geom/src/temporal/temporal_proxy.rs | 16 ++++++--- .../src/temporal/temporal_transform.rs | 12 +++++-- crates/rmg-geom/src/temporal/tick.rs | 9 +++-- crates/rmg-geom/src/types/aabb.rs | 23 ++++++++++--- crates/rmg-geom/src/types/transform.rs | 34 ++++++++++++------- docs/decision-log.md | 2 +- docs/execution-plan.md | 27 ++------------- 14 files changed, 79 insertions(+), 62 deletions(-) mode change 100644 => 100755 .githooks/commit-msg mode change 100644 => 100755 .githooks/pre-commit mode change 100644 => 100755 .githooks/pre-push mode change 100644 => 100755 .githooks/pre-rebase diff --git a/.githooks/commit-msg b/.githooks/commit-msg old mode 100644 new mode 100755 diff --git a/.githooks/pre-commit b/.githooks/pre-commit old mode 100644 new mode 100755 diff --git a/.githooks/pre-push b/.githooks/pre-push old mode 100644 new mode 100755 diff --git a/.githooks/pre-rebase b/.githooks/pre-rebase old mode 100644 new mode 100755 diff --git a/crates/rmg-geom/Cargo.toml b/crates/rmg-geom/Cargo.toml index ace9b24..6240d67 100644 --- a/crates/rmg-geom/Cargo.toml +++ b/crates/rmg-geom/Cargo.toml @@ -4,7 +4,7 @@ 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" +repository = "https://github.com/flyingrobots/echo" readme = "../../README.md" keywords = ["echo", "geometry", "aabb", "broad-phase"] categories = ["game-engines", "data-structures"] @@ -12,4 +12,4 @@ categories = ["game-engines", "data-structures"] [dependencies] rmg-core = { path = "../rmg-core" } -#[dev-dependencies] +[dev-dependencies] diff --git a/crates/rmg-geom/src/lib.rs b/crates/rmg-geom/src/lib.rs index 05d00a4..b3d3b5d 100644 --- a/crates/rmg-geom/src/lib.rs +++ b/crates/rmg-geom/src/lib.rs @@ -22,10 +22,10 @@ Design notes: - 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; +/// Foundational geometric types. +pub mod types; // Broad-phase will land in a follow-up PR. // pub mod broad; diff --git a/crates/rmg-geom/src/temporal/mod.rs b/crates/rmg-geom/src/temporal/mod.rs index fec2fe1..0a7c638 100644 --- a/crates/rmg-geom/src/temporal/mod.rs +++ b/crates/rmg-geom/src/temporal/mod.rs @@ -1,8 +1,10 @@ //! 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."] +#[allow(clippy::module_name_repetitions)] pub mod temporal_proxy; +#[doc = "Start/end transforms over a tick and fat AABB computation."] +#[allow(clippy::module_name_repetitions)] +pub mod temporal_transform; +#[doc = "Discrete Chronos ticks (u64 newtype)."] +pub mod tick; diff --git a/crates/rmg-geom/src/temporal/temporal_proxy.rs b/crates/rmg-geom/src/temporal/temporal_proxy.rs index 9344968..f237f6e 100644 --- a/crates/rmg-geom/src/temporal/temporal_proxy.rs +++ b/crates/rmg-geom/src/temporal/temporal_proxy.rs @@ -16,17 +16,25 @@ pub struct TemporalProxy { 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 } } + 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 } + pub const fn entity(&self) -> u64 { + self.entity + } /// Tick associated with the motion window. #[must_use] - pub const fn tick(&self) -> Tick { self.tick } + pub const fn tick(&self) -> Tick { + self.tick + } /// Conservative fat AABB for this proxy. #[must_use] - pub const fn fat(&self) -> Aabb { self.fat } + 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 index 4f25004..bef126d 100644 --- a/crates/rmg-geom/src/temporal/temporal_transform.rs +++ b/crates/rmg-geom/src/temporal/temporal_transform.rs @@ -20,15 +20,21 @@ pub struct TemporalTransform { 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 } } + 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 } + pub const fn start(&self) -> Transform { + self.start + } /// Returns the end transform. #[must_use] - pub const fn end(&self) -> Transform { self.end } + pub const fn end(&self) -> Transform { + self.end + } /// Computes a conservative fat AABB for a collider with local-space `shape` AABB. /// diff --git a/crates/rmg-geom/src/temporal/tick.rs b/crates/rmg-geom/src/temporal/tick.rs index ea144d3..c484242 100644 --- a/crates/rmg-geom/src/temporal/tick.rs +++ b/crates/rmg-geom/src/temporal/tick.rs @@ -10,10 +10,13 @@ pub struct Tick { impl Tick { /// Creates a new tick with the given index. #[must_use] - pub const fn new(index: u64) -> Self { Self { index } } + pub const fn new(index: u64) -> Self { + Self { index } + } /// Returns the tick index. #[must_use] - pub const fn index(&self) -> u64 { self.index } + 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 index 3a84b69..6ce88ad 100644 --- a/crates/rmg-geom/src/types/aabb.rs +++ b/crates/rmg-geom/src/types/aabb.rs @@ -30,17 +30,24 @@ impl Aabb { 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"); + 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 } + pub fn min(&self) -> Vec3 { + self.min + } /// Returns the maximum corner. #[must_use] - pub fn max(&self) -> Vec3 { self.max } + pub fn max(&self) -> Vec3 { + self.max + } /// Builds an AABB centered at `center` with half-extents `hx, hy, hz`. #[must_use] @@ -85,7 +92,10 @@ impl Aabb { #[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) } + Self { + min: self.min.sub(&delta), + max: self.max.add(&delta), + } } /// Computes the AABB that bounds this box after transformation by `mat`. @@ -128,7 +138,10 @@ impl Aabb { /// 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"); + 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..] { diff --git a/crates/rmg-geom/src/types/transform.rs b/crates/rmg-geom/src/types/transform.rs index 59baed7..92dd048 100644 --- a/crates/rmg-geom/src/types/transform.rs +++ b/crates/rmg-geom/src/types/transform.rs @@ -23,26 +23,40 @@ 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) } + 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 } + Self { + translation, + rotation, + scale, + } } /// Translation component. #[must_use] - pub fn translation(&self) -> Vec3 { self.translation } + pub fn translation(&self) -> Vec3 { + self.translation + } /// Rotation component. #[must_use] - pub fn rotation(&self) -> Quat { self.rotation } + pub fn rotation(&self) -> Quat { + self.rotation + } /// Scale component. #[must_use] - pub fn scale(&self) -> Vec3 { self.scale } + pub fn scale(&self) -> Vec3 { + self.scale + } /// Returns the column-major `Mat4` corresponding to this transform. #[must_use] @@ -52,19 +66,13 @@ impl Transform { 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, + 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, + 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 6c34030..0d6e5df 100644 --- a/docs/decision-log.md +++ b/docs/decision-log.md @@ -15,7 +15,7 @@ | 2025-10-26 | RMG + Confluence | Adopt RMG v2 (typed DPOi engine) and Confluence synchronization as core architecture | Unify runtime/persistence/tooling on deterministic rewrites | Launch Rust workspace (rmg-core/ffi/wasm/cli), port ECS rules, set up Confluence networking | | 2025-10-27 | Core math split | Split `rmg-core` math into focused submodules (`vec3`, `mat4`, `quat`, `prng`) replacing monolithic `math.rs`. | Improves readability, testability, and aligns with strict linting. | Update imports; no behavior changes intended; follow-up determinism docs in snapshot hashing. | | 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-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. | ## 2025-10-28 — rmg-geom foundation (PR #8) compile + clippy fixes diff --git a/docs/execution-plan.md b/docs/execution-plan.md index 2ee88c0..5b663bc 100644 --- a/docs/execution-plan.md +++ b/docs/execution-plan.md @@ -54,32 +54,9 @@ This is Codex’s working map for building Echo. Update it relentlessly—each s - 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 +> 2025-10-28 — PR #7 (rmg-core engine spike) -- Focus: close out review nits on engine/scheduler/footprint/demo; ensure CI/hook stability; keep scope tight. -- Done: - - Snapshot hashes: reachable-only BFS; sorted edges by `EdgeId`. - - Scheduler: `finalize_tx()` clears `active` and `pending` to avoid leaks; reserve gate wired. - - Demo parity: `build_port_demo_engine()` registers its rule; ports footprint guards writes/ports on existing nodes. - - Footprint: `PortKey` bit layout documented (bits 31..2: port_id u30; bit 1: reserved; bit 0: dir) + u30 masking with debug-assert; factor_mask invariant documented. - - Hooks/CI: pinned pre-push toolchain, robust banned-pattern scan, adjusted docs-guard to core API; fixed rustdoc links. - - MSRV: rmg-core stays at 1.68; workspace uses stable for wasm dependencies. - - Engine/tests: enforce `join_fn` invariant for `ConflictPolicy::Join`; remove `expect` panic in `apply()` corruption path; add NaN-propagation test for `clamp`; do not push yet (waiting for more feedback). - -> 2025-10-27 — Core math modularization (PR #5) - -- **Focus**: Split `rmg-core` math into focused submodules (`vec3`, `mat4`, `quat`, `prng`). -- **Definition of done**: CI passes; decision log updated; no behavior changes (pure refactor). - -> 2025-10-27 — PR #7 (echo/split-core-math-engine) merge prep - -- **Focus**: Land the extracted math + engine spike; add doc guard updates and preflight fmt/clippy/tests. -- **Definition of done**: `docs/decision-log.md` + `docs/execution-plan.md` updated; `cargo fmt --check`, `cargo clippy -D warnings -D missing_docs`, and `cargo test` pass; branch is fast‑forward mergeable into `main`. - -> 2025-10-27 — MWMR reserve gate + telemetry wiring - -- **Focus**: Enforce `reserve()` gate (independence), add compact rule id execution path, and emit per‑tx telemetry summary; pin toolchain. -- **Definition of done**: Scheduler `finalize_tx()` called by `Engine::commit`, compact‑id → rule lookup used on execute path, `rust-toolchain.toml` added and `rust-version = 1.68` set in crates; tests remain green. +- Landed on main; see Decision Log for summary of changes and CI outcomes. --- From 9b6d8be9e635a9aea531a2467309c07569f138fd Mon Sep 17 00:00:00 2001 From: "J. Kirby Ross" Date: Tue, 28 Oct 2025 14:32:40 -0700 Subject: [PATCH 12/14] geom(PR #8): rename temporal modules to avoid clippy repetition (temporal::motion, temporal::window); move files; fix EngineError doc link in rmg-core tx.rs; fmt --- crates/rmg-core/src/tx.rs | 2 +- crates/rmg-geom/src/lib.rs | 4 +-- crates/rmg-geom/src/temporal/mod.rs | 6 ++-- .../temporal/{temporal_proxy.rs => motion.rs} | 16 ++++++--- crates/rmg-geom/src/temporal/tick.rs | 9 +++-- .../{temporal_transform.rs => window.rs} | 12 +++++-- crates/rmg-geom/src/types/aabb.rs | 23 ++++++++++--- crates/rmg-geom/src/types/transform.rs | 34 ++++++++++++------- 8 files changed, 72 insertions(+), 34 deletions(-) rename crates/rmg-geom/src/temporal/{temporal_proxy.rs => motion.rs} (77%) rename crates/rmg-geom/src/temporal/{temporal_transform.rs => window.rs} (90%) 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/src/lib.rs b/crates/rmg-geom/src/lib.rs index 05d00a4..b3d3b5d 100644 --- a/crates/rmg-geom/src/lib.rs +++ b/crates/rmg-geom/src/lib.rs @@ -22,10 +22,10 @@ Design notes: - 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; +/// Foundational geometric types. +pub mod types; // Broad-phase will land in a follow-up PR. // pub mod broad; diff --git a/crates/rmg-geom/src/temporal/mod.rs b/crates/rmg-geom/src/temporal/mod.rs index fec2fe1..c3b26cf 100644 --- a/crates/rmg-geom/src/temporal/mod.rs +++ b/crates/rmg-geom/src/temporal/mod.rs @@ -1,8 +1,8 @@ //! Temporal types and helpers used for tick-based motion and broad-phase. +#[doc = "Broad-phase proxy carrying entity id, tick, and fat AABB."] +pub mod motion; #[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; +pub mod window; diff --git a/crates/rmg-geom/src/temporal/temporal_proxy.rs b/crates/rmg-geom/src/temporal/motion.rs similarity index 77% rename from crates/rmg-geom/src/temporal/temporal_proxy.rs rename to crates/rmg-geom/src/temporal/motion.rs index 9344968..f237f6e 100644 --- a/crates/rmg-geom/src/temporal/temporal_proxy.rs +++ b/crates/rmg-geom/src/temporal/motion.rs @@ -16,17 +16,25 @@ pub struct TemporalProxy { 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 } } + 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 } + pub const fn entity(&self) -> u64 { + self.entity + } /// Tick associated with the motion window. #[must_use] - pub const fn tick(&self) -> Tick { self.tick } + pub const fn tick(&self) -> Tick { + self.tick + } /// Conservative fat AABB for this proxy. #[must_use] - pub const fn fat(&self) -> Aabb { self.fat } + pub const fn fat(&self) -> Aabb { + self.fat + } } diff --git a/crates/rmg-geom/src/temporal/tick.rs b/crates/rmg-geom/src/temporal/tick.rs index ea144d3..c484242 100644 --- a/crates/rmg-geom/src/temporal/tick.rs +++ b/crates/rmg-geom/src/temporal/tick.rs @@ -10,10 +10,13 @@ pub struct Tick { impl Tick { /// Creates a new tick with the given index. #[must_use] - pub const fn new(index: u64) -> Self { Self { index } } + pub const fn new(index: u64) -> Self { + Self { index } + } /// Returns the tick index. #[must_use] - pub const fn index(&self) -> u64 { self.index } + pub const fn index(&self) -> u64 { + self.index + } } - diff --git a/crates/rmg-geom/src/temporal/temporal_transform.rs b/crates/rmg-geom/src/temporal/window.rs similarity index 90% rename from crates/rmg-geom/src/temporal/temporal_transform.rs rename to crates/rmg-geom/src/temporal/window.rs index 4f25004..bef126d 100644 --- a/crates/rmg-geom/src/temporal/temporal_transform.rs +++ b/crates/rmg-geom/src/temporal/window.rs @@ -20,15 +20,21 @@ pub struct TemporalTransform { 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 } } + 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 } + pub const fn start(&self) -> Transform { + self.start + } /// Returns the end transform. #[must_use] - pub const fn end(&self) -> Transform { self.end } + pub const fn end(&self) -> Transform { + self.end + } /// Computes a conservative fat AABB for a collider with local-space `shape` AABB. /// diff --git a/crates/rmg-geom/src/types/aabb.rs b/crates/rmg-geom/src/types/aabb.rs index 3a84b69..6ce88ad 100644 --- a/crates/rmg-geom/src/types/aabb.rs +++ b/crates/rmg-geom/src/types/aabb.rs @@ -30,17 +30,24 @@ impl Aabb { 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"); + 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 } + pub fn min(&self) -> Vec3 { + self.min + } /// Returns the maximum corner. #[must_use] - pub fn max(&self) -> Vec3 { self.max } + pub fn max(&self) -> Vec3 { + self.max + } /// Builds an AABB centered at `center` with half-extents `hx, hy, hz`. #[must_use] @@ -85,7 +92,10 @@ impl Aabb { #[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) } + Self { + min: self.min.sub(&delta), + max: self.max.add(&delta), + } } /// Computes the AABB that bounds this box after transformation by `mat`. @@ -128,7 +138,10 @@ impl Aabb { /// 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"); + 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..] { diff --git a/crates/rmg-geom/src/types/transform.rs b/crates/rmg-geom/src/types/transform.rs index 59baed7..92dd048 100644 --- a/crates/rmg-geom/src/types/transform.rs +++ b/crates/rmg-geom/src/types/transform.rs @@ -23,26 +23,40 @@ 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) } + 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 } + Self { + translation, + rotation, + scale, + } } /// Translation component. #[must_use] - pub fn translation(&self) -> Vec3 { self.translation } + pub fn translation(&self) -> Vec3 { + self.translation + } /// Rotation component. #[must_use] - pub fn rotation(&self) -> Quat { self.rotation } + pub fn rotation(&self) -> Quat { + self.rotation + } /// Scale component. #[must_use] - pub fn scale(&self) -> Vec3 { self.scale } + pub fn scale(&self) -> Vec3 { + self.scale + } /// Returns the column-major `Mat4` corresponding to this transform. #[must_use] @@ -52,19 +66,13 @@ impl Transform { 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, + 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, + 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) } From b98e81d08edcd73b4c3aac3381f375aaaeb14c17 Mon Sep 17 00:00:00 2001 From: "J. Kirby Ross" Date: Tue, 28 Oct 2025 14:51:04 -0700 Subject: [PATCH 13/14] geom(PR #8): make Transform::identity const; add Tick #[repr(transparent)] + From conversions; fmt; devcontainer respects rust-toolchain; fix pre-commit staging + truthy values; clarify TxId doc (zero returns UnknownTx); docs(toolchain alignment) --- .devcontainer/devcontainer.json | 2 -- .devcontainer/post-create.sh | 12 ++++++----- .githooks/pre-commit | 28 ++++++++++++++++++-------- crates/rmg-core/src/tx.rs | 6 +++--- crates/rmg-geom/src/temporal/tick.rs | 13 ++++++++++++ crates/rmg-geom/src/types/transform.rs | 2 +- docs/decision-log.md | 1 + docs/execution-plan.md | 9 +++++---- 8 files changed, 50 insertions(+), 23 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index b942b36..78dd96d 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -7,7 +7,6 @@ "installZsh": false }, "ghcr.io/devcontainers/features/rust:1": { - "version": "1.90", "profile": "minimal", "components": ["rustfmt", "clippy"] }, @@ -36,4 +35,3 @@ "overrideCommand": false, "postCreateCommand": "/bin/bash .devcontainer/post-create.sh" } - diff --git a/.devcontainer/post-create.sh b/.devcontainer/post-create.sh index 40690d2..833d0bd 100755 --- a/.devcontainer/post-create.sh +++ b/.devcontainer/post-create.sh @@ -1,19 +1,21 @@ #!/usr/bin/env bash set -euo pipefail -echo "[devcontainer] Installing MSRV toolchain (1.68.0) and common targets..." +echo "[devcontainer] Installing MSRV toolchain (1.68.0) and respecting rust-toolchain.toml..." 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 +# Do not override default; let rust-toolchain.toml control toolchain selection for this repo. +# Install optional newer toolchain for local convenience (kept as non-default). +rustup toolchain install 1.90.0 --profile minimal || true +# Ensure components/targets are available for the active (rust-toolchain.toml) toolchain. +rustup component add rustfmt clippy || true +rustup target add wasm32-unknown-unknown || true 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 c887697..a3839b6 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -50,14 +50,26 @@ if command -v rustup >/dev/null 2>&1; then fi # 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 +_auto_fmt="${ECHO_AUTO_FMT:-1}" +case "${_auto_fmt}" in + 1|true|TRUE|yes|YES|on|ON) + echo "pre-commit: ECHO_AUTO_FMT=${_auto_fmt} → running cargo fmt (auto-fix)" + cargo fmt --all || { echo "pre-commit: cargo fmt failed" >&2; exit 1; } + # Re-stage only staged files that were reformatted + if STAGED=$(git diff --cached --name-only); then + if [[ -n "$STAGED" ]]; then + echo "$STAGED" | xargs -r git add -- + fi + fi + ;; + 0|false|FALSE|no|NO|off|OFF) + cargo fmt --all -- --check || { echo "pre-commit: cargo fmt check failed" >&2; exit 1; } + ;; + *) + # Unknown value → safest is check-only + cargo fmt --all -- --check || { echo "pre-commit: cargo fmt check failed" >&2; exit 1; } + ;; +esac # 4) Docs guard (scaled): only require docs when core public API changed STAGED=$(git diff --cached --name-only) diff --git a/crates/rmg-core/src/tx.rs b/crates/rmg-core/src/tx.rs index f561f68..1f3b7a9 100644 --- a/crates/rmg-core/src/tx.rs +++ b/crates/rmg-core/src/tx.rs @@ -23,9 +23,9 @@ pub struct TxId(u64); impl TxId { /// Constructs a `TxId` from a raw `u64` value. /// - /// # Safety Note - /// Callers must not construct `TxId(0)` as it is reserved as invalid. - /// Using an invalid `TxId` with engine operations results in undefined behavior. + /// # Note on zero + /// Constructing `TxId(0)` is allowed, but engine operations treat it as + /// invalid and will return [`crate::engine_impl::EngineError::UnknownTx`]. #[must_use] pub const fn from_raw(value: u64) -> Self { Self(value) diff --git a/crates/rmg-geom/src/temporal/tick.rs b/crates/rmg-geom/src/temporal/tick.rs index c484242..389de11 100644 --- a/crates/rmg-geom/src/temporal/tick.rs +++ b/crates/rmg-geom/src/temporal/tick.rs @@ -2,6 +2,7 @@ /// /// The engine advances in integer ticks with a fixed `dt` per branch. This /// newtype ensures explicit tick passing across APIs. +#[repr(transparent)] #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Tick { index: u64, @@ -20,3 +21,15 @@ impl Tick { self.index } } + +impl From for Tick { + fn from(value: u64) -> Self { + Self { index: value } + } +} + +impl From for u64 { + fn from(t: Tick) -> Self { + t.index + } +} diff --git a/crates/rmg-geom/src/types/transform.rs b/crates/rmg-geom/src/types/transform.rs index 92dd048..8f37801 100644 --- a/crates/rmg-geom/src/types/transform.rs +++ b/crates/rmg-geom/src/types/transform.rs @@ -22,7 +22,7 @@ pub struct Transform { impl Transform { /// Identity transform (no translation, no rotation, unit scale). #[must_use] - pub fn identity() -> Self { + pub const fn identity() -> Self { Self { translation: Vec3::new(0.0, 0.0, 0.0), rotation: Quat::identity(), diff --git a/docs/decision-log.md b/docs/decision-log.md index 0d6e5df..24f3627 100644 --- a/docs/decision-log.md +++ b/docs/decision-log.md @@ -17,6 +17,7 @@ | 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 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. | + ## 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. diff --git a/docs/execution-plan.md b/docs/execution-plan.md index 5b663bc..512cb63 100644 --- a/docs/execution-plan.md +++ b/docs/execution-plan.md @@ -33,11 +33,12 @@ This is Codex’s working map for building Echo. Update it relentlessly—each s ## Today’s Intent -> 2025-10-28 — Devcontainer scaffolding +> 2025-10-28 — Devcontainer/toolchain alignment -- 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. +- Single source of truth: `rust-toolchain.toml` (MSRV = 1.68.0). +- Devcontainer must not override default toolchain; the feature installs Rust but selection is controlled by `rust-toolchain.toml`. +- Post-create respects `rust-toolchain.toml` (no `rustup default stable`); installs MSRV (1.68.0) and optionally 1.90.0 without changing the default; adds rustfmt/clippy and wasm32 target. +- CI should pin the toolchain explicitly (MSRV job on 1.68; avoid forcing `stable` overrides in workspace jobs). > 2025-10-28 — Pre-commit auto-format flag update From 4fdaeb5f50d863c1d9f19ce4f38f00883cd202cc Mon Sep 17 00:00:00 2001 From: "J. Kirby Ross" Date: Tue, 28 Oct 2025 15:01:32 -0700 Subject: [PATCH 14/14] geom(PR #8): rename PositionManifold -> PositionProxy to satisfy clippy pedantic (no repetition); fmt --- crates/rmg-geom/src/temporal/{motion.rs => manifold.rs} | 6 +++--- crates/rmg-geom/src/temporal/mod.rs | 4 ++-- crates/rmg-geom/src/temporal/{window.rs => timespan.rs} | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) rename crates/rmg-geom/src/temporal/{motion.rs => manifold.rs} (87%) rename crates/rmg-geom/src/temporal/{window.rs => timespan.rs} (92%) diff --git a/crates/rmg-geom/src/temporal/motion.rs b/crates/rmg-geom/src/temporal/manifold.rs similarity index 87% rename from crates/rmg-geom/src/temporal/motion.rs rename to crates/rmg-geom/src/temporal/manifold.rs index f237f6e..357a404 100644 --- a/crates/rmg-geom/src/temporal/motion.rs +++ b/crates/rmg-geom/src/temporal/manifold.rs @@ -1,19 +1,19 @@ use crate::temporal::tick::Tick; use crate::types::aabb::Aabb; -/// Broad-phase proxy summarizing an entity’s motion window for a tick. +/// Broad-phase proxy summarizing an entity’s swept position manifold over 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 { +pub struct PositionProxy { entity: u64, tick: Tick, fat: Aabb, } -impl TemporalProxy { +impl PositionProxy { /// 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 { diff --git a/crates/rmg-geom/src/temporal/mod.rs b/crates/rmg-geom/src/temporal/mod.rs index c3b26cf..5891527 100644 --- a/crates/rmg-geom/src/temporal/mod.rs +++ b/crates/rmg-geom/src/temporal/mod.rs @@ -1,8 +1,8 @@ //! Temporal types and helpers used for tick-based motion and broad-phase. #[doc = "Broad-phase proxy carrying entity id, tick, and fat AABB."] -pub mod motion; +pub mod manifold; #[doc = "Discrete Chronos ticks (u64 newtype)."] pub mod tick; #[doc = "Start/end transforms over a tick and fat AABB computation."] -pub mod window; +pub mod timespan; diff --git a/crates/rmg-geom/src/temporal/window.rs b/crates/rmg-geom/src/temporal/timespan.rs similarity index 92% rename from crates/rmg-geom/src/temporal/window.rs rename to crates/rmg-geom/src/temporal/timespan.rs index bef126d..875bcb7 100644 --- a/crates/rmg-geom/src/temporal/window.rs +++ b/crates/rmg-geom/src/temporal/timespan.rs @@ -12,13 +12,13 @@ use crate::types::{aabb::Aabb, transform::Transform}; /// 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 { +pub struct Timespan { start: Transform, end: Transform, } -impl TemporalTransform { - /// Creates a new `TemporalTransform` from start and end transforms. +impl Timespan { + /// Creates a new `Timespan` from start and end transforms. #[must_use] pub const fn new(start: Transform, end: Transform) -> Self { Self { start, end }