World Content Schemas & Admin CLI
Purpose
Agents and operators need a discoverable, machine-readable contract for creating and updating world content (segments, items, buildings, placed objects). The flatland-admin CLI and schemas/ directory are the source of truth.
Discovery (agents)
At any time, run:
flatland-admin capabilities --json # commands, env vars, HTTP routes
flatland-admin schema list --json # content type ids + status
flatland-admin schema show segment # full JSON Schema
Canonical manifest: schemas/manifest.json
Content types
| Schema id | Format | Storage | CLI |
|---|---|---|---|
segment |
YAML (segment: root) |
world_segments.yaml_body |
segment validate/show (local), segment list/get/upsert/delete (API) |
item-template |
JSON | item_templates.body |
item validate (local), item list/get/upsert/delete |
building-schematic |
JSON | building_schematics.body |
building validate (local), building list/get/upsert/delete |
world-object |
JSON | world_objects row |
object list/get/create/update/delete |
Example segment: assets/world/segments/starter-plains.yaml
New characters spawn at the first spawn_points entry in the starter segment (or segment center if none).
Content architecture (target)
Today (dev spike): monolithic files — assets/crafting/blueprints.yaml, assets/items/catalog.yaml, NPCs inline in segment YAML. Worker loads once at startup; restart required after edits.
Target (Tier 3.5 in specification):
| Layer | Dev (files) | Prod (admin API) | Hot reload |
|---|---|---|---|
| Item templates | assets/items/ |
PUT /v1/admin/item-templates/{id} |
Yes — swap ContentBundle |
| Recipes / blueprints | assets/crafting/ |
TBD (DB or object store) | Yes |
| NPC definitions | assets/npcs/ + schedules/llm/ |
TBD | Yes |
| World segment | assets/world/segments/ |
PUT /v1/admin/segments/{id} |
Yes — placement + refs only |
| Server tuning | assets/config/server-settings.yaml |
TBD | Yes |
Storage model (Tier 4.1 admin UI): Dual layer — not Postgres instead of files. Authoring + git + agents use assets/ on disk; prod publishes validated YAML/JSON blobs to Postgres (002_world_content.sql) or object storage for large assets; region workers parse once into in-memory ContentBundle. Tier 4.1 UI defaults to file read/write; optional “Publish to control plane” for deploy. Do not normalize every content field into relational tables.
Segment YAML should shrink to placement: spawn points, terrain zones, resource node anchors, building positions, npc_ref / definition_id — not full NPC backstory or trade tables (those live in assets/npcs/, see 19-npcs-and-creatures.html).
Terrain presentation (glyph + color for TUI) lives in assets/world/terrain-kinds.yaml, keyed by zone kind (hill, bog, shallow_water, …). Movement costs stay in sim (TerrainKind::modifiers).
Map glyphs (resources, NPCs, doors, loot, defaults) live in assets/world/map-glyphs.yaml — keyed by item_template, NPC label (lowercase), plus defaults for fallbacks. Prefer single-column UTF-8 symbols; emoji may misalign until width-aware layout lands.
Terrain zones support one or more axis-aligned rectangles per zone. Legacy single-rect (x0/y0/x1/y1 on the zone) still loads; use rects: for L-shapes, corridors, and irregular patches:
Overlap priority — when multiple zones cover the same (x, y), the zone with the highest z_order wins (tie-break: later in the YAML list). Omit z_order on legacy segments and the loader assigns list index. In content-admin Map layout, select a terrain zone and use Bring forward / Send back to reorder the stack.
Economic overlays (tax / property / growth) use the same rect + z_order pattern but are separate families — see 37-economic-map-zones.html. Biomes, adjacency, procedural fill, and terrain transitions (including runtime deltas and weather multipliers) are specified in 38-biomes-adjacency-and-terrain-transitions.html.
terrain_zones:
- id: winding-bog
kind: bog
elevation: 0.0
z_order: 0
rects:
- { x0: 10, y0: 10, x1: 30, y1: 12 }
- { x0: 28, y0: 12, x1: 35, y1: 25 }
- id: hill-overlay
kind: hill
elevation: 2.0
z_order: 1
rects:
- { x0: 20, y0: 15, x1: 28, y1: 22 }
Map layout editor labels — every placed entity shows a two-letter tag on the canvas (e.g. OT for Oak Tree, IO for iron ore). Hover the marker or move the crosshair over it for the full title (resource name, NPC label, spawn name, etc.). Edit via World → Map layout in content-admin (make dev-content-admin → http://127.0.0.1:7386).
Z-bands (Tier 4.5) — multi-floor gameplay at the same (x,y) column. Wired on connect snapshot as ZPlatformView / ZTransitionView (rects flattened per rect):
# Southeast mountain — south trail (1 m steps) to plateau; cliff on other faces
terrain_zones:
- id: mtn-trail-base
x0: 223
y0: 198
x1: 227
y1: 206
kind: hill
elevation: 0.0
- id: mtn-trail-1
elevation: 1.0
# … mtn-trail-2 … mtn-trail-4 …
- id: mtn-plateau
x0: 220
y0: 158
x1: 232
y1: 166
kind: hill
elevation: 4.0
z_walls:
- id: mtn-north-face
z0: 0.0
z1: 5.0
rects:
- { x0: 219, y0: 156, x1: 233, y1: 158 }
- Terrain zones — topographic ground elevation; gentle steps walk up, cliffs block (
terrain_step_on_move) - Platforms — walkable surface at fixed
z(bridge, rooftop) — optionalz_platforms - Transitions — stairs/ramps;
u/jwhile in rect (z_transitions) - Walls — block 3D combat line-of-sight through the volume
See starter-plains.yaml mtn-* terrain and 22 §3.10.
Validation on load: recipe DAG cycle detect, orphan template refs, resolved npc/building ids (flatland-admin content validate).
Auth
Admin HTTP routes require session or API token with scopes:
| Operation | Scope |
|---|---|
| List / get | read (session always allowed) |
| Create / update / delete | write |
flatland auth login --email you@example.com --password '…'
flatland auth token create --name admin --scope read,write
export FLATLAND_API_TOKEN=flat_live_…
export FLATLAND_API_URL=http://127.0.0.1:7380
flatland-admin also reads ~/.config/flatland/session.json when no token env var is set.
Quickstart: seed starter map
make db-up
cargo run -p flatland-control-plane-bin # migrates 001 + 002
flatland auth login --email you@example.com --password '…'
flatland-admin segment upsert --file assets/world/segments/starter-plains.yaml
flatland-admin segment list
HTTP admin API
Base: http://127.0.0.1:7380/v1/admin
| Method | Path | Body |
|---|---|---|
| GET | /segments |
— |
| GET | /segments/{id} |
— |
| PUT | /segments/{id} |
{ "yaml": "…" } |
| DELETE | /segments/{id} |
— |
| GET | /item-templates |
— |
| PUT | /item-templates/{template_id} |
{ "body": { … } } |
| GET | /building-schematics |
— |
| PUT | /building-schematics/{schematic_id} |
{ "body": { … } } |
| GET | /world-objects?segment_id= |
— |
| POST | /world-objects |
world-object JSON |
| PATCH | /world-objects/{id} |
partial update |
| DELETE | /world-objects/{id} |
— |
All responses use the standard envelope: { "ok": true, "data": … }.
Play flow (authenticated)
# terminal 1 — control plane
cargo run -p flatland-control-plane-bin
# terminal 2 — game server (validates Hello via control plane)
export FLATLAND_CONTROL_PLANE_URL=http://127.0.0.1:7380
cargo run -p flatland-server
# terminal 3 — register, login, play
flatland auth login --email you@example.com --password '…'
cargo run -p flatland3 -- --name traveler
On first connect, flatland creates character traveler at the starter segment spawn (32, 32, 0). Position checkpoints on disconnect.
Solo dev without accounts: flatland --local (uses DevLocal auth).
Item template example
{
"template_id": "oak_log",
"display_name": "Oak Log",
"category": "resource",
"stackable": true,
"props": { "weight_kg": 2.5 }
}
flatland-admin item validate --file oak_log.json
flatland-admin item upsert --file oak_log.json
World object example
{
"segment_id": "starter-plains",
"kind": "resource_node",
"label": "oak_tree_01",
"template_id": "oak_log",
"position_x": 40,
"position_y": 28,
"position_z": 0,
"props": { "respawn_secs": 300 }
}
flatland-admin object create --file oak_tree.json
flatland-admin object list --segment starter-plains