From 177beeed1e8ed5b4972edbd6f55cd997c3e83db2 Mon Sep 17 00:00:00 2001 From: Willow Sparks Date: Wed, 10 Jun 2026 09:53:17 +0100 Subject: [PATCH] docs: replace CLAUDE.md with minimal AGENTS.md + project skills Per arxiv 2602.11988: bloated context files reduce agent success rates and add cost; keep AGENTS.md to minimal requirements. Detailed knowledge moves to progressively-disclosed skills in .agents/skills/: - slice-invariant: the core engine invariant, regression tests, and the headless visual verification workflow - 4d-conventions: matrix/depth/rotation/GPU layout conventions --- .agents/skills/4d-conventions/SKILL.md | 49 +++++++++++++++++++ .agents/skills/slice-invariant/SKILL.md | 57 ++++++++++++++++++++++ AGENTS.md | 13 +++++ CLAUDE.md | 65 ------------------------- 4 files changed, 119 insertions(+), 65 deletions(-) create mode 100644 .agents/skills/4d-conventions/SKILL.md create mode 100644 .agents/skills/slice-invariant/SKILL.md create mode 100644 AGENTS.md delete mode 100644 CLAUDE.md diff --git a/.agents/skills/4d-conventions/SKILL.md b/.agents/skills/4d-conventions/SKILL.md new file mode 100644 index 0000000..6527b0f --- /dev/null +++ b/.agents/skills/4d-conventions/SKILL.md @@ -0,0 +1,49 @@ +--- +name: 4d-conventions +description: Rust4D math, matrix, rotation, and GPU conventions. Use when editing rust4d_math, Camera4D, WGSL shaders, projection matrices, or anything passing matrices between Rust and the GPU. Prevents convention-mismatch bugs (row/column major, depth range, rotation planes). +--- + +# Rust4D Conventions + +## Matrices + +- `Mat4 = [[f32; 4]; 4]` is **column-major**: `m[col][row]`. `mat4::transform` + computes `M * v`; `mat4::mul(a, b)` applies `b` first. +- Raw memory order matches WGSL `mat4x4` (first inner array = first + column), so matrices upload without conversion. +- `camera.camera_matrix()` is **camera→world**. The slice shader computes + `transpose(camera_mat) * (world_pos - camera_pos)` for world→camera; this is + only valid while the matrix stays orthogonal. + +## Depth + +- Projection uses wgpu's **[0, 1]** depth range (near→0, far→1), NOT OpenGL's + [-1, 1]. Pinned by `test_perspective_depth_range_is_wgpu_zero_to_one`. + +## 4D rotations (SkipY) + +- Camera orientation = `skip_y(rotation_4d) * pitch`. Pitch (YZ) is stored + separately and clamped; `rotation_4d` is a Rotor4 acting in XZW via SkipY, + so the Y (gravity) axis is never affected. +- SkipY maps the 3D rotation axes (X, Y, Z) onto 4D axes (X, Z, W). Therefore: + - pre-SkipY YZ plane → 4D **ZW** (`rotate_w`: forward tilts toward +W) + - pre-SkipY XZ plane → 4D **XW** (`rotate_xw`: right tilts toward +W) + - pre-SkipY XY plane → 4D **XZ** (yaw) +- Normalize rotors after composing (`.compose(&r).normalize()`). + +## Slicing pipeline + +- `SliceParams` is `#[repr(C)]` + bytemuck; field order and 16-byte alignment + must match the WGSL struct exactly. A struct-size unit test pins the layout — + update both sides together. +- The compute shader slices at camera-space W = `slice_w` (`camera.slice_offset`, + a camera-space offset, NOT a world W coordinate). +- Triangle counter counts vertices (×3) for DrawIndirect. + +## Scenes / config + +- Scenes are RON files in `scenes/`; config is TOML in `config/` + (`default.toml` < `user.toml` < env vars, via figment). +- Default speeds: `move_speed = 3.0`, `w_move_speed = 2.0` — anisotropic by + design; see the `slice-invariant` skill for why these must scale inputs, + never world axes. diff --git a/.agents/skills/slice-invariant/SKILL.md b/.agents/skills/slice-invariant/SKILL.md new file mode 100644 index 0000000..9f75774 --- /dev/null +++ b/.agents/skills/slice-invariant/SKILL.md @@ -0,0 +1,57 @@ +--- +name: slice-invariant +description: Verify the slice-plane invariant when changing Rust4D camera, movement, physics, or rendering code. Use before and after touching Camera4D, CharacterController4D, SimulationSystem, the slice shader, or movement input handling. Explains the invariant, the regression tests, and the headless visual verification workflow. +--- + +# The Slice Invariant + +For any world point `p`, its camera-space W coordinate `dot(camera.ana(), p - camera.position)` +must NOT change during WASD movement. Only deliberate ana movement (Q/E) and 4D +rotations may change it. The slice compute shader cuts geometry at exactly this +coordinate, so violating the invariant makes every cross-section on screen morph +while the player walks (the historical "4D movement bug"). + +## Rules that protect it + +1. **Never scale world axes anisotropically for movement.** Speeds scale + *semantic inputs* — slice movement (WASD) by `move_speed`, ana movement (Q/E) + by `w_move_speed` — each uniformly, BEFORE composing in world space. See + `CharacterController4D::apply_movement(physics, slice_dir, ana_dir)`. +2. WASD directions are projected to the horizontal XZW hyperplane (Y zeroed). + They stay orthogonal to `ana` because ana never has a Y component (SkipY). +3. The camera matrix must stay orthogonal (rotors are normalized after compose); + the shader relies on `transpose == inverse`. + +## Numeric verification (always run this) + +```bash +nix develop --command cargo test --test slice_invariant +``` + +Drives the full app stack (scene file, physics, controller, simulation) with a +fixed timestep and asserts zero drift across rotations, pitch, and key +combinations. Failure output prints `[MOVE]`/`[CAM]` lines with positions, ana +vectors, and per-frame drift. + +## Visual verification (for rendering-path changes) + +```bash +nix develop --command cargo run --example headless_protocol .scratchpad/captures +``` + +Renders a scripted protocol through the real slice + render pipelines into +offscreen textures — no window needed. Outputs: + +- PPM frames per phase (baseline, strafe control, 45° rotation, strafe-after- + rotation, forward, near approach). Convert: `magick f.ppm f.png`. +- `[STATE] slice_w(tesseract)=…` per capture — must stay constant within every + WASD phase (sign of `-0.000000` is fine). +- `[CAPTURE] … triangles=N` — triangle count may change at rotations only. + +What to compare across frames within a movement phase: cross-section shape and +color gradient must be constant; translation/parallax is fine. If the shape +shrinks/grows or the W color gradient shifts (e.g., purple → green), the slice +plane is drifting. + +To compare against a known-good build, run the example on both revisions into +different directories and `magick a.png b.png +append cmp.png`. diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..7d034bd --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,13 @@ +# Rust4D + +A 4D game engine in Rust: renders 3D cross-sections of 4D geometry via a GPU +compute pipeline, with 4D physics, Lua scripting, spatial audio, and an egui HUD. +Cargo workspace: app crate in `src/`, engine crates in `crates/rust4d_*`. + +## Requirements + +- Build and test inside the nix dev shell: `nix develop --command cargo test --workspace` +- Keep `cargo clippy --workspace --all-targets` warning-free +- Before changing camera, movement, physics, or slicing code, load the + `slice-invariant` skill; for math/shader conventions, load `4d-conventions` +- `tests/slice_invariant.rs` must pass — it guards the engine's core invariant diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index b56a2fd..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1,65 +0,0 @@ -# About This Project - -Rust4D is a 4D game engine written in Rust. It renders 3D cross-sections of -4D geometry via a GPU compute pipeline (tetrahedra slicing), with 4D physics, -Lua scripting, spatial audio, and an egui HUD. - -# Repository Map - -``` -Rust4D/ -├── src/ # Application crate (lib + bin) -│ ├── main.rs # Windowed app entry point (winit) -│ ├── lib.rs # Exports config, input, systems for tests/harnesses -│ ├── config.rs # TOML config (figment: defaults < files < env) -│ ├── input/ # Input mapping (raw events → semantic actions) -│ └── systems/ # Render, simulation, window, geometry systems -├── crates/ -│ ├── rust4d_math/ # Vec4, Rotor4, Mat4, shapes, rays -│ ├── rust4d_core/ # ECS world, scenes (RON), transforms, assets -│ ├── rust4d_render/ # wgpu pipelines, Camera4D, particles, sprites, HUD -│ ├── rust4d_physics/ # 4D rigid bodies, colliders, raycasts, triggers -│ ├── rust4d_input/ # CameraController (WASD + Q/E + mouse) -│ ├── rust4d_game/ # CharacterController4D, events, FSM, tweens -│ ├── rust4d_scripting/ # Lua 5.4 (mlua), sandboxed, hot-reload -│ └── rust4d_audio/ # kira-based 4D spatial audio -├── examples/ # Runnable demos + headless_protocol (visual verification) -├── tests/ # Integration tests (slice_invariant.rs is the key one) -├── scenes/ # RON scene definitions -├── config/ # default.toml + user.toml -├── flake.nix # Nix dev shell (toolchain, Vulkan, screenshot tools) -├── scratchpad/ # Symlink to shared Obsidian vault (see ~/.pi/agent/AGENTS.md) -└── .scratchpad/ # Local throwaway temp files (gitignored) -``` - -# Development - -Use the nix dev shell for builds — it provides the toolchain, Vulkan ICD -wiring, and imaging tools: - -```bash -nix develop --command cargo test --workspace -``` - -# The Slice Invariant (read before touching camera/movement/rendering) - -For any world point `p`, its camera-space W coordinate `dot(ana, p - cam_pos)` -must NOT change during WASD movement — only deliberate Q/E movement and 4D -rotations may change it. If this breaks, every cross-section on screen morphs -while walking (the historical "4D movement bug"). - -- Guarded by `tests/slice_invariant.rs` (drives the full app stack). -- Never scale world axes anisotropically for movement; scale semantic inputs - (slice movement vs ana movement) — see `CharacterController4D::apply_movement`. -- Visual verification: `cargo run --example headless_protocol ` renders - the protocol through the real GPU pipelines and saves PPM frames + numeric - `[STATE] slice_w` logs. Convert with `magick x.ppm x.png`. - -# Conventions - -- Matrices are column-major `[[f32; 4]; 4]` (`m[col][row]`), matching WGSL. - `camera_matrix()` is camera→world; the slice shader transposes for view. -- Depth convention is wgpu [0,1] (not OpenGL [-1,1]) — see `perspective_matrix`. -- 4D rotations go through SkipY: the 3D rotation axes (X, Y, Z) map onto the - 4D axes (X, Z, W), so Y (gravity) is never affected. `rotate_w` = ZW plane, - `rotate_xw` = XW plane. -- knotmirror 0.0.1