Skip to content

fix(physics): memory safety + FFI hardening - #5

Merged
steeltroops-ai merged 3 commits into
mainfrom
audit/sp1-memory-safety
Apr 28, 2026
Merged

fix(physics): memory safety + FFI hardening#5
steeltroops-ai merged 3 commits into
mainfrom
audit/sp1-memory-safety

Conversation

@steeltroops-ai

@steeltroops-ai steeltroops-ai commented Apr 28, 2026

Copy link
Copy Markdown
Owner

Three small fixes to the physics engine and the worker bridge.

SAB shadow-curve overflow. The shadow-curve writer in gravitas-wasm could write 64 (x, y) points plus a 128-slot clear loop into the PHYSICS block, which only holds 128 f32 slots and reserves 16 for scalars. The 64-point write overflowed by 15 slots into TELEMETRY, corrupting the FPS counter and frame-index reads on the main thread. Capped at 56 points with a const-time assert and a debug_assert per write.

FFI safety contract. attach_sab accepted any pointer JS handed it. Added null + 4-byte alignment checks (returns Result) and documented the SAFETY contract on the unsafe block in tick_sab so the invariants the caller must uphold are visible at the callsite.

Worker panic hook. init_hooks was exported from gravitas-wasm but never invoked, so any Rust panic in the worker was swallowed as a generic "worker terminated" message. Now called immediately after the WASM module loads.

Renormalize discriminant. renormalize_null solved a quadratic in p_r and silently no-op'd when the discriminant went slightly negative (rounding noise after long integration runs), leaving the caller with stale state on the wrong side of the null cone. Replaced with a three-band guard: nonneg passes through; values in [-1e-12, 0) clamp to zero; anything more negative returns NormalizationError. The integrator maps the error to a new NormalizationFailure termination reason.

Test plan

  • cargo test --manifest-path physics-engine/Cargo.toml — clean
  • bun run test — 326/326 pass
  • bun run build:wasm — clean
  • New: physics-engine/gravitas-wasm/tests/sab_bounds.rs (4 cases, layout invariants)
  • New: physics-engine/gravitas-core/tests/normalize.rs (4 cases, three-band coverage)

Summary by CodeRabbit

  • Bug Fixes

    • Physics engine now properly detects and terminates invalid calculations that drift off the null cone, rather than silently continuing with corrupted state.
  • Improvements

    • Enhanced validation and error reporting for physics engine initialization.
    • Shadow curve rendering now includes proper boundary checks and point capping for improved stability.

Three FFI hygiene issues addressed together because they all live in
the gravitas-wasm shim and shared the same root cause (the SAB pointer
contract was never written down).

The shadow-curve writer was unbounded and overflowed the PHYSICS block
into TELEMETRY by 15 f32 slots, corrupting FPS and frame-index reads
on the main thread. Cap the writer at SHADOW_CURVE_MAX_POINTS (56)
with a const-time assert that the curve fits and a debug_assert per
write.

attach_sab accepted any pointer JS handed it. While today's code path
runs on an internal Rust-owned buffer, future callers shouldn't be
able to feed a null or misaligned address. Make it return Result with
runtime checks, and document the SAFETY contract on the unsafe block
in tick_sab.

Also reword the CONTROL[1..3] consume-on-read comment so future readers
understand the small race window (one frame's input lost worst case at
60-75 Hz).
The Rust panic hook in gravitas-wasm was exported but never invoked,
so any panic inside the worker terminated it silently with the
browser-generic "worker terminated" message instead of propagating
the actual stack trace.

Call init_hooks immediately after the dynamic import resolves and
before constructing PhysicsEngine.
renormalize_null projects p_r onto the null-cone via a quadratic in
p_r, so it relies on sqrt(B^2 - 4AC). Floating-point rounding over
long integration runs can push that discriminant slightly negative;
the previous code simply skipped the renormalize when it saw a
negative value, leaving the caller with stale state on the wrong
side of the null cone.

Split the discriminant into three bands. Nonneg passes through
unchanged. Values in [-1e-12, 0) clamp to zero (rounding noise,
the geodesic is on the cone within machine precision). Anything
below -1e-12 returns a NormalizationError; the integrator maps that
to a new NormalizationFailure termination reason so trajectory
consumers see a clean failure instead of integrating garbage.
@coderabbitai

