Home World Content Schemas

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-adminhttp://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) — optional z_platforms
  • Transitions — stairs/ramps; u/j while 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