2.8 KiB
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
scriptingcrate. This crate owns themluadependency, the API table registration, and the mod loader. Bothclientandserverdepend on it, but crucially,shareddoes 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
scriptingcrate wrapssharedtypes in newtypes rather than implementingUserDatafor them directly insideshared. This keeps our core protocol/data crate completely free ofmlua.
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.