coderabbitai Bot commented Apr 28, 2026

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 52b2ef93-6dd6-4aaf-bfcd-52eb306b9889

📥 Commits

Reviewing files that changed from the base of the PR and between b9624f0 and caa096d.

📒 Files selected for processing (8)
  • physics-engine/gravitas-core/src/geodesic/mod.rs
  • physics-engine/gravitas-core/src/geodesic/termination.rs
  • physics-engine/gravitas-core/src/invariants/mod.rs
  • physics-engine/gravitas-core/src/invariants/renormalization.rs
  • physics-engine/gravitas-core/tests/normalize.rs
  • physics-engine/gravitas-wasm/src/lib.rs
  • physics-engine/gravitas-wasm/tests/sab_bounds.rs
  • src/workers/physics.worker.ts

📝 Walkthrough

Walkthrough

This pull request introduces error handling for renormalization failures in geodesic integration. The renormalize_null function is refactored to return a Result type with discriminant guards to detect and report unrecoverable drift, while the integrate function now terminates early on normalization failures. Additionally, WASM physics engine updates include SAB protocol validation and shadow curve management improvements.

Changes

Cohort / File(s) Summary
Renormalization Error Handling
physics-engine/gravitas-core/src/invariants/renormalization.rs, physics-engine/gravitas-core/src/invariants/mod.rs
renormalize_null now returns Result<(), NormalizationError> with discriminant guards. Slightly-negative discriminants within tolerance are clamped; values more negative trigger NegativeDiscriminant error. Early exit when a_quad magnitude is negligible. New NormalizationError enum and ROUNDING_TOLERANCE constant exposed via module re-export.
Geodesic Integration Error Propagation
physics-engine/gravitas-core/src/geodesic/mod.rs, physics-engine/gravitas-core/src/geodesic/termination.rs
integrate now treats renormalization failures as immediate termination, returning Trajectory with new TerminationReason::NormalizationFailure variant. Failures on initial normalization prevent any steps; periodic renormalization failures halt further integration.
WASM SAB Protocol & Bounds
physics-engine/gravitas-wasm/src/lib.rs
SAB protocol updates: OFFSET_* values treated as f32 indices rather than byte offsets. New parameterized shadow-curve region within PHYSICS block with constants SHADOW_CURVE_OFFSET_IN_PHYSICS, SHADOW_CURVE_MAX_POINTS, SHADOW_CURVE_FLOATS. attach_sab now validates pointer (non-null, f32-aligned) and returns Result<(), JsValue>.
Renormalization Tests
physics-engine/gravitas-core/tests/normalize.rs
New test module validating discriminant-guard behavior: confirms properly null states return Ok, slightly-perturbed states recover, strongly off-null states fail with expected error, and constants remain within tolerance bands.
SAB Bounds Validation
physics-engine/gravitas-wasm/tests/sab_bounds.rs
New test module verifying SAB write layout invariants: enforces offset ordering (CONTROL < CAMERA < PHYSICS < TELEMETRY < LUTS), validates shadow-curve slot calculations, and ensures SHADOW_CURVE_FLOATS equals SHADOW_CURVE_MAX_POINTS * 2.
Worker Error Surfacing
src/workers/physics.worker.ts
WASM initialization now invokes init_hooks() to surface Rust panics as JavaScript console stack traces instead of silent worker termination.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 Through twisted spacetime paths we bound,
When renormalization fails profound,
With guarded guards and errors clear,
We catch the drift and disappear,
Null-cones preserved, our math stays sound! 🥕✨

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch audit/sp1-memory-safety

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@steeltroops-ai
steeltroops-ai merged commit 43cae02 into main Apr 28, 2026
0 of 2 checks passed
@steeltroops-ai
steeltroops-ai deleted the audit/sp1-memory-safety branch April 28, 2026 21:13

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: caa096d37e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +245 to +249
return Trajectory {
final_state: state,
termination: TerminationReason::NormalizationFailure,
steps_taken: steps,
max_hamiltonian_drift: max_drift,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Count and record the failed step before early return

This return path runs after a full integrator step has already mutated state, but it exits before steps += 1 and before appending to path. As a result, when TerminationReason::NormalizationFailure occurs, final_state is one step ahead of both steps_taken and the recorded path (so path.last() can differ from final_state when record_path is enabled), which breaks trajectory bookkeeping for downstream analysis and debugging.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant