docs(workspace): replace unicode ellipsis with ascii in docs
This commit is contained in:
parent
f7abc1bb10
commit
eb7a297f3e
14
AGENTS.md
14
AGENTS.md
|
|
@ -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.
|
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.
|
- **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.
|
- **[`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.
|
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:
|
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.
|
- 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.
|
- 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.
|
- 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.
|
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).
|
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:
|
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.
|
- 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.
|
- 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.
|
- 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.
|
- 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
|
## 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"`).
|
- **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.
|
- **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)]
|
[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.
|
- **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.
|
- **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.
|
- **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.
|
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
|
## Common commands
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.
|
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 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 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`.
|
- 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).
|
- New records are started from [`template.md`](template.md).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ The API is also exposed to two execution contexts, a client-side Lua VM and a se
|
||||||
|
|
||||||
## Decision
|
## 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.
|
- 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.
|
- 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.
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ This decision concerns **data packs** (declarative content). It is distinct from
|
||||||
|
|
||||||
## Decision
|
## 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.
|
Each data-pack schema is treated as a stable contract, versioned as deliberately as the Lua API.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ The forces at play: the technical situation, the constraints, and the problem th
|
||||||
|
|
||||||
## Decision
|
## 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
|
## Consequences
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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:
|
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.
|
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/
|
/assets/
|
||||||
data/ # base-game declarative content
|
data/ # base-game declarative content
|
||||||
blocks/ items/ recipes/ …
|
blocks/ items/ recipes/ ...
|
||||||
scripts/ # base-game Lua (behavior)
|
scripts/ # base-game Lua (behavior)
|
||||||
textures/ models/ sounds/ icons/ shaders/ # base-game assets
|
textures/ models/ sounds/ icons/ shaders/ # base-game assets
|
||||||
```
|
```
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue