M0 scaffold: Godot 4 survival game skeleton + architecture/addon-SDK docs

This commit is contained in:
dev
2026-11-03 22:23:20 +00:00
commit 180b776392
13 changed files with 724 additions and 0 deletions

125
docs/ADDON_SDK.md Normal file
View 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.

116
docs/ARCHITECTURE.md Normal file
View File

@@ -0,0 +1,116 @@
# Architecture
This is the big-picture design. It's deliberately written before heavy coding so
the hard decisions (networking authority, addon sandbox) are made up front, not
bolted on.
## 1. Guiding principles
1. **Server is authoritative.** The client is a renderer + input device. It never
decides game state (health, inventory, block placement, hits). This is the
anti-cheat baseline — you cannot bolt it on later.
2. **Same binary, two modes.** One Godot project runs as the client *and* as the
headless dedicated server (`--headless -- --server`). No separate codebase.
3. **Addons can never hurt the host.** Mods run in a sandbox (Lua) with a capped
API, CPU/memory quotas, and no filesystem/network/host access. Designed in from
day one.
4. **Low-poly + lighting, not textures.** Art pipeline stays cheap so a solo dev
can actually finish.
## 2. Process topology
```
┌────────────────────────────────────────────┐
│ Dedicated Server (headless Godot) │
│ - owns ALL game state │
│ - runs the simulation tick (fixed timestep) │
│ - loads & sandboxes addons │
│ - persists the world (save system) │
└───────────────▲───────────────▲──────────────┘
│ ENet/UDP │
┌───────────┴───┐ ┌───────┴─────────┐
│ Client A │ │ Client B │
│ render+input │ │ render+input │
│ client-side │ │ client-side │
│ prediction │ │ prediction │
└────────────────┘ └──────────────────┘
```
Single-player = the client spawns an in-process server (loopback). So
single-player and multiplayer share the exact same code path — no "SP special
case" that rots.
## 3. Networking
- **Transport:** Godot's built-in high-level multiplayer over **ENet** (reliable
UDP). Good for 2–16 players, which is the realistic self-hosted survival scale.
- **Model:** server-authoritative with client-side prediction + reconciliation for
the local player's movement (so it feels responsive), and interpolation for
remote entities (so they look smooth).
- **RPC discipline:** clients send *intents* ("I want to place a wall here",
"I swung at this"), never *results*. The server validates, applies, and
broadcasts the authoritative result.
- **Scale target:** 2–16 players per server. NOT Rust's 200. That's a deliberate
scope cut — 200-player netcode is a multi-year problem on its own.
## 4. World
- **Terrain:** heightmap-based mesh, chunked. Each chunk is a mesh the server
generates deterministically from a seed, so clients can regenerate geometry
locally and the server only syncs *edits* (not the whole mesh).
- **Generation:** deterministic noise (seed → biome map → heightmap → object
placement). Deterministic = client and server agree without syncing megabytes.
- **Biomes:** start with 2–3 (meadows, forest, maybe a harsher zone) à la Valheim.
- **Streaming:** load/unload chunks around each player. Server keeps a slightly
larger radius than clients.
## 5. Entities & simulation
- Fixed-timestep simulation tick on the server (e.g. 20–30 Hz). Rendering runs at
the client's framerate, decoupled.
- Entity types: player, resource node (tree/rock), dropped item, creature,
building piece, projectile.
- Each entity has a network id; the server owns it and replicates state deltas.
## 6. Building system
- **Snap-together prefabs:** walls, floors, roofs, stairs — each with snap points.
- **Structural rules (Valheim-style):** pieces need support; stability falls off
with distance from the ground, unsupported pieces collapse. Great emergent
gameplay, and it's a clean server-authoritative calculation.
- Placement is a client *intent*; the server validates snap + support + resource
cost, then commits.
## 7. Survival systems
- Health, stamina, hunger (and later temperature/comfort like Valheim).
- Crafting: recipes (inputs → output + required crafting station + skill level).
- Inventory: grid or slot-based, server-authoritative.
- Day/night cycle + weather driving lighting and creature behaviour.
## 8. Persistence
- World save = seed + chunk edits + entity states + player inventories.
- Server writes periodic snapshots + on clean shutdown. Format: Godot resources or
a simple binary/JSON hybrid. Keep it moddable-inspectable where cheap.
## 9. Addon SDK — the differentiator (full spec in ADDON_SDK.md)
- Addons are **Lua scripts** run inside a sandboxed Lua VM embedded in the server.
- The host exposes a **deliberately limited API surface** — the only things an
addon can touch. No `os`, no `io`, no `require` of host files, no raw sockets.
- **Quotas:** instruction-count / wall-time caps per callback (so an addon can't
hang the tick), and a memory ceiling per addon VM.
- **Event model:** addons register handlers (`on_player_join`, `on_block_place`,
`on_tick`, …). The host calls them inside the sandbox with the quota enforced.
- **Content model:** addons can also *declare* data (new items, recipes, prefabs)
via manifest files the host validates before loading.
- Addons run **server-side** (authoritative). Clients receive the resulting state
like any other. (A later client-side cosmetic addon lane is possible but separate
and even more locked down.)
## 10. Why Godot 4 + GDScript here
- Free, open-source, no licensing worry, exports to all three desktop OSes.
- Godot 4's renderer (SDFGI / voxel GI, volumetric fog) is enough for the Valheim
atmosphere without expensive assets.
- GDScript keeps iteration fast for a solo dev; drop to C++ GDExtension only for
hotspots (terrain meshing, noise) if profiling demands it.
- The Lua addon VM is embedded via a GDExtension or a Lua-in-Godot addon
(e.g. `godot-luaAPI`), keeping mods off GDScript (so a mod can't call engine
internals).
## 11. Deliberate scope cuts (so this can actually ship)
- 2–16 players, not 200.
- Desktop only first (no console/mobile).
- A handful of biomes, items, creatures for the vertical slice — breadth comes later.
- No photoreal art. Low-poly + lighting only.

