M0 scaffold: Godot 4 survival game skeleton + architecture/addon-SDK docs
This commit is contained in:
125
docs/ADDON_SDK.md
Normal file
125
docs/ADDON_SDK.md
Normal file
@@ -0,0 +1,125 @@
|
||||
# Addon SDK — design spec
|
||||
|
||||
This is the differentiating feature: **easy add-ons with guard rails.** Modders
|
||||
get a friendly, well-documented API; the server stays safe from buggy or
|
||||
malicious addons.
|
||||
|
||||
## Design goals
|
||||
1. **Easy to write.** A modder writes a small Lua script + a manifest. No build
|
||||
step, no engine knowledge.
|
||||
2. **Impossible to hurt the host.** No filesystem, no network, no arbitrary code
|
||||
against the engine, no hanging the server tick, no unbounded memory.
|
||||
3. **Server-authoritative.** Addons run on the server. Their effects replicate to
|
||||
clients like any other game state.
|
||||
4. **Content without code where possible.** Many mods (new items, recipes,
|
||||
building pieces) are pure *data* — declared in a manifest, no Lua needed.
|
||||
|
||||
## Why Lua (not WASM, not GDScript, not Python)
|
||||
- Lua is the industry-standard embedded game-modding language. Modders already
|
||||
know it (WoW, Factorio-ish, Garry's Mod, countless others).
|
||||
- A Lua VM is trivially sandboxable: you build the environment table yourself, so
|
||||
the addon literally cannot see anything you don't hand it.
|
||||
- GDScript was rejected: it can reach engine internals — too powerful for untrusted
|
||||
code.
|
||||
- WASM was considered (stronger isolation, authors use Rust/AssemblyScript) but is
|
||||
harder for casual modders. Keep it as a possible *advanced* lane later.
|
||||
- Embedding: a Lua-in-Godot binding (e.g. `godot-luaAPI` GDExtension) or a custom
|
||||
GDExtension wrapping Lua 5.4 / LuaJIT.
|
||||
|
||||
## Addon package layout
|
||||
```
|
||||
my_addon/
|
||||
addon.json manifest (required)
|
||||
main.lua entry script (optional — data-only addons skip it)
|
||||
items/ data definitions (optional)
|
||||
prefabs/ low-poly meshes/scenes the addon adds (optional, validated)
|
||||
assets/ textures, icons (optional, size-capped)
|
||||
```
|
||||
|
||||
### addon.json (manifest)
|
||||
```json
|
||||
{
|
||||
"id": "com.paul.ironpike",
|
||||
"name": "Iron Pike",
|
||||
"version": "1.0.0",
|
||||
"api": "1", // SDK API version it targets
|
||||
"author": "Paul",
|
||||
"permissions": ["items", "recipes", "events"], // explicit capability opt-in
|
||||
"entry": "main.lua" // omit for data-only addons
|
||||
}
|
||||
```
|
||||
- **Permissions are explicit.** An addon only gets the API groups it declares, and
|
||||
the server can refuse/limit them. A data-only addon declares no code perms at all.
|
||||
|
||||
## The sandbox (how the guard rails actually work)
|
||||
1. Each addon gets its **own fresh Lua VM** (isolation between addons).
|
||||
2. The global environment is a **whitelist table we build** — standard-library
|
||||
access is stripped: no `os`, `io`, `package`, `require`, `dofile`, `loadfile`,
|
||||
`load`, `debug`. Only safe primitives (`math`, `string`, `table`, `pairs`,
|
||||
`ipairs`, `tostring`, a sandboxed `print` that routes to the server log).
|
||||
3. The host injects a single global, `game`, which is the **entire API surface** —
|
||||
the only way an addon touches the world.
|
||||
4. **Instruction/time quota:** every addon callback runs under a hook that counts
|
||||
VM instructions (or wall-clock) and aborts if it exceeds the budget. An addon
|
||||
that loops forever is killed, the tick is never blocked.
|
||||
5. **Memory quota:** the VM's allocator is capped; exceeding it aborts the addon,
|
||||
not the server.
|
||||
6. **No reentry into the engine:** the `game` API functions are thin, validated
|
||||
shims written in GDScript/C++. The addon can't reach the Godot API, the
|
||||
filesystem, or the network.
|
||||
7. **Fault isolation:** an error in an addon callback is caught, logged with the
|
||||
addon id, and the addon can be disabled — the server keeps running.
|
||||
|
||||
## API surface (v1 sketch — `game.*`)
|
||||
Events (register handlers):
|
||||
```lua
|
||||
game.on("player_join", function(player) ... end)
|
||||
game.on("block_place", function(player, piece, pos) ... end) -- can veto
|
||||
game.on("tick", function(dt) ... end) -- quota-capped
|
||||
game.on("entity_death", function(entity, killer) ... end)
|
||||
```
|
||||
World queries (read-only, validated):
|
||||
```lua
|
||||
local p = game.players.get(id) -- safe player handle
|
||||
local n = game.world.count_entities("tree")
|
||||
local b = game.world.get_block(x, y, z) -- within loaded bounds only
|
||||
```
|
||||
World commands (intents the host validates & applies):
|
||||
```lua
|
||||
game.world.spawn("wolf", x, y, z) -- subject to limits/quotas
|
||||
game.chat.broadcast("Welcome, " .. p.name)
|
||||
game.inventory.give(p, "wood", 5) -- bounded
|
||||
```
|
||||
Content declaration (data, validated at load):
|
||||
```lua
|
||||
game.define_item{ id="iron_pike", name="Iron Pike", damage=42, stack=1 }
|
||||
game.define_recipe{ output="iron_pike", inputs={iron=10, wood=4}, station="forge" }
|
||||
```
|
||||
Everything an addon can do lives under `game.`. If it's not there, the addon can't
|
||||
do it — that's the guard rail.
|
||||
|
||||
## Limits the server enforces (configurable by the host owner)
|
||||
- Max addons loaded
|
||||
- Per-callback instruction/time budget
|
||||
- Per-addon memory ceiling
|
||||
- Rate limits on spawn/give/broadcast (so an addon can't spam)
|
||||
- Asset size caps (prefab poly count, texture dimensions)
|
||||
- Permission allow/deny per addon
|
||||
|
||||
## Threat model (what we explicitly defend against)
|
||||
| Threat | Defence |
|
||||
|--------|---------|
|
||||
| Addon reads server files / secrets | No `io`/`os`; no filesystem in the sandbox env |
|
||||
| Addon opens network connections | No socket library exposed |
|
||||
| Addon hangs the server (infinite loop) | Instruction/time quota aborts the callback |
|
||||
| Addon exhausts RAM | Per-VM memory cap |
|
||||
| Addon crashes the server | Every callback wrapped; errors isolate & disable the addon |
|
||||
| Addon escalates via another addon | Each addon = separate VM, no shared globals |
|
||||
| Addon spams spawns/messages | Host rate limits on world commands |
|
||||
| Malicious prefab (huge mesh) | Asset validation + poly/texture caps at load |
|
||||
|
||||
## A test we must pass (M3 done-criteria)
|
||||
Ship a deliberately-malicious example addon in the test suite that tries to:
|
||||
read a file, open a socket, loop forever, and allocate gigabytes. The server must
|
||||
contain all four, log them, disable the addon, and keep running. If that passes,
|
||||
the guard rails are real.
|
||||
Reference in New Issue
Block a user