Locations

locations

Locations are the named places on the map - cities, ruins, dungeons, waypoints - the specific points of interest players visit inside a region.

In the editor

"Locations that exist in the world"

Editor location
Files → World → Locations
Editor type
Edit in Studio + View JSON
Size limits
  • locations.*.basicInfo — 4,000
  • locations.*.hiddenInfo — 4,000
  • locations.*.areas.*.description — 4,000
  • locations (entire section) — 2,000,000

Schema

json
{
  "locations": {
    "<key>": {
      "name": "string",
      "basicInfo": "string",
      "x": "number",
      "y": "number",
      "region": "string",
      "radius": "number",
      "complexityType": "simple | complex | wilderness",
      "detailType": "basic | detailed",
      "hidden": "boolean",
      "areas": {
        "<key>": {
          "description": "string",
          "paths": [
            "string"
          ],
          "name": "string",
          "images": {
            "establishingShot": {}
          },
          "visualTags": [
            "string"
          ]
        }
      },
      "factions": [
        "string"
      ],
      "visualTags": [
        "string"
      ],
      "hiddenInfo": "string",
      "initialKnown": "boolean",
      "npcLevelRange": {
        "min": "number",
        "max": "number"
      },
      "images": {
        "establishingShot": {
          "crop": {
            "focus": "...",
            "zoom": "..."
          },
          "imageUrl": "string"
        }
      }
    }
  }
}

Example

json
{
  "locations": {
    "The Capital": {
      "name": "The Capital",
      "basicInfo": "The largest city in the realm. A walled city of towers, markets, and bureaucracy. The ruling council's seat dominates the skyline. Gatekeepers at every entrance, petitioners in every corridor, and information that costs money to obtain.",
      "x": -27,
      "y": 5,
      "radius": 6,
      "region": "The Heartland",
      "complexityType": "complex",
      "detailType": "detailed",
      "hiddenInfo": "A criminal syndicate has an active cell operating in the lower district. The mage guild's restricted archive holds three documents officially declared destroyed.",
      "areas": {
        "Council Hall": {
          "description": "The formal chamber of the ruling council. Stone galleries and tiered seating for petitioners, every surface worn smooth from centuries of use. The air carries the smell of old paper and lamp oil. Guards at every door watch the visitors more than the council members.",
          "paths": ["Market Quarter", "Academy Gate"]
        },
        "Market Quarter": {
          "description": "The city's commercial heart — a permanent noise of competing vendors, loaded carts, and shouted prices. Dense enough that a conversation can happen three feet from a city watchman without being overheard. Merchants here know where information is bought and sold as well as goods.",
          "paths": ["Council Hall", "Lower District", "Academy Gate"]
        }
      },
      "factions": ["The Merchant Council", "The Mage Guild", "The City Watch"],
      "initialKnown": true,
      "hidden": false
    }
  }
}

Simple location (no areas):

json
{
  "Border Watchtower": {
    "name": "Border Watchtower",
    "basicInfo": "One of the frontier's forward observation posts. Manned by a rotating garrison detachment. Sparse, functional, and chronically under-supplied.",
    "x": 12,
    "y": -8,
    "radius": 2,
    "region": "The Frontier",
    "complexityType": "simple",
    "detailType": "basic",
    "areas": {},
    "factions": ["The Border Watch"],
    "hiddenInfo": "The garrison rotation is on a fortnight cycle and the next swap is two days late. The current crew know it; the Border Watch command does not.",
    "initialKnown": true
  }
}

Each location must be a plain object with all fields merged (not an array or split objects).

Note: hiddenInfo is available to the AI during play but players never see it directly. The AI uses it to stay consistent with the world's secrets - dropping hints in atmosphere and description rather than stating secrets outright. This pattern applies consistently across locations, NPCs, and factions: basicInfo is the surface, hiddenInfo is what drives the AI's behavior beneath it. On regions hiddenInfo is unused — put region secrets in a location or NPC instead.

Fields

name

Display name of the location, shown to players. Required. Required, and the outer key must match it exactly: Voyage rejects a world whose key and name differ.

complexityType

Required, and one of "simple", "complex" or "wilderness": Voyage rejects a world whose location omits it. The correct field name is complexityType, not complexity. Using the shortened form complexity is not a valid schema field and is ignored by the codec. "linear" is NOT a valid value. Use "complex" for any location with multiple areas.

detailType

