Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
f200bf8
math(Mat4): canonicalize -0.0 in rotation_x/y/z; add MulAssign for Ma…
flyingrobots Oct 28, 2025
ab46f7a
docs: log PR #13 (Mat4 canonical zero + MulAssign) in execution plan …
flyingrobots Oct 28, 2025
a19ca32
merge(core-math-canonical-zero←main): resolve docs guard conflicts; k…
flyingrobots Oct 29, 2025
c7d31e0
ci: rerun fmt-check (ensure mat4_mul_tests.rs is rustfmt-aligned)
flyingrobots Oct 29, 2025
db1288c
fmt(core-tests): apply rustfmt to mat4_mul_tests.rs (assert_ne! multi…
flyingrobots Oct 29, 2025
9abcb76
hooks(pre-commit): abort commit when rustfmt makes changes to preserv…
flyingrobots Oct 29, 2025
da752dc
docs+hooks: document and enforce pre-commit policy — abort commit whe…
flyingrobots Oct 29, 2025
4e25735
docs(contributing): document pre-commit rustfmt abort policy and part…
flyingrobots Oct 29, 2025
e20d3f7
ci+hooks+docs: add audit/deny jobs; extend rustdoc gate (core/geom/ff…
flyingrobots Oct 29, 2025
f55f44d
test(core/mat4): strengthen MulAssign coverage with non-trivial lhs/r…
flyingrobots Oct 29, 2025
b2abd25
test(core/mat4): fix clippy pedantic hex literal grouping in RNG seed
flyingrobots Oct 29, 2025
d2b9a5b
test(core/mat4): expand randomized MulAssign property to 64 iteration…
flyingrobots Oct 29, 2025
6e2d8e9
test(core/mat4): improve -0.0 diagnostics with axis+index; document a…
flyingrobots Oct 29, 2025
ab85b9d
ci(audit): fix action ref → actions-rs/audit-check@v1; update plan/lo…
flyingrobots Oct 29, 2025
7e53584
ci(audit): use rustsec/audit-check@v1 to avoid toolchain install fail…
flyingrobots Oct 30, 2025
a583c34
ci(licenses): add deny.toml allowlist for permissive licenses; docume…
flyingrobots Oct 30, 2025
5c0394d
ci(deny): replace path-only rmg-core deps with workspace dependencies…
flyingrobots Oct 30, 2025
b361d20
ci(audit): run cargo-audit on Rust 1.75.0 to satisfy MSRV; keep works…
flyingrobots Oct 30, 2025
0ec9db8
hooks(pre-push): log skipped crates; fail when required crates missin…
flyingrobots Oct 30, 2025
277918d
ci(audit): replace action with explicit cargo-audit install/run under…
flyingrobots Oct 30, 2025
378752b
rmg-core: tighten determinism + tests
flyingrobots Oct 30, 2025
b7feffe
docs: log 2025-10-30 intent — rmg-core determinism tests + API hardening
flyingrobots Oct 30, 2025
cafd33f
docs: record 2025-10-30 determinism hardening (rmg-core)
flyingrobots Oct 30, 2025
2e051db
docs(rustdoc): fix intra-doc links and API re-exports
flyingrobots Oct 30, 2025
40944e8
ci: add pinned Security Audit workflow (cargo-audit 0.21.1 on Rust 1.75)
flyingrobots Oct 30, 2025
5f67400
ci(audit): force toolchain override with +1.75.0 for install and run
flyingrobots Oct 30, 2025
3e331d9
toolchain: use @stable everywhere
flyingrobots Oct 30, 2025
6047bcb
docs: execution plan + decision log notes for rustdoc lint cleanup (S…
flyingrobots Oct 30, 2025
1b6f0ac
tests(geom): allow missing_docs and add crate doc for integration tes…
flyingrobots Oct 30, 2025
0094d0c
hooks: enforce @stable policy and disallow SKIP_HOOKS
flyingrobots Oct 30, 2025
9e57f8d
hooks/docs: remove SKIP_HOOKS entirely and update contributor guidanc…
flyingrobots Oct 30, 2025
f2322ec
hooks: docs guard now triggers on any Rust changes (.rs anywhere); up…
flyingrobots Oct 30, 2025
0650389
ci(audit): use rustsec/audit-check@v2 on stable instead of reinstalli…
flyingrobots Oct 30, 2025
dd9bcb1
rmg-core: remove duplicate clippy allow; spec: clarify state_root edg…
flyingrobots Oct 30, 2025
e6d5ed2
toolchain: pin to Rust 1.90.0 for deterministic builds; align CI and …
flyingrobots Oct 30, 2025
c95e8ea
docs: decision-log section break — keep table for early entries; add …
flyingrobots Oct 30, 2025
046fd81
ci(audit): supply GITHUB_TOKEN to rustsec/audit-check@v2
flyingrobots Oct 30, 2025
5ea8a8f
ci(audit): replace rustsec/audit-check with cached cargo-audit instal…
flyingrobots Oct 30, 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
4 changes: 4 additions & 0 deletions .devcontainer/post-create.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,7 @@ 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."
if [ -f Makefile ]; then
echo "[devcontainer] Installing git hooks (make hooks)"
make hooks || true
fi
29 changes: 12 additions & 17 deletions .githooks/pre-commit
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
#!/usr/bin/env bash
set -euo pipefail


if [[ "${SKIP_HOOKS:-}" == 1 ]]; then
exit 0
fi

# 1) PRNG coupling guard (existing logic)
PRNG_FILE="crates/rmg-core/src/math/prng.rs"
if git diff --cached --name-only | grep -qx "$PRNG_FILE"; then
Expand Down Expand Up @@ -54,13 +49,13 @@ 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
echo "pre-commit: ECHO_AUTO_FMT=${_auto_fmt} → checking format"
if ! cargo fmt --all -- --check; then
echo "pre-commit: running cargo fmt to apply changes" >&2
cargo fmt --all || { echo "pre-commit: cargo fmt failed" >&2; exit 1; }
echo "pre-commit: rustfmt updated files. Aborting commit to preserve index integrity (partial staging safe)." >&2
echo "Hint: review changes, restage (e.g., 'git add -p' or 'git add -A'), then commit again." >&2
exit 1
fi
;;
0|false|FALSE|no|NO|off|OFF)
Expand All @@ -72,12 +67,12 @@ case "${_auto_fmt}" in
;;
esac

# 4) Docs guard (scaled): only require docs when core public API changed
# 4) Docs guard: require docs updates on any Rust changes
STAGED=$(git diff --cached --name-only)
CORE_API_CHANGED=$(echo "$STAGED" | grep -E '^crates/rmg-core/src/.*\.rs$' | grep -v '/tests/' || true)
if [[ -n "$CORE_API_CHANGED" ]]; then
echo "$STAGED" | grep -Fx 'docs/execution-plan.md' >/dev/null || { echo 'pre-commit: docs/execution-plan.md must be updated when core API changes.' >&2; exit 1; }
echo "$STAGED" | grep -Fx 'docs/decision-log.md' >/dev/null || { echo 'pre-commit: docs/decision-log.md must be updated when core API changes.' >&2; exit 1; }
RUST_CHANGED=$(echo "$STAGED" | grep -E '\\.rs$' || true)
if [[ -n "$RUST_CHANGED" ]]; then
echo "$STAGED" | grep -Fx 'docs/execution-plan.md' >/dev/null || { echo 'pre-commit: docs/execution-plan.md must be updated when Rust files change.' >&2; exit 1; }
echo "$STAGED" | grep -Fx 'docs/decision-log.md' >/dev/null || { echo 'pre-commit: docs/decision-log.md must be updated when Rust files change.' >&2; exit 1; }
fi

# 5) Lockfile guard: ensure lockfile version is v3 (current cargo format)
Expand Down
86 changes: 34 additions & 52 deletions .githooks/pre-push
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#!/usr/bin/env bash
set -euo pipefail
PINNED="${PINNED:-1.71.1}"
# MSRV floor for library checks (override with MSRV env)
MSRV="${MSRV:-1.71.1}"
# Resolve the pinned toolchain from rust-toolchain.toml, fallback to explicit env or a sane default
PINNED_FROM_FILE=$(awk -F '"' '/^channel/ {print $2}' rust-toolchain.toml 2>/dev/null || echo "")
PINNED="${PINNED:-${PINNED_FROM_FILE:-1.90.0}}"

for cmd in cargo rustup rg; do
if ! command -v "$cmd" >/dev/null 2>&1; then
Expand All @@ -13,64 +13,46 @@ done

echo "🐰 BunBun 🐇"

if [[ "${SKIP_HOOKS:-}" == 1 ]]; then
exit 0
fi

echo "[pre-push] fmt (default toolchain)"
echo "[pre-push] fmt (stable)"
cargo fmt --all -- --check

echo "[pre-push] clippy (workspace, default toolchain)"
echo "[pre-push] clippy (workspace, stable)"
cargo clippy --all-targets -- -D warnings -D missing_docs

echo "[pre-push] tests (workspace, default toolchain)"
echo "[pre-push] tests (workspace, stable)"
cargo test --workspace

echo "[pre-push] Testing against MSRV ${MSRV} (core libraries)"
# If any participating crate declares a rust-version greater than MSRV, skip MSRV checks entirely.
CORE_RV=$(awk -F '"' '/^rust-version/ {print $2}' crates/rmg-core/Cargo.toml 2>/dev/null || echo "")
GEOM_RV=$(awk -F '"' '/^rust-version/ {print $2}' crates/rmg-geom/Cargo.toml 2>/dev/null || echo "")
if { [[ -n "$CORE_RV" ]] && printf '%s\n%s\n' "$MSRV" "$CORE_RV" | sort -V | tail -n1 | grep -qx "$CORE_RV" && [[ "$CORE_RV" != "$MSRV" ]]; } \
|| { [[ -n "$GEOM_RV" ]] && printf '%s\n%s\n' "$MSRV" "$GEOM_RV" | sort -V | tail -n1 | grep -qx "$GEOM_RV" && [[ "$GEOM_RV" != "$MSRV" ]]; }; then
echo "[pre-push] Skipping MSRV block: one or more crates declare rust-version > ${MSRV} (core=${CORE_RV:-unset}, geom=${GEOM_RV:-unset})"
else
if ! rustup run "$MSRV" cargo -V >/dev/null 2>&1; then
echo "[pre-push] MSRV toolchain ${MSRV} not installed. Install via: rustup toolchain install ${MSRV}" >&2
exit 1
fi
# Only run MSRV tests for crates that declare rust-version <= MSRV; skip otherwise.
msrv_ok() {
local crate="$1"
local rv
rv=$(awk -F '"' '/^rust-version/ {print $2}' "crates/${crate}/Cargo.toml" 2>/dev/null || echo "")
if [[ -z "$rv" ]]; then
return 0
fi
# If declared rust-version is greater than MSRV, skip.
if printf '%s\n%s\n' "$MSRV" "$rv" | sort -V | tail -n1 | grep -qx "$rv" && [[ "$rv" != "$MSRV" ]]; then
echo "[pre-push] Skipping MSRV test for ${crate} (rust-version ${rv} > MSRV ${MSRV})"
return 1
fi
# If crate depends on workspace rmg-core whose rust-version exceeds MSRV, skip as well
if grep -qE '^rmg-core\s*=\s*\{[^}]*path\s*=\s*"\.\./rmg-core"' "crates/${crate}/Cargo.toml" 2>/dev/null; then
local core_rv
core_rv=$(awk -F '"' '/^rust-version/ {print $2}' "crates/rmg-core/Cargo.toml" 2>/dev/null || echo "")
if [[ -n "$core_rv" ]] && printf '%s\n%s\n' "$MSRV" "$core_rv" | sort -V | tail -n1 | grep -qx "$core_rv" && [[ "$core_rv" != "$MSRV" ]]; then
echo "[pre-push] Skipping MSRV test for ${crate} (depends on rmg-core ${core_rv} > MSRV ${MSRV})"
return 1
fi
fi
return 0
}
if msrv_ok rmg-core; then cargo +"$MSRV" test -p rmg-core --all-targets; fi
if msrv_ok rmg-geom; then cargo +"$MSRV" test -p rmg-geom --all-targets; fi
fi
# MSRV lane removed: policy is stable everywhere.

# Rustdoc warnings guard (public crates)
echo "[pre-push] rustdoc warnings gate (rmg-core @ $PINNED)"
RUSTDOCFLAGS="-D warnings" cargo +"$PINNED" doc -p rmg-core --no-deps
echo "[pre-push] rustdoc warnings gate (rmg-geom @ $PINNED)"
RUSTDOCFLAGS="-D warnings" cargo +"$PINNED" doc -p rmg-geom --no-deps
required_crates=(rmg-core rmg-geom)
optional_crates=(rmg-ffi rmg-wasm)
missing_required=0

for krate in "${required_crates[@]}"; do
if [ -f "crates/${krate}/Cargo.toml" ]; then
echo "[pre-push] rustdoc warnings gate (${krate} @ $PINNED)"
RUSTDOCFLAGS="-D warnings" cargo +"$PINNED" doc -p "${krate}" --no-deps
else
echo "[pre-push] ERROR: required crate missing: crates/${krate}/Cargo.toml" >&2
missing_required=1
fi
done

for krate in "${optional_crates[@]}"; do
if [ -f "crates/${krate}/Cargo.toml" ]; then
echo "[pre-push] rustdoc warnings gate (${krate} @ $PINNED)"
RUSTDOCFLAGS="-D warnings" cargo +"$PINNED" doc -p "${krate}" --no-deps
else
echo "[pre-push] skipping ${krate}: missing crates/${krate}/Cargo.toml"
fi
done

if [ "$missing_required" -ne 0 ]; then
echo "[pre-push] One or more required crates are missing; aborting push." >&2
exit 1
fi

# Banned patterns
echo "[pre-push] scanning banned patterns"
Expand Down
44 changes: 38 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
- uses: actions/checkout@v4
with:
submodules: false
- uses: dtolnay/rust-toolchain@1.71.1
- uses: dtolnay/rust-toolchain@1.90.0
- uses: Swatinem/rust-cache@v2
with:
workspaces: |
Expand All @@ -30,7 +30,7 @@ jobs:
- uses: actions/checkout@v4
with:
submodules: false
- uses: dtolnay/rust-toolchain@1.71.1
- uses: dtolnay/rust-toolchain@1.90.0
with:
components: clippy
- uses: Swatinem/rust-cache@v2
Expand All @@ -47,7 +47,7 @@ jobs:
- uses: actions/checkout@v4
with:
submodules: false
- uses: dtolnay/rust-toolchain@1.71.1
- uses: dtolnay/rust-toolchain@1.90.0
- uses: Swatinem/rust-cache@v2
with:
workspaces: |
Expand Down Expand Up @@ -89,15 +89,47 @@ jobs:
exit 1;
}

# MSRV job removed per policy: use @stable everywhere

rustdoc:
name: Rustdoc (rmg-core warnings gate)
name: Rustdoc (warnings gate)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: false
- uses: dtolnay/rust-toolchain@1.71.1
- uses: dtolnay/rust-toolchain@1.90.0
- uses: Swatinem/rust-cache@v2
- name: rustdoc warnings gate
- name: rustdoc warnings gate (rmg-core)
run: RUSTDOCFLAGS="-D warnings" cargo doc -p rmg-core --no-deps
- name: rustdoc warnings gate (rmg-geom)
run: RUSTDOCFLAGS="-D warnings" cargo doc -p rmg-geom --no-deps
- name: rustdoc warnings gate (rmg-ffi)
run: |
if [ -f crates/rmg-ffi/Cargo.toml ]; then RUSTDOCFLAGS="-D warnings" cargo doc -p rmg-ffi --no-deps; fi
- name: rustdoc warnings gate (rmg-wasm)
run: |
if [ -f crates/rmg-wasm/Cargo.toml ]; then RUSTDOCFLAGS="-D warnings" cargo doc -p rmg-wasm --no-deps; fi

audit:
name: Security Audit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@1.90.0
- name: Install cargo-audit (latest)
run: cargo install cargo-audit --locked
- name: Run cargo audit
env:
CARGO_TERM_COLOR: always
run: cargo audit --deny warnings

deny:
name: Dependency Policy (cargo-deny)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: Run cargo-deny
uses: EmbarkStudios/cargo-deny-action@v1
33 changes: 33 additions & 0 deletions .github/workflows/security-audit.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Security Audit

on:
push:
branches:
- main
- "echo/**"
- "feat/**"
pull_request:

jobs:
audit:
name: Cargo Audit (stable)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: false
- uses: dtolnay/rust-toolchain@1.90.0
- uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
~/.cargo/bin/cargo-audit
key: audit-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}
- name: Install cargo-audit
run: |
if ! command -v cargo-audit >/dev/null; then
cargo install cargo-audit --locked
fi
- name: Run cargo audit
run: cargo audit --deny warnings
16 changes: 16 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,22 @@ Echo is a deterministic, renderer-agnostic engine. We prioritize:
- 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"`

