Synvael/CLAUDE.md

112 lines
9.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project goal
Voxel-based game with souls-like combat. Built in Rust; rendering targets Vulkan via [`ash`](https://github.com/ash-rs/ash) (raw Vulkan bindings, not a higher-level wrapper like `wgpu` or `vulkano`).
World is procedurally generated. Voxel edge length is half of Minecraft's (0.5 m), so the player occupies **3 blocks tall × 2 blocks wide**. This finer grid is load-bearing for design decisions: collision, mesh chunking, LOD thresholds, and network bandwidth all need to assume ~8× the voxel count of a Minecraft-scale world per unit volume — pick chunk sizes and data layouts accordingly. Supports both single-player and multiplayer via a dedicated server — that dual mode is why `server` exists as its own crate even for solo play (the single-player path is expected to run the server logic in-process or invoke the same crate, rather than having a separate offline code path).
## Workspace layout
Cargo workspace (resolver = "3", edition 2024) with four crates under `crates/`:
- `client` — binary. Windowed application using `winit` 0.30 (`ApplicationHandler` pattern, `ControlFlow::Poll`). Also pulls in `image`. Player-facing app titled "Project Catalyst"; handles input, windowing, and drives the renderer.
- `server` — binary. Authoritative game simulation (voxel world, combat, players). Used both for dedicated multiplayer hosts and as the simulation backend for single-player.
- `renderer` — library. Voxel/scene rendering on Vulkan via `ash`, decoupled from windowing so it can be driven by `client`.
- `shared` — library. Types and protocol shared between `client` and `server` (world/voxel data, network messages, combat primitives). Stays lean and dep-light; no `mlua`, no rendering, no engine internals.
- `scripting` — library. Lua modding API and bindings (owns the `mlua` dependency, `UserData` wrappers around `shared` types, API table registration, mod loader). Both `client` and `server` depend on it.
When adding code, keep the boundary tight: protocol/data types and game-rule primitives go in `shared`; Lua API surface and `mlua` integration in `scripting`; GPU/draw code in `renderer`; only input, windowing, and presentation glue live in `client`. Avoid growing `client` with simulation logic since it must work identically against either a local or remote `server`.
## Modding API (Lua) — dogfooded
The game exposes a Lua modding API, and **the base game itself is built on top of that same API** rather than treating it as a separate add-on layer. Built-in content (blocks, items, entities, recipes, etc.) is defined through the modding API so that mod authors can read the shipped code as reference for what's possible and how to do it.
This has hard implications when adding features:
- Any new gameplay primitive (a new block type, item, entity, ability, …) needs to be reachable through the Lua API, not just a Rust-only path. If you add a Rust-side concept without an API surface, you've broken the dogfooding contract.
- Prefer extending the API and then *using* it from the engine over adding a parallel Rust-only entry point.
- Keep the API stable and discoverable — mod authors will be reading it. Avoid leaking engine internals through it.
- The API and its bindings live in the **`scripting`** crate. It owns the `mlua` dependency, the API table registration, and the mod loader. Both `client` and `server` depend on it; `shared` does **not**`shared` stays the lean protocol/data layer.
- Authoritative APIs (world mutation, combat resolution) are defined in `scripting` but gated so the client-side Lua VM can't invoke them. One API surface, two execution contexts: client VM = read-only/UI/effects, server VM = authoritative.
- Prefer wrapper newtypes inside `scripting` over `impl UserData for SharedType` in `shared`, to avoid coupling the protocol crate to `mlua`.
## Assets
All game assets live under `/assets` at the repo root, organised into subfolders by kind: `icons/`, `models/`, `shaders/`, `sounds/`, `textures/`, `scripts/`. New assets must be placed in the matching subfolder — do not drop loose files into `/assets` itself, and do not scatter assets inside crate directories.
## Script locations
Three distinct locations, do not mix them:
- **`/assets/scripts/`** — the base game's own Lua, shipped with the binary. This is the dogfooded "first-party mod" the engine loads through the same API mod authors use. Mirror the structure modders will use (e.g. `scripts/blocks/`, `scripts/items/`, `scripts/entities/`) so it serves as a working reference.
- **`/mods/`** (top-level) — in-repo example mods or test fixtures. Kept out of `/assets/` because they're not engine-shipped content, and out of `crates/` because they're not Rust source.
- **`<user-data-dir>/mods/`** — player-installed mods, loaded at runtime only. Resolved via the `directories` / `dirs` crate (Linux: `~/.local/share/project-catalyst/mods/`, with platform equivalents elsewhere). Never read from a hard-coded path.
## Data packs & resource packs
Two distinct, orthogonal systems — keep them separate, do not collapse them into one "pack" concept.
**Resource packs** — client-side asset overlays. Textures, sounds, models, fonts, language files. No logic. A pack is a directory tree mirroring `/assets/` that overrides files by path. The renderer/asset loader resolves logical asset IDs against a stack of pack roots (base game → installed packs by priority) and the topmost hit wins. Server has no involvement. Owned by the asset pipeline (in `client`, or a sibling `assets` crate if it grows). Pack authors never touch Lua.
**Data packs** — declarative content definitions in JSON (or TOML/RON, TBD): blocks, items, recipes, loot tables, biomes, tags. **Do not build a parallel registration system** — the loader reads the JSON and calls the same Lua API the engine and Lua mods use. One source of truth: `data/blocks/stone.json` → loader → `blocks.register{ id = "stone", … }`. Loader belongs in `scripting` (or a sibling crate if it grows). Engine first-party content may use either JSON or Lua, whichever fits.
**Canonical load order** (later layers override earlier ones):
```
base game (assets/scripts + assets/data)
→ data packs (declarative content add/override)
→ Lua mods (full API access)
→ resource packs (client-only, asset overlay — always last so visuals win)
```
**Repo layout:**
```
/assets/
data/ # base-game declarative content
blocks/ items/ recipes/ …
scripts/ # base-game Lua (behavior)
textures/ models/ sounds/ icons/ shaders/ # base-game assets
```
**User-data layout** (runtime, resolved via `directories`/`dirs`):
```
<user-data>/
mods/ # Lua mods
datapacks/ # JSON content packs
resourcepacks/ # asset overlays (client only)
```
Every data-pack schema you accept is a stable contract, same as the Lua API. Version it deliberately.
## How the user wants to work with you
The user is **learning** most of the stack used here (Rust, Vulkan/`ash`, `winit`, `mlua`, voxel engines, networking, etc.) and wants you to act as a **teacher**, not as an autocomplete. This shapes every response:
- **Do not write the code outright.** Explain the concept, walk through the reasoning, point at what they'd need to write and why. Let them attempt it.
- **Exception:** if the user explicitly says they're stuck, struggling, or asks for the code directly ("just show me", "I give up", "write it"), then provide it — and explain it line by line afterwards.
- **Explain thoroughly.** Don't assume familiarity with crate APIs, idioms, GPU concepts, lifetimes, traits, etc. When you use a term that's non-obvious in context, define it briefly. Prefer "why" over "what" — they can read docs for "what".
- **Link to resources.** Point at official docs (rust-lang.org/Rustonomicon, Ash examples, `winit` docs, Khronos Vulkan spec, `mlua` book, vkguide.dev, "Learn Wgpu", etc.) when a topic deserves deeper reading than a chat reply can give. Prefer canonical sources over random blog posts.
- **Show worked examples and analogies** when a concept is abstract (lifetimes, descriptor sets, command buffers, ECS, network reconciliation). A small concrete example beats a paragraph of theory.
- **Ask before acting** when a task could be done several ways — surface the trade-offs and let the user pick the path, rather than picking silently.
- **Reviewing user-written code is fair game.** When the user writes something and asks for feedback, point out issues, suggest improvements, and explain *why* — that's still teaching.
In short: optimise for the user's understanding growing over time, not for the fastest path to working code.
## Common commands
```bash
cargo build # build all crates
cargo run -p client # run the windowed client
cargo run -p server # run the server
cargo test # run all tests
cargo test -p renderer it_works # run a single test by name
cargo check -p <crate> # fast type-check one crate
cargo clippy --all-targets
cargo fmt
```