Required, and one of "basic" or "detailed": Voyage rejects a world whose location omits it. A "basic" location of complexityType: "complex" is fleshed out by generateLocationDetails, which writes its areas and hiddenInfo; a "detailed" one is used as authored.

areas

areas use description not basicInfo, and every area needs both description and paths. A strong area description usually runs longer than a single sentence and covers three things: (1) physical character of the space, (2) what function or activity happens here, (3) one atmospheric or sensory detail that makes the area distinct. The third element — a smell, a sound, a social rule, a visual anomaly — is what makes areas feel inhabited rather than labelled.

Use display names for area keys, not snake_case. The area key is what the game displays to the player: "Council Hall", not "council_hall". An area can also carry an optional name; paths, an NPC's currentArea and a story start's locationAreas all resolve by the area key. The key is also referenced directly in paths, npcs.currentArea, and storyStarts.locationAreas — a rename requires updating all three, so get the names right from the start.

Warning: Location area objects use "description", not "basicInfo"; an area without description fails validation.

An area's own artwork takes the same images.establishingShot object, with the same crop framing, as the location's, and is generated during play the same way when it has no image.

factions

Optional Array<string> of faction keys present at this location; a key that names no faction raises a warning. Tells the narrator which factions have a presence here, influencing NPC affiliations and political framing in the scene.

npcLevelRange

Optional { min, max } integer band constraining the levels of AI-generated NPCs in this specific location. Takes priority over the parent region's npcLevelRange for NPCs generated here. Use it for outliers -- a high-tier dungeon inside a low-tier region, or a starter-friendly hub inside a high-tier region. NPCs with an explicit authored level ignore the band; only level-less generated NPCs are rolled near party level and then clamped into it. Omit to use the region's band, if it has one. Both values must be positive whole numbers; Voyage rejects anything else.

images.establishingShot.imageUrl

Optional string. URL of a banner or map image for this location, shown in the location view. A location image is shown two ways in play: a tall full-screen card on arrival, and a wide banner under Map → Location. Both are crops of the same file, so no single size avoids cropping — keep the subject centred and clear of the edges. Minimum 600×600. In the editor you can upload your own image or generate one from a prompt; both set this URL. Without one, the engine generates an image during play and saves it back, and an existing image is never regenerated. Whenever images.establishingShot is present, its crop is required.

images.establishingShot.crop.focus.x

Positions the visible window over the source image along the horizontal axis, as a percentage: 0 holds the left edge, 50 centres it, 100 holds the right edge. Defaults to 50. Out-of-range values are clamped. Saved by the crop control rather than typed by hand.

images.establishingShot.crop.focus.y

Positions the visible window over the source image along the vertical axis, as a percentage: 0 holds the top edge, 50 centres it, 100 holds the bottom edge. Defaults to 50. Out-of-range values are clamped. Saved by the crop control rather than typed by hand.

images.establishingShot.crop.zoom

Scales the image inside its frame, as a percentage of the unzoomed fit: 100 fills the frame with no magnification, 150 shows it at one and a half times, cropping to the area picked by the focus fields. Ranges from 100 to 300, default 100. Out-of-range values are clamped. Saved by the crop control rather than typed by hand.

basicInfo

The public-facing description of a location. It should answer: what is this place, who is here, and what is the dominant feeling of being in it? Keep it to 2–3 sentences.

hiddenInfo

The truth beneath the surface. Strong location hiddenInfo covers: a hidden faction presence or active operation, a historical or structural secret, specific character secrets attached to this place, and — most importantly — something actively happening right now that the player can discover. Static backstory is less useful than a live situation. Complex locations benefit from the fuller version; simple ones can be brief.

x

Horizontal coordinate (a finite number) within the parent region's cell, ranging from -regionSize/2 to +regionSize/2 (regionSize lives in Location Settings, default 100 so -50 to +50). The unit is local kilometres, and the two coordinate systems compose: a location's global position is region.x * regionSize + x, and the same for y. The Builder's Map view sets it by dragging.

y

Vertical coordinate within the parent region's cell, same range as x; positive y is north. The Builder's Map view sets it by dragging.

radius

The location's size on the map, as a positive finite number in local kilometres. Worlds commonly author 1, 2, or 3, which is a convention rather than a limit — any positive value is accepted, and the map marker is drawn from the value itself rather than from a fixed set of size classes.

The radius is also the travel catchment. A coordinate belongs to a location when it lies within that radius of the centre, and where two locations both contain a point, the one with the smaller radius takes it. Keep circles from overlapping so arrivals resolve where you expect, and give a location that should win a contested area the tighter radius rather than the wider one.

