Worldshaper/docs/runtime-api-proposal.md

7.3 KiB

Worldshaper Runtime API Proposal

Goal

Provide a stable HTTP API that a game engine can call to load Worldshaper-authored data without depending directly on editor-only JSON structure.

This is different from the current editor API.

The editor API is optimized for:

  • editing
  • saving
  • validation
  • editor UI state
  • internal content storage

The runtime API should be optimized for:

  • loading the world into a game engine
  • streaming chunks as the player moves
  • fetching only the catalog data the engine actually needs
  • staying stable even if the editor changes internally

Current State

Worldshaper already exposes useful HTTP routes in server.js, including:

  • GET /api/world-default
  • GET /api/world/:worldId
  • GET /api/world/:worldId/chunks
  • GET /api/content/tiles
  • GET /api/content/sprites
  • GET /api/content/dialogues
  • GET /api/content/npc_templates
  • GET /api/images/:filename

That means the project already has the foundations for a runtime API.

Why Not Use The Editor API Directly

Some current payloads include editor concerns that a game engine should not be forced to understand.

Examples:

  • world definitions contain editorUi
  • images are stored in editor-authored form in images.json
  • some content files may not always exist locally
  • route naming and response shape are editor-first, not engine-first

Using those routes directly would tightly couple the engine to the editor's internal storage format.

Recommendation

Add a separate runtime-facing API namespace:

  • GET /api/runtime/worlds
  • GET /api/runtime/worlds/:worldId
  • GET /api/runtime/worlds/:worldId/chunks
  • GET /api/runtime/catalog/tiles
  • GET /api/runtime/catalog/sprites
  • GET /api/runtime/catalog/npc-templates
  • GET /api/runtime/catalog/dialogues
  • GET /api/runtime/bundle/:worldId

This lets Worldshaper keep evolving internally while the engine targets a cleaner contract.

V1 should be small and practical.

Startup Flow

The engine should be able to:

  1. fetch the default or selected world
  2. fetch shared runtime catalogs once
  3. fetch chunks on demand as the player moves

Best First Endpoints

GET /api/runtime/worlds

Returns a lightweight world list.

Example:

{
  "worlds": [
    {
      "id": "overworld",
      "name": "Overworld Mock"
    }
  ]
}

GET /api/runtime/worlds/:worldId

Returns runtime-safe world metadata only.

Example:

{
  "world": {
    "id": "overworld",
    "name": "Overworld Mock",
    "chunkWidth": 32,
    "chunkHeight": 32,
    "tileSize": 32,
    "backgroundColor": "#060A14",
    "defaultBackgroundTileId": "tile_5b6206b849",
    "spawn": { "x": 80, "y": 80 }
  }
}

Do not include:

  • editorUi
  • editor layout metadata
  • editor-only presentation settings unless the engine truly uses them

GET /api/runtime/worlds/:worldId/chunks?chunkX=0&chunkY=0&radius=1

Returns chunk payloads around a center point.

Example:

{
  "worldId": "overworld",
  "center": { "chunkX": 0, "chunkY": 0 },
  "radius": 1,
  "chunks": [
    {
      "chunkX": 0,
      "chunkY": 0,
      "width": 32,
      "height": 32,
      "backgroundTileId": "tile_5b6206b849",
      "roomLayers": [
        { "layer": 0, "rows": ["................................"] },
        { "layer": 1, "rows": ["                                "] }
      ],
      "heightLayers": [],
      "instances": []
    }
  ]
}

This is the most important runtime route.

GET /api/runtime/catalog/tiles

Returns tile definitions needed for world rendering.

Example:

{
  "tiles": [
    {
      "id": "tile_5b6206b849",
      "symbol": ".",
      "name": "Grass",
      "width": 16,
      "height": 16,
      "pixelScale": 2,
      "rows": ["................"]
    }
  ]
}

GET /api/runtime/catalog/sprites

Returns sprite definitions needed for entity rendering.

Example:

{
  "sprites": [
    {
      "id": "npc_human_style_01",
      "name": "Human Style 01 - Wide Hat Coat",
      "width": 16,
      "height": 16,
      "pixelScale": 2,
      "rows": ["................"]
    }
  ]
}

GET /api/runtime/catalog/npc-templates

Returns entity templates the engine can use to resolve placed instances.

Example:

{
  "npcTemplates": [
    {
      "id": "npc_gatekeeper_bubbles",
      "name": "Bubbles",
      "faction": "dangerous_gatekeeper",
      "spriteId": "npc_human_style_13",
      "defaultDialogueId": "dlg_npc_gatekeeper_bubbles"
    }
  ]
}

GET /api/runtime/catalog/dialogues

Returns runtime dialogue definitions.

This may be enough for V1 if the engine can interpret the current dialogue node structure.

If not, Worldshaper should add a runtime dialogue normalization step later.

GET /api/runtime/bundle/:worldId

Optional convenience endpoint.

Returns:

  • runtime world metadata
  • tiles
  • sprites
  • npc templates
  • factions
  • dialogues

This is useful for prototypes and early engine work.

It may be too large for long-term world streaming, but it is a very good first integration endpoint.

Suggested Runtime Data Rules

The runtime API should follow these rules:

  • never expose editor layout/state unless the engine truly needs it
  • prefer explicit IDs over editor-specific structures
  • keep references stable: tileId, spriteId, dialogueId, templateId
  • return only what the engine needs to simulate and render
  • keep route names descriptive and runtime-oriented

What The Engine Likely Needs

Minimum likely needs:

  • world metadata
  • chunk geometry and layer rows
  • tile catalog
  • sprite catalog
  • instance template catalog
  • placed instances per chunk
  • dialogue definitions
  • factions or other lookup catalogs used by templates

Maybe later:

  • items
  • abilities
  • monsters
  • loot tables
  • triggers
  • transitions
  • quest hooks

Open Questions For The Engine Developer

These should be answered before implementation:

  1. Does the engine want raw JSON data from Worldshaper, or a normalized runtime contract?
  2. Should the engine load the full world at startup, or stream chunks around the player?
  3. Does the engine want graphics as row-based pixel data, or should Worldshaper also expose rendered asset URLs or atlas data?
  4. Will the engine consume dialogues in the current node format, or does it need a simpler compiled dialogue format?
  5. Does the engine need polling or version info so it can hot-reload changed data?

Unless the engine developer strongly objects, the best answer is:

  • normalized runtime contract
  • chunk streaming
  • row-based tile/sprite data first
  • existing dialogue format first
  • optional bundle endpoint for fast early integration

Practical Next Step

Before implementation, send your friend this exact question:

I can expose Worldshaper as a runtime API. Do you want a clean engine-facing contract with world metadata, chunk streaming, tiles, sprites, NPC templates, and dialogues, or do you want direct access to the raw editor JSON?

If he says "clean engine-facing contract", V1 should target:

  • GET /api/runtime/worlds/:worldId
  • GET /api/runtime/worlds/:worldId/chunks
  • GET /api/runtime/catalog/tiles
  • GET /api/runtime/catalog/sprites
  • GET /api/runtime/catalog/npc-templates
  • GET /api/runtime/catalog/dialogues
  • optional GET /api/runtime/bundle/:worldId