Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
cbc0242
feat(geom): add rmg-geom foundation (split PR 2)\n\n- types: Aabb, Tr…
flyingrobots Oct 27, 2025
8832650
docs(geom): add determinism/quantization notes to AABB + Transform + …
flyingrobots Oct 27, 2025
a086c56
geom(foundation): fix compile + clippy for PR #8\n\n- Remove unused …
flyingrobots Oct 28, 2025
c9ccd6e
merge main into echo/split-geom-foundation for CI metadata and math h…
flyingrobots Oct 28, 2025
025ede8
docs(PR #8): update execution plan + decision log per docs-guard
flyingrobots Oct 28, 2025
7f4188b
devcontainer: add VS Code devcontainer (Ubuntu 24.04) + post-create M…
flyingrobots Oct 28, 2025
6938f00
docs(rmg-core/tx): fix rustdoc broken link to EngineError::UnknownTx …
flyingrobots Oct 28, 2025
9862678
hooks(pre-commit): integrate cargo fmt (auto-fix with AUTO_FMT=1) and…
flyingrobots Oct 28, 2025
7756121
hooks(pre-commit): rename AUTO_FMT -> ECHO_AUTO_FMT; README/AGENTS: d…
flyingrobots Oct 28, 2025
80b7ede
docs: add ECHO_AUTO_FMT docs (README, AGENTS, CONTRIBUTING); executio…
flyingrobots Oct 28, 2025
9d1a355
hooks(pre-commit): default to auto-fix formatting (ECHO_AUTO_FMT=1 by…
flyingrobots Oct 28, 2025
8c26887
geom(PR #8): fix formatting per rustfmt; reorder modules; allow clipp…
flyingrobots Oct 28, 2025
9b6d8be
geom(PR #8): rename temporal modules to avoid clippy repetition (temp…
flyingrobots Oct 28, 2025
e86474e
geom(PR #8): resolve merge conflict; use temporal::{motion, window, t…
flyingrobots Oct 28, 2025
b98e81d
geom(PR #8): make Transform::identity const; add Tick #[repr(transpar…
flyingrobots Oct 28, 2025
4fdaeb5
geom(PR #8): rename PositionManifold -> PositionProxy to satisfy clip…
flyingrobots Oct 28, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"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": {
"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"
}
21 changes: 21 additions & 0 deletions .devcontainer/post-create.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/usr/bin/env bash
set -euo pipefail

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
# 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."
Empty file modified .githooks/commit-msg
100644 → 100755
Empty file.
23 changes: 21 additions & 2 deletions .githooks/pre-commit
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,27 @@ 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)
_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
Comment on lines +52 to +72
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

From: Linus Torvalds torvalds@linux-foundation.org
To: flyingrobots flyingrobots@users.noreply.github.com
Cc: code-rabbit@vger.kernel.org
Subject: [PATCH] pre-commit: auto-format restaging logic is sloppy

Good: You fixed the git add -A catastrophe.

Bad: Your restaging logic is still wrong.

Line 59 captures the list of staged files before checking whether cargo fmt actually touched them. Then line 61 blindly re-adds all of them.

If cargo fmt didn't change a file (already formatted), you're doing pointless re-staging work. More importantly, you're not verifying that the working tree actually differs from the index post-format.

Here's what you should do:

   if [[ "${_auto_fmt}" == "1" ]]; then
     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
+    # Re-stage only files that were both staged AND modified by fmt
+    git diff --cached --name-only | while IFS= read -r file; do
+      if git diff --quiet -- "$file" 2>/dev/null; then
+        : # File wasn't changed by fmt, skip
+      else
+        git add -- "$file"
+      fi
+    done

Or simpler (if you trust git add to be idempotent on unchanged files, which it is):

-    if STAGED=$(git diff --cached --name-only); then
-      if [[ -n "$STAGED" ]]; then
-        echo "$STAGED" | xargs -r git add --
-      fi
-    fi
+    git diff --cached --name-only --diff-filter=M | xargs -r git add --

The --diff-filter=M ensures you only re-stage files that were actually modified, not added/deleted/etc.

Also, minor nit: your case patterns on lines 55 and 65 accept TRUE and YES but real shell idiom is lowercase. Nobody sets ECHO_AUTO_FMT=TRUE in practice. You're overthinking it. Just accept 1|true|yes and 0|false|no.

BunBun

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# 3) Format (auto-fix if opted in)
_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
# 3) Format (auto-fix if opted in)
_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
git diff --cached --name-only --diff-filter=M | xargs -r git add --
;;
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
🤖 Prompt for AI Agents
In .githooks/pre-commit around lines 52 to 72, the restaging logic captures
staged files before running cargo fmt and then blindly re-adds them; change it
so you determine which files were actually modified by cargo fmt and only
re-stage those (e.g. compute the list after running cargo fmt and filter with a
diff filter like --diff-filter=M so you only re-add modified files), and
simplify the case patterns to accept only lowercase booleans (use 1|true|yes for
on and 0|false|no for off) to match common shell idioms.


# 4) Docs guard (scaled): only require docs when core public API changed
STAGED=$(git diff --cached --name-only)
Expand Down
Empty file modified .githooks/pre-push
100644 → 100755
Empty file.
Empty file modified .githooks/pre-rebase
100644 → 100755
Empty file.
6 changes: 6 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
12 changes: 12 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/<feature>` or `timeline/<experiment>`.
Expand Down Expand Up @@ -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.
Expand Down
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
8 changes: 4 additions & 4 deletions crates/rmg-core/src/tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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)
Expand Down
15 changes: 15 additions & 0 deletions crates/rmg-geom/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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://github.com/flyingrobots/echo"
readme = "../../README.md"
keywords = ["echo", "geometry", "aabb", "broad-phase"]
categories = ["game-engines", "data-structures"]

[dependencies]
rmg-core = { path = "../rmg-core" }

[dev-dependencies]
33 changes: 33 additions & 0 deletions crates/rmg-geom/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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.
"]

/// 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;

pub use types::aabb::Aabb;
pub use types::transform::Transform;
40 changes: 40 additions & 0 deletions crates/rmg-geom/src/temporal/manifold.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use crate::temporal::tick::Tick;
use crate::types::aabb::Aabb;

/// 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 PositionProxy {
entity: u64,
tick: Tick,
fat: Aabb,
}
Comment on lines +9 to +14
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

From: Linus Torvalds torvalds@linux-foundation.org
To: flyingrobots
Cc: code-rabbit@vger.kernel.org

Add Eq and Hash derives—you're crippling your own API.

Your rustdoc at line 7 says this proxy is "suitable for insertion into a broad-phase accelerator." Great. Except broad-phase accelerators use hash tables for O(1) proxy lookups by entity ID, and you've got PartialEq but no Eq or Hash.

This means your "suitable" type can't actually be used as a HashMap key or stored in a HashSet. You're shipping an API that promises one thing and delivers half of it.

Apply this diff:

-#[derive(Debug, Copy, Clone, PartialEq)]
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
 pub struct PositionProxy {

BunBun

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct PositionProxy {
entity: u64,
tick: Tick,
fat: Aabb,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct PositionProxy {
entity: u64,
tick: Tick,
fat: Aabb,
}
🤖 Prompt for AI Agents
In crates/rmg-geom/src/temporal/manifold.rs around lines 9 to 14, the
PositionProxy type currently derives Debug, Copy, Clone, and PartialEq but not
Eq or Hash; to make it usable as a key in HashMap/HashSet (as your docs promise)
add Eq and Hash to the derive list on PositionProxy. Also ensure the constituent
types (Tick and Aabb) implement/derive Eq and Hash as well (or implement those
traits manually) so the compiler can derive PositionProxy::Eq and ::Hash; update
their derives/imports accordingly.


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 {
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
}
}
Comment on lines +16 to +40
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

From: Linus Torvalds torvalds@linux-foundation.org
To: flyingrobots
Cc: code-rabbit@vger.kernel.org

Where are the unit tests?

Your PR description promises "unit tests will be added in this PR to meet the coverage gate." I see a struct with a constructor and three accessors, all marked #[must_use], but zero tests.

This isn't a "ship now, test later" situation. You're introducing a public API for a geometry foundation crate. Test it BEFORE merge, not after.

Verify that tests for PositionProxy::new, entity(), tick(), and fat() exist and cover:

  • Correct field initialization
  • Accessor return values
  • Copy/Clone behavior
  • PartialEq semantics

Run this script to check for tests:

BunBun


🏁 Script executed:

#!/bin/bash
# Description: Verify unit tests exist for PositionProxy

echo "=== Searching for PositionProxy tests ==="
rg -n --type rust -A 10 '#\[test\]|#\[cfg\(test\)\]' crates/rmg-geom/src/temporal/ | rg -A 10 'PositionProxy|manifold'

echo ""
echo "=== Searching for test modules ==="
fd -e rs test crates/rmg-geom/

Length of output: 77



From: Linus Torvalds torvalds@linux-foundation.org
To: flyingrobots
Cc: code-rabbit@vger.kernel.org

Tests promised in PR description? Let's talk about the ZERO tests actually shipped.

I ran the verification script. The results are crystal clear: no test blocks exist anywhere in crates/rmg-geom/src/temporal/, and no test files exist in crates/rmg-geom/. You promised "unit tests will be added in this PR to meet the coverage gate." Where. Are. They?

You've got a public API here—a constructor and three accessors for a geometry foundation crate—and you're trying to merge it naked. No tests. No coverage. No validation that PositionProxy initializes fields correctly, that accessors return what they claim to return, or that the #[must_use] annotations aren't just security theater.

Fix this before I look at this PR again:

  • Write unit tests for PositionProxy::new, entity(), tick(), and fat()
  • Verify field initialization correctness
  • Validate accessor return values
  • Test any Copy/Clone and PartialEq behavior
  • Ensure they're actually integrated into the test suite and will run

This is non-negotiable for a geometry foundation crate. Ship the tests with this PR or don't ship this PR.

BunBun

🤖 Prompt for AI Agents
crates/rmg-geom/src/temporal/manifold.rs lines 16-40: the PositionProxy public
API (const new, entity, tick, fat) has no unit tests; add a test module under
#[cfg(test)] that constructs PositionProxy via PositionProxy::new with
representative values, asserts each accessor returns the exact initialized value
(entity, tick, fat), verifies the struct is Copy/Clone by cloning/copying and
comparing, checks PartialEq (equality and inequality cases), and includes edge
cases (e.g. different ticks or fat AABBs) so field initialization is validated;
place tests in the same file or in tests/ ensuring they run with cargo test and
import any necessary types (Tick, Aabb) to make tests compile and exercise all
four API functions.

8 changes: 8 additions & 0 deletions crates/rmg-geom/src/temporal/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +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 manifold;
#[doc = "Discrete Chronos ticks (u64 newtype)."]
pub mod tick;
#[doc = "Start/end transforms over a tick and fat AABB computation."]
pub mod timespan;
35 changes: 35 additions & 0 deletions crates/rmg-geom/src/temporal/tick.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/// 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.
#[repr(transparent)]
#[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
}
}

impl From<u64> for Tick {
fn from(value: u64) -> Self {
Self { index: value }
}
}

impl From<Tick> for u64 {
fn from(t: Tick) -> Self {
t.index
}
}
Comment on lines +1 to +35
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

From: Linus Torvalds torvalds@linux-foundation.org
To: flyingrobots flyingrobots@users.noreply.github.com
Cc: code-rabbit@vger.kernel.org
Subject: Re: [PATCH] rmg-geom/tick: solid minimal newtype

You implemented the critical parts from the past review.

repr(transparent) (line 5) ensures FFI/ABI stability. The From conversions (lines 25-35) provide zero-cost ergonomics. The newtype is sound and the public API is documented.

Optional ergonomic additions still missing:

The past review suggested Display, Tick::ZERO, and next() for convenience. You skipped them. That's fine—they're not critical. But if you find yourself writing Tick::new(0) or Tick::new(tick.index() + 1) in downstream code, you'll wish you had them.

If you want to add them now:

+impl core::fmt::Display for Tick {
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        write!(f, "{}", self.index)
+    }
+}
+
 impl Tick {
+    /// Zero tick (epoch).
+    pub const ZERO: Self = Self { index: 0 };
+
     /// Creates a new tick with the given index.
@@
     pub const fn index(&self) -> u64 {
         self.index
     }
+
+    /// Next tick, saturating on overflow.
+    #[must_use]
+    pub const fn next(self) -> Self {
+        Self { index: self.index.saturating_add(1) }
+    }
 }

But honestly, the current code is fine for a foundation PR. Add them when you need them.

BunBun

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// 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.
#[repr(transparent)]
#[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
}
}
impl From<u64> for Tick {
fn from(value: u64) -> Self {
Self { index: value }
}
}
impl From<Tick> for u64 {
fn from(t: Tick) -> Self {
t.index
}
}
/// 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.
#[repr(transparent)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Tick {
index: u64,
}
impl core::fmt::Display for Tick {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self.index)
}
}
impl Tick {
/// Zero tick (epoch).
pub const ZERO: Self = Self { index: 0 };
/// 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
}
/// Next tick, saturating on overflow.
#[must_use]
pub const fn next(self) -> Self {
Self { index: self.index.saturating_add(1) }
}
}
impl From<u64> for Tick {
fn from(value: u64) -> Self {
Self { index: value }
}
}
impl From<Tick> for u64 {
fn from(t: Tick) -> Self {
t.index
}
}
🤖 Prompt for AI Agents
In crates/rmg-geom/src/temporal/tick.rs around lines 1 to 35, add small
ergonomic helpers: implement std::fmt::Display for Tick to show the inner index,
add a public associated constant ZERO: Tick = Tick::new(0), and add a pub const
fn next(&self) -> Tick that returns Tick::new(self.index + 1) (use wrapping_add
if you want wrap behavior or document overflow; prefer checked/wrapping as
needed). Ensure these additions are documented and keep visibility/public API
consistent with the type.

50 changes: 50 additions & 0 deletions crates/rmg-geom/src/temporal/timespan.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
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 Timespan {
start: Transform,
end: Transform,
}

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 }
}

/// 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)
}
}
Loading