region

Key of the parent region in regions; the location renders inside that region's map cell. Voyage rejects a world whose location omits it. An empty or unknown key is accepted but raises a warning.

visualTags

Visual metadata tags for the location.

initialKnown

Whether players know the location from the start. Set it to true for a location they should see from the start; without it, the location starts unknown and counts as known once the party has visited it.

hidden

true hides the location. The location-hidden trigger effect sets or toggles it during play, so a trigger can reveal a hidden location or hide one as the story moves on. Optional.

paths

Required key in every area object. Every area must include "paths", even as []. Paths name sibling area keys within the same location; a path to an area that doesn't exist there raises a warning, and moving between locations is travel, not a path. Remove a deleted area from every other area's paths. Keep paths symmetric (if area A lists area B, area B lists area A back); Voyage adds a missing reverse path. An area with no path to it can't be reached by moving within the location.

Movement and engine behavior

Movement types

Four distinct movement modes the engine recognizes:

TypeScopeBehavior
MOVEWithin a locationMoves between areas using paths. Respects the path graph.
TRAVELBetween locationsMoves between locations via map coordinates. Distance and travel time apply.
TELEPORTAnywhereBypasses all distance, path, and travel constraints. Instant.
FAST TRAVELAnywhereLike teleport with explicit targeting — bypasses constraints but player must name a destination.

MOVE is the default within a complex location. TRAVEL is the default between locations. TELEPORT and FAST TRAVEL are instant. Arriving at a location in another region or realm updates the party's region and realm too (see Realm travel).

Travel difficulty

Travel is simple unless the party is restrained, in combat, or the destination is not yet established; those cases call for a skill check. Naming a destination that isn't a defined location doesn't fail: the party travels toward it as an untracked destination. Travel is impossible only when the destination is explicitly locked by its text (a boss guarding it, sealed access, a required access item, authority clearance), so a location or area that should be gated must state the lock in its basicInfo, hiddenInfo, or area description.

generateLocationDetails

generateLocationDetails runs only for locations with complexityType: "complex" and detailType: "basic". It reads basicInfo and any existing areas, and generates areas (with paths) and hiddenInfo.

Wilderness fallback

When the party ends a scene in untracked space and no permanent settlement is established, the engine falls back to a synthetic location named Wilderness with area Wilderness. Parsing is case-insensitive on the AI side, so wilderness, Wilderness, and WILDERNESS all canonicalize to the same Wilderness location.

Authoring tips

Simple vs complex

  • complexityType: "simple" — a single point of interest with no internal navigation. Good for taverns, shrines, waypoints.
  • complexityType: "complex" — multiple areas connected by paths. Use for dungeons, cities, estates, major landmarks.

Complex location design

  • Give a dungeon or major landmark enough areas to explore.
  • Make sure paths are bidirectional where logical (if you can walk from A to B, usually you can walk back).
  • Put different encounters, NPCs, or items in each area — don't duplicate.
  • Use hiddenInfo at the location level for secrets that only reveal after exploration or specific quest progress.

Areas inside a complex location

areas within a complex location define the navigable sub-zones. A city needs at minimum a public space and a private one — the distinction creates social texture. paths should be symmetric: if Market Quarter lists Council Hall as a path, then Council Hall should list Market Quarter back.

Settlements need a social area

Settlements (locations with NPCs, quest hooks, or hospitality) should include at least one social gathering area — a tavern, market, plaza, parlor, or equivalent — where the player can seek information and quests.

Endgame locations

  • Start with initialKnown: false — they're discovered, not given.
  • Have several areas forming a linear or branching progression.
  • Have their own hiddenInfo that recontextualises something the player thought they knew.
  • Be tied to the quests that lead there and resolve there.

Region coverage

A region with a single location has no internal exploration; a settlement or hub, a danger site, and a ruin or secret site make a good core.

Map and radius

radius is measured from the centre outward, so a radius of 3 spans 6 km across. Overlapping circles are resolved rather than rejected — a point inside two locations goes to the one with the smaller radius — but keeping them apart is still the way to be sure arrivals land where you intended.

There is no street view in the game — players cannot navigate inside a location visually. Use areas within a complex location to represent navigable sub-zones. Multiple separate location circles in a region can make a dense area (like a city) feel more navigable on the map.

City design choice: A city can be one large complex location (single circle, many areas) or multiple smaller complex locations filling a region. Multiple circles are more map-readable; a single circle with many areas gives deeper internal navigation.