From 87d9f53ddcb6d3864f33d12500c999c4b0417610 Mon Sep 17 00:00:00 2001 From: "J. Kirby Ross" Date: Tue, 28 Oct 2025 09:11:53 -0700 Subject: [PATCH 1/2] docs: log PR #14 (geom merge train) in execution plan and decision log --- docs/decision-log.md | 6 ++++++ docs/execution-plan.md | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/docs/decision-log.md b/docs/decision-log.md index ee34cd7..8a8c612 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 — Geometry merge train (PR #14) + +- Decision: Use an integration branch to validate #8 (geom foundation) + #9 (broad-phase AABB) together. +- Rationale: Surface cross-PR interactions early and avoid rebase/force push; adhere to merge-only policy. +- Impact: New crate `rmg-geom` (AABB, Transform, TemporalTransform) and baseline broad-phase with tests. No public API breaks in core. +- Next: If green, merge train PR; close individual PRs as merged-via-train. diff --git a/docs/execution-plan.md b/docs/execution-plan.md index 3795d60..20aebe4 100644 --- a/docs/execution-plan.md +++ b/docs/execution-plan.md @@ -33,6 +33,11 @@ This is Codex’s working map for building Echo. Update it relentlessly—each s ## Today’s Intent +> 2025-10-28 — PR #14 (geom merge train) opened + +- Focus: validate rmg-geom foundation + broad-phase AABB together before landing. +- Outcome: Opened train branch echo/merge-train-geom-1 merging #8 → #9; gather feedback and CI before merge. + > 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 1c052bc864377f3da9fe6a254de8e5b57cd7c98d Mon Sep 17 00:00:00 2001 From: "J. Kirby Ross" Date: Wed, 29 Oct 2025 10:09:00 -0700 Subject: [PATCH 2/2] geom(timespan): make fat_aabb conservative for rotations by unioning start/mid/end AABBs; add rotation-offset coverage test; update plan + decision log --- crates/rmg-geom/src/temporal/timespan.rs | 44 +++++++++++++++++++-- crates/rmg-geom/tests/geom_broad_tests.rs | 47 +++++++++++++++++++++++ docs/decision-log.md | 7 ++++ docs/execution-plan.md | 5 +++ 4 files changed, 99 insertions(+), 4 deletions(-) diff --git a/crates/rmg-geom/src/temporal/timespan.rs b/crates/rmg-geom/src/temporal/timespan.rs index 875bcb7..cf114cf 100644 --- a/crates/rmg-geom/src/temporal/timespan.rs +++ b/crates/rmg-geom/src/temporal/timespan.rs @@ -38,13 +38,49 @@ impl Timespan { /// 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. + /// Policy (deterministic): unions the AABBs at three sample poses — start (t=0), + /// midpoint (t=0.5), and end (t=1). This strictly contains pure translations + /// and captures protrusions that can occur at `t≈0.5` during rotations about + /// an off‑centre pivot, which a start/end‑only union can miss. + /// + /// Sampling count is fixed (3) for determinism; future work may make the + /// sampling policy configurable while keeping results identical across peers. #[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) + + // Midpoint transform via linear interp of translation/scale and + // normalized-linear blend of rotation (nlerp), then convert to Mat4. + let t0 = self.start.translation().to_array(); + let t1 = self.end.translation().to_array(); + let tm = rmg_core::math::Vec3::new( + 0.5 * (t0[0] + t1[0]), + 0.5 * (t0[1] + t1[1]), + 0.5 * (t0[2] + t1[2]), + ); + + let s0 = self.start.scale().to_array(); + let s1 = self.end.scale().to_array(); + let sm = rmg_core::math::Vec3::new( + 0.5 * (s0[0] + s1[0]), + 0.5 * (s0[1] + s1[1]), + 0.5 * (s0[2] + s1[2]), + ); + + let q0 = self.start.rotation().to_array(); + let q1 = self.end.rotation().to_array(); + let qm = rmg_core::math::Quat::new( + 0.5 * (q0[0] + q1[0]), + 0.5 * (q0[1] + q1[1]), + 0.5 * (q0[2] + q1[2]), + 0.5 * (q0[3] + q1[3]), + ) + .normalize(); + + let mid_tf = crate::types::transform::Transform::new(tm, qm, sm); + let am = shape.transformed(&mid_tf.to_mat4()); + + a0.union(&a1).union(&am) } } diff --git a/crates/rmg-geom/tests/geom_broad_tests.rs b/crates/rmg-geom/tests/geom_broad_tests.rs index 7ee8049..a24556c 100644 --- a/crates/rmg-geom/tests/geom_broad_tests.rs +++ b/crates/rmg-geom/tests/geom_broad_tests.rs @@ -43,3 +43,50 @@ fn broad_phase_pair_order_is_deterministic() { // Expected canonical order: (0,1), (0,3), (1,3) assert_eq!(pairs, vec![(0, 1), (0, 3), (1, 3)]); } + +#[test] +fn fat_aabb_covers_mid_rotation_with_offset() { + use core::f32::consts::FRAC_PI_2; + // Local shape: rod from x=0..2 (center at (1,0,0)) with small thickness + let local = + Aabb::from_center_half_extents(rmg_core::math::Vec3::new(1.0, 0.0, 0.0), 1.0, 0.1, 0.1); + + let t0 = Transform::new( + rmg_core::math::Vec3::new(0.0, 0.0, 0.0), + rmg_core::math::Quat::identity(), + rmg_core::math::Vec3::new(1.0, 1.0, 1.0), + ); + let t1 = Transform::new( + rmg_core::math::Vec3::new(0.0, 0.0, 0.0), + rmg_core::math::Quat::from_axis_angle(rmg_core::math::Vec3::new(0.0, 0.0, 1.0), FRAC_PI_2), + rmg_core::math::Vec3::new(1.0, 1.0, 1.0), + ); + let span = Timespan::new(t0, t1); + + // Compute mid pose explicitly (45°); this can protrude beyond both endpoints. + let mid_rot = rmg_core::math::Quat::from_axis_angle( + rmg_core::math::Vec3::new(0.0, 0.0, 1.0), + FRAC_PI_2 * 0.5, + ); + let mid = Transform::new( + rmg_core::math::Vec3::new(0.0, 0.0, 0.0), + mid_rot, + rmg_core::math::Vec3::new(1.0, 1.0, 1.0), + ); + let mid_aabb = local.transformed(&mid.to_mat4()); + + let fat = span.fat_aabb(&local); + let fmin = fat.min().to_array(); + let fmax = fat.max().to_array(); + let mmin = mid_aabb.min().to_array(); + let mmax = mid_aabb.max().to_array(); + + assert!( + fmin[0] <= mmin[0] && fmin[1] <= mmin[1] && fmin[2] <= mmin[2], + "fat min must enclose mid min: fat={fmin:?} mid={mmin:?}" + ); + assert!( + fmax[0] >= mmax[0] && fmax[1] >= mmax[1] && fmax[2] >= mmax[2], + "fat max must enclose mid max: fat={fmax:?} mid={mmax:?}" + ); +} diff --git a/docs/decision-log.md b/docs/decision-log.md index a064553..c7296d3 100644 --- a/docs/decision-log.md +++ b/docs/decision-log.md @@ -84,3 +84,10 @@ - Decision: Pre-commit runs `cargo fmt --all -- --check` whenever staged Rust files are detected. Retain the PRNG coupling guard but remove the unconditional early exit so formatting still runs when the PRNG file isn’t staged. - EditorConfig: normalize line endings (LF), ensure final newline, trim trailing whitespace, set 2-space indent for JS/TS/JSON and 4-space for Rust. - Consequence: Developers get immediate feedback on formatting; cleaner diffs and fewer CI round-trips. + +## 2025-10-29 — Geom fat AABB bounds mid-rotation + +- Context: Broad-phase must not miss overlaps when a shape rotates about an off‑centre pivot; union of endpoint AABBs can under‑approximate mid‑tick extents. +- Decision: `Timespan::fat_aabb` now unions AABBs at start, mid (t=0.5 via nlerp for rotation, lerp for translation/scale), and end. Sampling count is fixed (3) for determinism. +- Change: Implement midpoint sampling in `crates/rmg-geom/src/temporal/timespan.rs`; add test `fat_aabb_covers_mid_rotation_with_offset` to ensure mid‑pose is enclosed. +- Consequence: Deterministic and more conservative broad‑phase bounds for typical rotation cases without introducing policy/config surface yet; future work may expose a configurable sampling policy. diff --git a/docs/execution-plan.md b/docs/execution-plan.md index d326adf..3dcee9f 100644 --- a/docs/execution-plan.md +++ b/docs/execution-plan.md @@ -33,6 +33,11 @@ This is Codex’s working map for building Echo. Update it relentlessly—each s ## Today’s Intent +> 2025-10-29 — Geom fat AABB midpoint sampling (merge-train) + +- Update `rmg-geom::temporal::Timespan::fat_aabb` to union AABBs at start, mid (t=0.5), and end to conservatively bound rotations about off‑centre pivots. +- Add test `fat_aabb_covers_mid_rotation_with_offset` to verify the fat box encloses the mid‑pose AABB. + > 2025-10-29 — Hooks formatting gate (PR #12) - Pre-commit: add rustfmt check for staged Rust files (`cargo fmt --all -- --check`).