Skip to content

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Terminal window
cargo build -p nihil # build the compositor binary (debug)
cargo test -p nihil-core # unit tests (math verified against the Hypr3D dump)
cargo check -p nihil # fast typecheck

Run the compositor (nested mode, needs a running Wayland session):

Terminal window
./target/debug/nihil
# it creates $XDG_RUNTIME_DIR/wayland-N; clients: WAYLAND_DISPLAY=wayland-N alacritty

IPC: $XDG_RUNTIME_DIR/nihil.sock (Unix socket, commands: windows, list).

Any user-visible process must be launched via alacritty + the win.sh wrapper (tee to log, hold on Enter), per the user’s global CLAUDE.md.

Nihil (ENW): a Wayland compositor where the desktop is a 3D world and windows are objects in it. Read SPEC.md before any work — it is the single source of truth: decisions marked ✅ are settled (never change without explicit user approval), 💡 are candidate ideas (only implement if the current milestone needs them), unresolved questions go into SPEC section 7, never guess.

Architecture (two crates, strict split):

  • nihil-core — pure-Rust headless Bevy world: all scene logic (camera, window quads, movers, raycast, rooms, drag, panels). No GPU, no Wayland types. Smithay reads GlobalTransform out of the ECS each frame.
  • nihil-config — sandboxed Lua config (mlua, whitelisted stdlib) → compiled window rules (first-match-wins, AND across fields) + ONE timed hotkey DFA (double-tap windows, chords) + VRAM warnings. See configs/example.lua. Broken config → warn + defaults.
  • nihil — thin Wayland layer on Smithay 0.7 (winit backend, GLES renderer): protocols, buffers, input, and the 3D GLES draw pass. state.rs = handler glue + the Bevy App; state/winit.rs = event loop + frame; render.rs = raw-GLES quad pipeline (flips y-inverted shm textures in clip space); input.rs = keyboard/pointer routed through the hotkey DFA (nihil-config).

Key conventions (all verified against a memory dump of Hypr3D, the reference system):

  • World: right-handed, Y-up, meters, floor y=0, eye 1.6 m, vfov 65°.
  • Window = quad, size_m = logical_px / density (density is per-window, default 200 px/m). Changing density rescales the quad, never re-configures the client.
  • Camera: forward = (sin yaw·cos p, sin p, -cos yaw·cos p), up = right × forward, pitch clamp ±1.5.
  • Bevy 0.20: hierarchy uses ChildOf (no Parent); Transform::forward() == -local_z — window content faces -Z.
  • The Bevy renderer (wgpu) is not used; rendering is Smithay’s GLES via GlesRenderer::with_context.
  • Hit-test reads density at ray time (never cached); RaySource is a trait (VR later).

Network protocol (presence + shared windows, QUIC + FlatBuffers) is specced in PROTOCOL.md — protocol freezes before code, per SPEC.

  • Work in small steps per the milestones in SPEC.md section 8 (M0–M9, N0–N4); close each with its acceptance criterion.
  • No hidden limits: dangerous config values are allowed but warn + confirm.
  • Don’t render what isn’t visible (frame callbacks by visibility).
  • Commit with the trailer Co-Authored-By: Claude Code <[email protected]>.