docs(workspace): replace unicode ellipsis with ascii in docs

This commit is contained in:
Serkyo 2026-06-28 03:33:07 +02:00
parent f7abc1bb10
commit eb7a297f3e
6 changed files with 14 additions and 14 deletions

View file

@ -13,7 +13,7 @@ World is procedurally generated. Voxel edge length is 0.5 m, so the player occup
Documentation is layered by altitude; keep content at the layer it belongs to so no single file accretes everything.
- **This file (`AGENTS.md`)**: cross-cutting engineering conventions and architecture invariants, i.e. the rules that apply regardless of which feature is being touched. There is a finite set of these, so this file should grow slowly. Subsystem-specific specs do **not** belong here.
- **[`docs/`](docs/) + Rust module docs (`//!`, `///`)**: per-subsystem technical implementation docs. How an individual system (meshing, networking, worldgen, ) is built. Prefer module docs next to the code; promote to a `docs/<subsystem>.md` note when the design spans multiple files.
- **[`docs/`](docs/) + Rust module docs (`//!`, `///`)**: per-subsystem technical implementation docs. How an individual system (meshing, networking, worldgen, ...) is built. Prefer module docs next to the code; promote to a `docs/<subsystem>.md` note when the design spans multiple files.
- **[`docs/adr/`](docs/adr/)**: Architecture Decision Records capturing the *why* behind significant, hard-to-reverse choices, one append-only file per decision. See [`docs/README.md`](docs/README.md) for the full scheme and [`docs/adr/0001-record-architecture-decisions.md`](docs/adr/0001-record-architecture-decisions.md) for the practice.
The canonical game-*design* specification (intent, world rules, gameplay behaviour) is maintained separately and is not part of this repository; this repo documents how that design is implemented.
@ -36,7 +36,7 @@ The game exposes a Lua modding API, and **the base game itself is built on top o
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.
- 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, since 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**, since `shared` stays the lean protocol/data layer.
@ -63,7 +63,7 @@ Three distinct locations, do not mix them:
Two distinct, orthogonal systems; keep them separate, do not collapse them into one "pack" concept. **Resource packs** are client-side asset overlays (textures, sounds, models, fonts, language files; no logic). **Data packs** are declarative content definitions (JSON/TOML/RON): blocks, items, recipes, loot tables, biomes, tags.
The load-bearing rule: **do not build a parallel registration system.** The data-pack loader reads the declarative files and calls the same Lua API the engine and Lua mods use, giving one source of truth (`data/blocks/stone.json` → loader → `blocks.register{ }`). Each schema is a stable contract; version it deliberately. The decision is recorded in [ADR-0007](docs/adr/0007-declarative-content-via-modding-api.md).
The load-bearing rule: **do not build a parallel registration system.** The data-pack loader reads the declarative files and calls the same Lua API the engine and Lua mods use, giving one source of truth (`data/blocks/stone.json` → loader → `blocks.register{ ... }`). Each schema is a stable contract; version it deliberately. The decision is recorded in [ADR-0007](docs/adr/0007-declarative-content-via-modding-api.md).
Full subsystem detail (load order, repo and user-data layout, resolution semantics) lives in [`docs/packs.md`](docs/packs.md).
@ -83,7 +83,7 @@ Keep commits scoped to a single concept. Do not batch multiple unrelated changes
The game is **multithreaded by design**: single-threaded would not meet the perf budget for voxel meshing, worldgen, rendering, networking, and simulation running together. Code should assume multiple threads and design data ownership accordingly:
- Prefer message-passing (channels: `crossbeam-channel`, `flume`, or `std::sync::mpsc`) and per-thread ownership over shared mutable state.
- When sharing is unavoidable, use the right primitive for the access pattern: `Arc<Mutex<_>>` for low-contention shared state, `Arc<RwLock<_>>` for read-heavy, atomics (`AtomicU32`, `AtomicBool`, ) for counters and flags, lock-free structures (`crossbeam`, `dashmap`) for hot paths. Avoid wrapping large hot data in a single `Mutex` "just in case", which is how you accidentally serialise the whole engine.
- When sharing is unavoidable, use the right primitive for the access pattern: `Arc<Mutex<_>>` for low-contention shared state, `Arc<RwLock<_>>` for read-heavy, atomics (`AtomicU32`, `AtomicBool`, ...) for counters and flags, lock-free structures (`crossbeam`, `dashmap`) for hot paths. Avoid wrapping large hot data in a single `Mutex` "just in case", which is how you accidentally serialise the whole engine.
- Worldgen and chunk meshing are the obvious parallelism wins. A thread pool (e.g. `rayon`, or a hand-rolled one) feeding meshing/generation jobs is expected.
- Vulkan command-buffer recording can be parallelised too, but Vulkan **queues** are not free-threaded: only one thread submits to a given queue at a time. Plan ownership of `vk::Queue` accordingly.
- The Lua VMs (one per execution context: client, server) are **not** thread-safe in `mlua`'s default config; treat each VM as owned by a single thread, and dispatch work to/from it via channels.
@ -122,7 +122,7 @@ The game is **multithreaded by design**: single-threaded would not meet the perf
## Content IDs & namespacing
All registered content (blocks, items, recipes, biomes, entities, ) is identified by a **namespaced string** of the form `"namespace:id"`. The full rationale is in [ADR-0005](docs/adr/0005-namespaced-content-ids.md).
All registered content (blocks, items, recipes, biomes, entities, ...) is identified by a **namespaced string** of the form `"namespace:id"`. The full rationale is in [ADR-0005](docs/adr/0005-namespaced-content-ids.md).
- **Engine's reserved namespace:** `core:`. All first-party content registered by the base game uses it (`"core:stone"`, `"core:iron_sword"`). Mods pick their own short namespace (`"mymod:weird_dirt"`).
- **Strict form required.** A bare ID with no `:` is an **error at registration / parse time**, not silently coerced to `core:`. Same rule everywhere: engine scripts, data packs, Lua mods, recipe references, save files. No exceptions; the symmetry is the point.
@ -159,7 +159,7 @@ Format:
[optional footer(s)]
```
- **Type** (required, exactly one): `feat` (new feature), `fix` (bug fix), `refactor` (no behaviour change), `perf`, `docs`, `test`, `chore` (build/tooling/deps), `build`, `ci`. Breaking changes append `!` before the colon: `feat(scripting)!: `.
- **Type** (required, exactly one): `feat` (new feature), `fix` (bug fix), `refactor` (no behaviour change), `perf`, `docs`, `test`, `chore` (build/tooling/deps), `build`, `ci`. Breaking changes append `!` before the colon: `feat(scripting)!: ...`.
- **Scope** (required): the crate the change primarily affects, one of `client`, `server`, `renderer`, `shared`, `scripting`. For changes that genuinely span the whole workspace (e.g. workspace-level Cargo config, repo-wide `.gitattributes`), use `workspace`. For changes confined to non-Rust assets, use `assets`. Avoid omitting the scope, and avoid inventing per-commit scopes.
- **Subject:** imperative mood ("add", not "added" / "adds"), lowercase, no trailing period, ≤ ~72 chars.
- **Body:** keep commit messages short and simple, usually subject only, no body. The exception is `fix(...)` commits for non-trivial bugs, where a body explaining the root cause and why the fix works is valuable. Don't pad routine `feat`/`refactor`/`chore`/`docs` commits with bodies.
@ -177,7 +177,7 @@ feat(server)!: change tick rate from 20 to 30 Hz
If a single commit truly touches multiple crates and can't be reasonably split, that's a signal to split it. Only fall back to `workspace` scope when the change is intrinsically workspace-wide.
**Do not add any AI assistant as a co-author on commits.** No `Co-Authored-By: …` trailers for assistants, no "Generated with …" footers. Commits are authored by the human running the work.
**Do not add any AI assistant as a co-author on commits.** No `Co-Authored-By: ...` trailers for assistants, no "Generated with ..." footers. Commits are authored by the human running the work.
## Common commands

View file

@ -13,8 +13,8 @@ A durable, low-ceremony place is required to record *why* significant choices we
Architecture Decision Records (ADRs), in the lightweight format popularised by Michael Nygard, are used to capture significant, hard-to-reverse decisions.
- Each ADR is a single Markdown file in `docs/adr/`, numbered sequentially (`0001-…`, `0002-…`).
- Each record carries a status (`Proposed`, `Accepted`, `Deprecated`, or `Superseded by `) and a date.
- Each ADR is a single Markdown file in `docs/adr/`, numbered sequentially (`0001-...`, `0002-...`).
- Each record carries a status (`Proposed`, `Accepted`, `Deprecated`, or `Superseded by ...`) and a date.
- Records are **append-only**: once accepted, an ADR is not edited to reflect a later change of mind. A new ADR is written instead and the old one is marked `Superseded`.
- New records are started from [`template.md`](template.md).

View file

@ -11,7 +11,7 @@ The API is also exposed to two execution contexts, a client-side Lua VM and a se
## Decision
The base game is built on top of the modding API; the shipped content (blocks, items, entities, recipes, ) is defined through the same API mod authors use, so it doubles as reference material.
The base game is built on top of the modding API; the shipped content (blocks, items, entities, recipes, ...) is defined through the same API mod authors use, so it doubles as reference material.
- Any new gameplay primitive must be reachable through the Lua API, not only through a Rust-internal path. Adding a Rust-side concept with no API surface breaks the dogfooding contract.
- The API and its bindings live in the `scripting` crate, which owns the `mlua` dependency, the API table registration, and the mod loader. Both `client` and `server` depend on it; `shared` does not.

View file

@ -11,7 +11,7 @@ This decision concerns **data packs** (declarative content). It is distinct from
## Decision
The data-pack loader reads the declarative files and **calls the same Lua API** the engine and Lua mods use. There is one registration path: `data/blocks/stone.json` → loader → `blocks.register{ id = "stone", }`. No parallel registration system is built. The loader belongs in `scripting` (or a sibling crate if it grows). First-party content may use either JSON or Lua, whichever fits.
The data-pack loader reads the declarative files and **calls the same Lua API** the engine and Lua mods use. There is one registration path: `data/blocks/stone.json` → loader → `blocks.register{ id = "stone", ... }`. No parallel registration system is built. The loader belongs in `scripting` (or a sibling crate if it grows). First-party content may use either JSON or Lua, whichever fits.
Each data-pack schema is treated as a stable contract, versioned as deliberately as the Lua API.

View file

@ -9,7 +9,7 @@ The forces at play: the technical situation, the constraints, and the problem th
## Decision
The choice that was made, stated in the active, present tense ("The engine uses …", "Worldgen seeds from …"). One decision per record.
The choice that was made, stated in the active, present tense ("The engine uses ...", "Worldgen seeds from ..."). One decision per record.
## Consequences

View file

@ -15,7 +15,7 @@ Declarative content definitions in JSON (or TOML/RON, TBD): blocks, items, recip
No parallel registration system is built. The loader reads the declarative files 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", }
data/blocks/stone.json → loader → blocks.register{ id = "stone", ... }
```
The loader belongs in `scripting` (or a sibling crate if it grows). Engine first-party content may use either JSON or Lua, whichever fits. Every data-pack schema is a stable contract, the same as the Lua API, version it deliberately.
@ -36,7 +36,7 @@ base game (assets/scripts + assets/data)
```
/assets/
data/ # base-game declarative content
blocks/ items/ recipes/
blocks/ items/ recipes/ ...
scripts/ # base-game Lua (behavior)
textures/ models/ sounds/ icons/ shaders/ # base-game assets
```