#### Partial Staging & rustfmt
- rustfmt formats entire files, not only staged hunks. To preserve index integrity, our pre-commit hook now aborts the commit if running `cargo fmt` would change any files. It first checks with `cargo fmt --check`, and if changes are needed it applies them and exits with a helpful message.
- Workflow when this happens:
1) Review formatting changes: `git status` and `git diff`.
2) Restage intentionally formatted files (e.g., `git add -A` or `git add -p`).
3) Commit again.
- Tips:
- If you need to keep a partial-staged commit, do two commits: first commit the formatter-only changes, then commit your code changes.
- You can switch to check-only with `ECHO_AUTO_FMT=0` (commit will still fail on formatting issues, but nothing is auto-applied).
- Do not bypass hooks. The repo runs fmt, clippy, tests, and rustdoc on the pinned toolchain before push.
- Toolchain: pinned to Rust 1.90.0. Ensure your local override matches:

- rustup toolchain install 1.90.0
- rustup override set 1.90.0
- When any Rust code changes (.rs anywhere), update both `docs/execution-plan.md` and `docs/decision-log.md` with intent and a brief rationale. The hook enforces this.

## 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
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ members = [
]
resolver = "2"

[workspace.dependencies]
rmg-core = { version = "0.1.0", path = "crates/rmg-core" }