58
docs/ROADMAP.md Normal file
View File

@@ -0,0 +1,58 @@
# Roadmap
One milestone ≈ a chunk of working sessions. Each ends with something you can
actually run and see. Timelines assume solo dev + heavy AI assistance, steady
(not full-time) pace.
## M0 — World skeleton (single-player) · ~2–4 weeks
**Goal:** walk around a lit, atmospheric terrain. Prove the look.
- [x] Project scaffold, git repo, docs
- [ ] Godot installed, project opens
- [ ] First-person / third-person character controller (move, jump, mouse-look)
- [ ] One flat/heightmap terrain chunk with a material
- [ ] Atmospheric lighting: directional sun, sky, volumetric fog, tonemapping
- [ ] Place one low-poly tree + rock to set the mood
- **Done when:** you load the game and walk around a Valheim-ish lit field.
## M1 — Survival loop (single-player) · ~3–5 weeks
**Goal:** the core game verbs.
- [ ] Resource nodes: chop tree → wood, mine rock → stone
- [ ] Inventory + simple hotbar UI
- [ ] Crafting (recipe: wood → tools / building pieces)
- [ ] Health / stamina, day-night cycle
- [ ] Snap-together building: place a wall/floor, resource cost, basic support rule
- **Done when:** gather → craft → build a tiny shelter, single-player.
## M2 — Multiplayer · ~2–4 months
**Goal:** self-hosted, authoritative, 2–8 players.
- [ ] Refactor state to server-authoritative (single-player becomes loopback server)
- [ ] Headless dedicated server mode (`--headless -- --server`)
- [ ] ENet transport, client prediction for local movement, interpolation for others
- [ ] Replicate resource nodes, inventory, building, health
- [ ] Persistence: world save/load on the server
- **Done when:** a friend joins your self-hosted server and you build together.
## M3 — Addon SDK + sandbox · ~2–3 months
**Goal:** the differentiator. Easy, safe mods.
- [ ] Embed sandboxed Lua VM in the server
- [ ] Capped API surface (events + safe world queries/commands)
- [ ] Per-addon CPU/time + memory quotas
- [ ] Addon manifest format (declare items/recipes/prefabs as data)
- [ ] 2–3 example addons (a new craftable item; a "welcome" message on join; a
custom spawn rule)
- [ ] SDK docs for modders
- **Done when:** a modder drops an addon folder in and it safely adds content,
and a deliberately-malicious test addon is contained (can't read files,
can't hang the server).
## M4 — Content & polish · ongoing
- More biomes, creatures, items, building pieces
- PvP / raiding rules, building damage
- Better UI, settings, controls
- Audio, music, weather
- Packaged builds for Win/Mac/Linux (+ Mac signing/notarisation)
## Honest overall estimate
A genuinely playable **self-hosted survival game with a working sandboxed addon
SDK** is roughly **6–12 months** of steady solo work with AI help. The M0–M1
vertical slice (a few weeks) is what proves the concept and keeps you motivated.