diff --git a/.claude/commands/document.md b/.claude/commands/document.md new file mode 100644 index 000000000..2bf98da46 --- /dev/null +++ b/.claude/commands/document.md @@ -0,0 +1,35 @@ +Document the specified topic into the `docs/` folder. + +## Instructions + +1. **Identify the topic** from the user's prompt. If unclear, ask for clarification. +2. **Research thoroughly** — read all relevant source files, configs, and code before writing. Do not document from memory or assumptions. +3. **Choose the right location** in `docs/`: + - Use existing subdirectories if the topic fits (e.g., `docs/ci/` for CI/CD topics). + - Create a new subdirectory if documenting a distinct system (e.g., `docs/networking/`, `docs/ecs/`, `docs/storage/`). + - Use `README.md` as the main file in each subdirectory. + - Add additional files in the subdirectory for sub-topics if needed (e.g., `docs/networking/packet-format.md`). +4. **Writing style**: + - Be concise and direct. No fluff or filler. + - Use tables for structured data (configs, flags, comparisons). + - Include concrete examples (commands, code snippets, file paths). + - Document the "why" behind non-obvious decisions, not just the "what". + - Keep it accurate to the current state of the code — don't document aspirational or planned features. +5. **After writing**, update `CLAUDE.md` if the documented topic affects build commands, architecture descriptions, or development workflows already mentioned there. + +## Subdirectory Conventions + +``` +docs/ + ci/ — CI/CD pipelines, build profiles, caching, releases + architecture/ — System design, crate relationships, data flow + networking/ — Protocol, packets, connection lifecycle + storage/ — World storage, LMDB, chunk format + ecs/ — Bevy ECS patterns, components, systems, messages + commands/ — Command system, adding new commands + registry/ — Block/item registry, build-time codegen +``` + +Create subdirectories as needed. Not all of these need to exist from day one. + +$ARGUMENTS diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..3322e97fc --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,103 @@ +name: Release +on: + push: + tags: [ "v*" ] + +env: + CARGO_TERM_COLOR: always +defaults: + run: + shell: bash +jobs: + validate: + name: Validate + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v5 + - name: Install Rust nightly + uses: dtolnay/rust-toolchain@nightly + with: + components: rustfmt, clippy + targets: x86_64-unknown-linux-gnu + - uses: Swatinem/rust-cache@v2 + with: + shared-key: "ferrumc" + - name: Run cargo fmt + run: cargo fmt --all -- --check + - name: Run Clippy + run: cargo clippy --all-targets -- -D warnings + - name: Install cargo-nextest + uses: taiki-e/install-action@v2 + with: + tool: cargo-nextest + - name: Run Tests + run: cargo nextest run --target x86_64-unknown-linux-gnu --all-targets --all-features -E "not kind(bench)" + + build-release: + name: Build Release (${{ matrix.target }}) + needs: validate + runs-on: ${{ matrix.os }} + strategy: + matrix: + include: + - os: ubuntu-latest + target: x86_64-unknown-linux-gnu + - os: ubuntu-24.04-arm + target: aarch64-unknown-linux-gnu + - os: windows-latest + target: x86_64-pc-windows-msvc + - os: macos-14 + target: aarch64-apple-darwin + steps: + - name: Checkout repository + uses: actions/checkout@v5 + - name: Install Rust nightly + uses: dtolnay/rust-toolchain@nightly + with: + targets: ${{ matrix.target }} + - uses: Swatinem/rust-cache@v2 + with: + shared-key: "ferrumc" + - name: Build + run: cargo build --profile production --target ${{ matrix.target }} --all-features + - name: Package (Linux/macOS) + if: runner.os != 'Windows' + run: tar -czf ferrumc-${{ github.ref_name }}-${{ matrix.target }}.tar.gz -C target/${{ matrix.target }}/production ferrumc + - name: Package (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: Compress-Archive -Path target/${{ matrix.target }}/production/ferrumc.exe -DestinationPath ferrumc-${{ github.ref_name }}-${{ matrix.target }}.zip + - name: Checksum (Linux/macOS) + if: runner.os != 'Windows' + run: sha256sum ferrumc-${{ github.ref_name }}-${{ matrix.target }}.tar.gz > ferrumc-${{ github.ref_name }}-${{ matrix.target }}.tar.gz.sha256 + - name: Checksum (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: (Get-FileHash ferrumc-${{ github.ref_name }}-${{ matrix.target }}.zip -Algorithm SHA256).Hash.ToLower() + " ferrumc-${{ github.ref_name }}-${{ matrix.target }}.zip" | Out-File -Encoding ascii ferrumc-${{ github.ref_name }}-${{ matrix.target }}.zip.sha256 + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: ferrumc-${{ matrix.target }} + path: | + ferrumc-${{ github.ref_name }}-${{ matrix.target }}.* + + publish: + name: Publish Release + needs: build-release + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + merge-multiple: true + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + generate_release_notes: true + files: | + ferrumc-*.tar.gz + ferrumc-*.zip + ferrumc-*.sha256 diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index f913debbb..8838f54d7 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -37,65 +37,20 @@ jobs: tool: cargo-audit - name: Run Cargo Audit run: cargo audit --ignore RUSTSEC-2023-0071 - build: - name: Build and Upload Artifacts - if: github.ref == 'refs/heads/master' - runs-on: ${{ matrix.os }} - needs: [formatting_and_security, test] - strategy: - matrix: - include: - - os: macos-14 - target: aarch64-apple-darwin - - os: ubuntu-latest - target: x86_64-unknown-linux-gnu - - os: windows-latest - target: x86_64-pc-windows-msvc - - os: ubuntu-24.04-arm - target: aarch64-unknown-linux-gnu - steps: - - uses: actions/checkout@v5 - - - uses: dtolnay/rust-toolchain@nightly - with: - targets: ${{ matrix.target }} - - - uses: Swatinem/rust-cache@v2 - - - name: Build - run: cargo build --release --target ${{ matrix.target }} - - - name: Upload executable - uses: actions/upload-artifact@v4 - with: - name: ferrumc-${{ matrix.os }} - path: target/${{ matrix.target }}/release/ferrumc* - test: name: Run Tests - runs-on: ${{ matrix.os }} - strategy: - matrix: - include: - - os: macos-14 - target: aarch64-apple-darwin - - os: ubuntu-latest - target: x86_64-unknown-linux-gnu - - os: windows-latest - target: x86_64-pc-windows-msvc - - os: ubuntu-24.04-arm - target: aarch64-unknown-linux-gnu + runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v5 - name: Install Rust nightly uses: dtolnay/rust-toolchain@nightly with: - targets: ${{ matrix.target }} + targets: x86_64-unknown-linux-gnu - uses: Swatinem/rust-cache@v2 - name: Install cargo-nextest uses: taiki-e/install-action@v2 with: tool: cargo-nextest - name: Run Tests - run: cargo nextest run --target ${{ matrix.target }} --all-targets --all-features -E "not kind(bench)" + run: cargo nextest run --target x86_64-unknown-linux-gnu --all-targets --all-features -E "not kind(bench)" diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..76a1c4b8e --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,139 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +FerrumC is a high-performance Minecraft 1.21.8 (protocol 772) server implementation written in Rust. Not a framework (unlike Valence/Minestom) — this is a full vanilla server replacement prioritizing speed and memory efficiency over 1:1 vanilla parity. Uses Bevy ECS for game logic, Tokio for async networking, and LMDB for chunk storage. + +## Build & Development Commands + +```bash +# Build (requires Rust nightly) +cargo build # debug build +cargo build --release # release build +cargo build --profile production # release + LTO, stripped, single codegen unit (used by CI releases) +cargo build --profile hyper # max optimization (LTO, stripped, abort on panic) +cargo build --profile profiling # release + debug symbols +cargo build --features dashboard # include web dashboard + +# Run server +cargo run --release # defaults to "run" subcommand +cargo run --release -- setup # generate config.toml +cargo run --release -- import --import-path=/path/to/world # import vanilla world +cargo run --release -- clear # clear world data +cargo run --release -- --log=info # custom log level (trace/debug/info/warn/error) + +# Tests (CI uses cargo-nextest) +cargo nextest run --all-targets --all-features -E "not kind(bench)" +cargo nextest run -p ferrumc-nbt # single crate +cargo test -p ferrumc-core # alternative without nextest + +# Lints (all enforced in CI) +cargo fmt --all -- --check +cargo clippy --all-targets -- -D warnings +cargo audit --ignore RUSTSEC-2023-0071 + +# Profiling with Tracy +cargo run --release --features tracy +``` + +## Architecture + +### Workspace Structure + +Monorepo with ~30 crates under `src/`. All dependencies are declared at workspace level in the root `Cargo.toml` and referenced by member crates. + +- **`src/bin`** — Main binary. CLI parsing, server launch, game loop, packet handlers, ECS system registration. +- **`src/lib/core`** — Core primitives (chunks, connections, identity, transforms). `ferrumc-state` holds the global `ServerState` shared via `Arc`. +- **`src/lib/net`** — Network layer with custom Minecraft protocol implementation. Sub-crates: `codec` (packet encoding/decoding), `encryption` (AES-128-CFB8 + RSA). +- **`src/lib/adapters/nbt`** — Custom NBT parser, hand-crafted for performance. +- **`src/lib/adapters/anvil`** — Custom Anvil chunk format reader using memory-mapped I/O (`memmap2`) and `yazi` compression. +- **`src/lib/storage`** — Persistent KV storage via `heed` (LMDB-based). +- **`src/lib/world`** / **`world_gen`** — World management (chunk cache: `DashMap` with `WyHash`) and terrain generation. +- **`src/lib/scheduler`** — Timed schedule system for the game loop. +- **`src/lib/components`** / **`entities`** — Bevy ECS components (Health, Gamemode, Abilities) and entity definitions. +- **`src/lib/registry`** — Block/item registry using perfect hashing (`phf`) with build-time codegen. +- **`src/lib/commands`** / **`default_commands`** — Trait-based command system. +- **`src/lib/messages`** — Inter-system event messages for ECS. +- **`src/lib/dashboard`** — Optional web dashboard (Axum, feature-gated behind `dashboard`). +- **`src/lib/derive_macros`** — Procedural macros (e.g., `#[packet(...)]`). +- **`src/lib/utils/`** — Logging (tracing + Tracy), profiling, thread pool, general-purpose utilities. +- **`src/tests`** — Integration tests (NBT, codec, protocol). + +### Dependency Hierarchy + +``` +ferrumc-core → ferrumc-components → ferrumc-net → ferrumc (bin) +``` + +### Game Loop (`src/bin/src/game_loop.rs`) + +Timed schedules via Bevy ECS, managed by a custom `Scheduler`: + +- **tick** (configurable TPS, default 20): Main game logic — packet handling, player updates, commands, physics, mob AI. Uses `MissedTickBehavior::Burst` (catch up to 5 missed ticks). +- **world_sync** (15s): Persist world to disk. Skips if missed. +- **chunk_gc** (5s): Unload unused chunks from memory. Skips if missed. +- **keepalive** (1s, 250ms phase offset): Connection keepalives and ping updates. Skips if missed. + +### Networking Flow + +TCP connections → dedicated Tokio thread (single-threaded runtime) → `handle_connection` per client → crossbeam channels → ECS systems on main thread. + +Connection states: Handshake → Login → Configuration → Play + +### Key Architectural Decisions + +- **Bevy ECS** is the core concurrency model — lockless, multithreaded, zero-copy where possible. +- **Custom serialization everywhere** — NBT, Anvil, and network codec are hand-written for performance, not using off-the-shelf Minecraft protocol libraries. +- **`DashMap` with `WyHash`** for concurrent chunk caching. +- **`phf` perfect hashing** for O(1) block/item lookups at runtime, compiled into the binary. +- Prefer `Arc` over `Mutex` for read-heavy shared data. + +## Code Conventions + +- **No `unwrap()`** — use `expect("descriptive context")` or proper error handling (`match`/`if let`). +- **Avoid `.clone()`** unless necessary or in one-time startup paths. +- **New crates must define their own `thiserror`-based error types.** +- **New dependencies go in the workspace `Cargo.toml`**, not individual crate manifests. +- **Use `#[expect(lint)]` instead of `#[allow(lint)]`** so suppressions are flagged when unnecessary. +- **Tests that only generate/dump data must be `#[ignore]`d.** +- **Use `get_root_path()`** instead of chaining `../` for project-relative paths. No absolute paths. + +### Workspace Lints + +Denied at workspace level (will fail CI): `wildcard_dependencies`, `cast_lossless`, `cast_ptr_alignment`, `match_bool`, `mut_mut`, `borrow_as_ptr`, `infinite_loop`, `unused_unsafe`, `missing_abi`, `future_incompatible`. + +## Key Patterns + +### ECS (Bevy) + +- Components for entity data, Resources for global state, Systems for game logic. +- Messages (`src/lib/messages`) for inter-system event communication. + +### Packets + +Follow existing `#[packet(...)]` macro patterns in `ferrumc-net`. + +### Async + +- Tokio for async I/O (networking runs on a separate single-threaded Tokio runtime). +- crossbeam channels for thread-to-ECS communication (new connections, etc.). + +## Branch Naming + +- `feature/feature-name`, `fix/fixed-thing`, `rework/refactored-thing`, `housekeeping`, `docs` +- All PRs target `master`. + +## Documentation + +Project documentation lives in `docs/`, organized by topic in subdirectories (`docs/ci/`, `docs/networking/`, etc.). Use the `/document` command to add or update documentation. + +**All non-trivial systems, pipelines, and architectural decisions must be documented.** When making significant changes (new workflows, new crates, new systems, architectural shifts), update or create the relevant docs. Documentation should reflect the current state of the code, not aspirational designs. + +See `docs/` for the full documentation index. + +## Rules for Generated Code + +- Comments must be appropriate for an open source project with multiple contributors — they must NOT be aimed at any individual and must be timeless. +- Never co-author commits with Claude. diff --git a/Cargo.toml b/Cargo.toml index d2556cd0e..935727617 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -95,6 +95,13 @@ overflow-checks = false panic = "abort" codegen-units = 1 +[profile.production] +inherits = "release" +strip = "symbols" +lto = true +opt-level = 3 +codegen-units = 1 + [profile.bench] debug = true diff --git a/docs/ci/README.md b/docs/ci/README.md new file mode 100644 index 000000000..18d41f2c6 --- /dev/null +++ b/docs/ci/README.md @@ -0,0 +1,82 @@ +# CI/CD Pipeline + +FerrumC uses GitHub Actions for continuous integration and release automation. The pipeline is split into two workflows: one for PR validation and one for tagged releases. + +## Workflows + +### `rust.yml` — PR & Push CI + +**Triggers:** Pull requests to `master`, pushes to `master`, manual `workflow_dispatch`. + +**Concurrency:** Grouped by PR head ref. New pushes to the same PR cancel in-progress runs. + +| Job | Runner | What it does | +|---|---|---| +| **Formatting and Security** | `ubuntu-latest` | `cargo fmt --check`, `cargo clippy -D warnings`, `cargo audit` | +| **Run Tests** | `ubuntu-latest` | `cargo nextest run` with `--all-targets --all-features` (excludes benchmarks) | + +Both jobs run in parallel with no dependency between them. + +### `release.yml` — Tagged Release Pipeline + +**Triggers:** Push of tags matching `v*` (e.g., `v1.0.0`, `v0.2.0-rc1`). + +| Job | Runner | What it does | +|---|---|---| +| **Validate** | `ubuntu-latest` | Same checks as CI: fmt, clippy, nextest | +| **Build Release** | 4-platform matrix | Builds with `--profile production --all-features`, packages binaries, generates SHA256 checksums | +| **Publish Release** | `ubuntu-latest` | Creates GitHub Release with auto-generated notes, attaches all archives and checksums | + +#### Build Matrix + +| OS | Target | Archive Format | +|---|---|---| +| `ubuntu-latest` | `x86_64-unknown-linux-gnu` | `.tar.gz` | +| `ubuntu-24.04-arm` | `aarch64-unknown-linux-gnu` | `.tar.gz` | +| `windows-latest` | `x86_64-pc-windows-msvc` | `.zip` | +| `macos-14` | `aarch64-apple-darwin` | `.tar.gz` | + +#### Release Artifact Naming + +Archives: `ferrumc-{tag}-{target}.tar.gz` (or `.zip` for Windows) +Checksums: `ferrumc-{tag}-{target}.tar.gz.sha256` (or `.zip.sha256`) + +Example: `ferrumc-v1.0.0-x86_64-unknown-linux-gnu.tar.gz` + +## Build Profiles + +| Profile | Use case | Key settings | +|---|---|---| +| `dev` | Local development | Default + per-package optimizations for heavy deps (yazi, bevy_ecs, tokio) | +| `release` | Standard release | Cargo defaults | +| `production` | CI release builds | release + `strip`, `lto`, `opt-level = 3`, `codegen-units = 1` | +| `profiling` | Tracy profiling | release + `debug = true` | +| `hyper` | Maximum performance | production + `panic = "abort"`, no overflow checks, no debug assertions | + +The `production` profile is used by the release workflow. It's similar to `hyper` but keeps panic unwinding (no `panic = "abort"`), which is safer for a server binary — a panic unwinds and logs a backtrace instead of hard-crashing. + +## Caching + +Each job in `rust.yml` uses `Swatinem/rust-cache@v2` with **separate cache keys per job** (the default). This is intentional — the formatting job compiles to `target/debug/` while the test job compiles to `target/x86_64-unknown-linux-gnu/debug/` (due to the explicit `--target` flag). Sharing a cache key between them causes the test job to always miss, since GitHub Actions cache is immutable (first write wins). + +The release workflow uses `shared-key: "ferrumc"` across its jobs since the validate and build jobs can benefit from shared artifacts. + +### Cache performance + +With warm caches, typical CI times are: +- Formatting and Security: ~30-40s +- Run Tests: ~50-60s + +Cold cache (first run or after Cargo.lock changes): ~2-3 minutes per job. + +## Creating a Release + +1. Ensure `master` is in a releasable state (CI green). +2. Tag the commit: `git tag v1.0.0` +3. Push the tag: `git push origin v1.0.0` +4. The release workflow runs automatically: validate → build (4 platforms) → publish GitHub Release. +5. If the build fails on any platform, fix the issue and create a new tag. + +## Suppressed Advisories + +`cargo audit` ignores `RUSTSEC-2023-0071`. If this advisory is resolved upstream, the ignore can be removed from the workflow.