[profile.release]
opt-level = "s"
lto = true
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@

Most game engines are object-oriented state machines. Unity, Unreal, Godot all maintain mutable object hierarchies that update every frame. Echo says: "No, everything is a graph, and the engine rewrites that graph deterministically using typed transformation rules."

## Snapshot Hashes

Echo records two hashes during a commit:
- `state_root`: deterministic hash of the reachable graph state under the current root.
- `commit hash` (commit_id): hash of a canonical header including `state_root`, parents, and deterministic digests for plan/decisions/rewrites.

See `docs/spec-merkle-commit.md` for the precise encoding and invariants.

Echo is fundamentally **built different**.

RMG provides atomic, in-place edits of recursive meta-graphs with deterministic local scheduling and snapshot isolation.
Expand Down
4 changes: 2 additions & 2 deletions crates/rmg-core/src/footprint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ use crate::ident::{EdgeId, Hash, NodeId};
/// conflicts on boundary interfaces. The engine only requires stable equality
/// and ordering; it does not rely on a specific bit layout.
///
/// For demos/tests, use [`pack_port_key`] to derive a deterministic 64‑bit key
/// from a [`NodeId`], a `port_id`, and a direction flag.
/// For demos/tests, use [`pack_port_key`](crate::footprint::pack_port_key) to derive a
/// deterministic 64‑bit key from a [`NodeId`], a `port_id`, and a direction flag.
pub type PortKey = u64;

/// Simple ordered set of 256‑bit ids based on `BTreeSet` for deterministic
Expand Down
2 changes: 2 additions & 0 deletions crates/rmg-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
clippy::module_name_repetitions,
clippy::use_self
)]
// Permit intentional name repetition for public API clarity (e.g., FooFoo types) and
// functions named after their module for discoverability (e.g., `motion_rule`).

/// Deterministic math subsystem (Vec3, Mat4, Quat, PRNG).
pub mod math;
Expand Down
Loading