Synvael/docs/adr/0006-base-game-on-modding-api.md
Serkyo cae434d227
Some checks are pending
CI / Rust Check & Lint (push) Waiting to run
CI / Rust Tests (push) Waiting to run
CI / Lua Lint & Format (push) Waiting to run
CI / LFS Pointer Guard (push) Waiting to run
docs(workspace): rewrite the subsystem notes and ADRs
2026-08-06 22:52:57 +02:00

30 lines
2.8 KiB
Markdown

# 0006. Base game built on the modding API
- **Status:** Accepted
- **Date:** 2026-06-27
## Context
The engine exposes a Lua modding API. When building a game engine, you generally have two options: treat the modding API as a bolt-on layer over a separate, highly privileged internal engine path, or force the engine to define its own content through the exact same API that mod authors use.
The first option usually leads to the engine drifting far ahead of the API. Mod authors end up with second-class capabilities and lack any working reference material to look at.
We also have to expose this API to two different execution contexts: a client-side Lua VM and a server-side Lua VM. These have completely different trust levels. Authoritative operations (like mutating the world or resolving combat) absolutely must not be invocable from the client VM.
## Decision
The base game is built entirely on top of the modding API. All shipped content (blocks, items, entities, recipes, etc.) is defined through the exact same API that mod authors use, allowing it to double as living reference material.
- If we add a new gameplay primitive, it must be reachable through the Lua API. We cannot add a Rust-internal path that skips the API. Adding a Rust-side concept with no API surface fundamentally breaks our dogfooding contract.
- The API and all of its bindings live in the `scripting` crate. This crate owns the `mlua` dependency, the API table registration, and the mod loader. Both `client` and `server` depend on it, but crucially, `shared` does not.
- We define authoritative APIs exactly once, but they are strictly **gated by execution context**. The client VM is locked down to read-only state, UI, and effects, while the server VM holds true authority. It is a single API surface running in two distinct contexts.
- The `scripting` crate wraps `shared` types in newtypes rather than implementing `UserData` for them directly inside `shared`. This keeps our core protocol/data crate completely free of `mlua`.
## Consequences
- Mod authors can confidently read our first-party content as a faithful example of what the API allows, simply because it doesn't use any privileged paths that they lack access to.
- The API must remain highly stable and easily discoverable because it is simultaneously the engine's surface and the modder's surface. Engine internals must never leak through it.
- We enforce the client/server trust boundary structurally at the API layer rather than relying on ad hoc checks everywhere.
- You simply cannot "add a feature to the engine" and then expose it to mods later as an afterthought. Creating the API surface is a mandatory part of the definition of done.
- Our declarative content loading strictly follows this same single-path rule; you can read more about that in [ADR-0007](0007-declarative-content-via-modding-api.md).