File Formats
Field-by-field reference for every file the editor writes into a project, from project.vastopia and game_config.json to map binaries and data JSON.
21 min read
How a project is laid out#
A Vastopia game is a folder of plain files: JSON, INI, Godot .tscn text scenes, C# scripts and a handful of small binary blobs. There is no database. The editor reads and writes these files directly, and the MCP tools edit the same files, so anything you see here can also be inspected with a text editor or kept in version control.
The project splits into two halves that ship separately:
| Half | Folder | Packed into | Holds |
|---|---|---|---|
| Client | assets/ | the game PCK players download | Models, icons, animations, interface scenes, terrain, names and descriptions |
| Server | server/ | server_data.zip uploaded with each build | Stats, drops, scripts, spawn coordinates, collision, prices |
Two folders are build products rather than source: packs/ holds intermediate PCK output, and .export_cache/ (dot-prefixed, never packed) caches converted models so the editor and the publish step do not re-convert them. See Project structure for the full tree.
Conventions that apply everywhere#
- IDs. Items, NPCs, objects, VFX and interfaces use sequential integers allocated by the editor. Skills use numeric
skill_ids. Shops, prayers and tasks use string ids. Assets use 26-character ULIDs. Never invent an id: the Data Editor, the map tools and the MCP all allocate the next free one. - Floats for ints. Godot serialises many integers as floats in client JSON (
"item_id": 1.0,"row": 0.0). Every loader tolerates this. Server JSON tends to use clean integers. - Paths. Client model and icon references are
res://assets/...orassets/...project-relative paths. Server script references are project-relative, such asserver/scripts/objects/Obstacle.cs. A machine-absolute path in client data breaks the game at runtime, so keep everything relative. - Versions. Files that carry a
"version"key keep it; copy the value from an existing file of the same kind. - Coordinates.
TILE_SIZEis 0.5 world units. Spawn files store world units; tile-based files store integer tile coordinates. See Coordinate system.
Project root files#
project.vastopia#
An INI file in Godot ConfigFile style: quoted string values, a blank line after each section header. Written by the project wizard and updated by the editor as you work.
[project]
name="Default"
version="1.0.0"
created="2026-08-16T18:45:08"
modified="2026-08-16T18:45:08"
description=""
data_format=1
[paths]
maps_dir="assets/maps/"
ui_dir="assets/UI/"
data_dir="assets/data/"
interface_dir="assets/interface/screens/"
[game]
starting_map=""
title="Default"
[platform]
game_id=""
[server]
project_path=""
[client]
project_path=""
| Section | Key | Type | Default | Meaning |
|---|---|---|---|---|
[project] | name | string | the name you gave the project | Display name used by the wizard and the editor title |
[project] | version | string | "1.0.0" | Project version string |
[project] | created, modified | ISO-8601 string | now | Timestamps; modified is bumped on save |
[project] | description | string | "" | Free text |
[project] | data_format | int | 1 | On-disk format version. The MCP server compares it against the range it was built for and refuses to modify a project from a newer editor. |
[project] | template_id, template_version | string | "default", "" | Which starter template the project was created from; used to copy a system's files back when you re-enable it |
[paths] | maps_dir, ui_dir, data_dir, interface_dir | string | as above | Relative to the project root, trailing / |
[game] | starting_map | string | "" | Name of the map players spawn into; mirrored in game_config.json |
[game] | start_x, start_z | int | 0 | Spawn tile; mirrored in game_config.json |
[game] | title | string | project name | Game title |
[platform] | game_id | string | "" | UUID of the game on the platform, set by the Project Wizard. Required to publish; the publish dialog has no game selector and reads this value. |
[platform] | deploy_host, deploy_port | string, int | "", 7777 | Optional self-hosted deploy target |
[server] | project_path | string | "" | Local path to a Vastopia-Server checkout for Dev Test. Machine-specific; not shipped. |
[client] | project_path | string | "" | Local path to a Vastopia-Client checkout. Machine-specific; not shipped. |
[systems] | enabled | array of strings | all systems | Gameplay systems switched on for this project. Projects without the key read as "everything on". |
[branding] | icon_path, icon_cdn_url | string | "" | Game icon chosen in the wizard |
[export] | pack_only_used_models | bool | true | Skip packing models nothing references |
[world] | max_world_size | int | 0 | Maximum map footprint per side in world units; 0 = unlimited. The Map Editor warns on save when a map exceeds it. |
[map_defaults] | terrain_type, lighting_theme | int | 0 | Indices preselected in the Map Editor's create-map view |
game_config.json#
The metadata file the launcher and client read. Uploaded as-is with every build.
{
"description": "",
"developer": "",
"genre": "Adventure",
"starting_map": "",
"tags": [],
"title": "Default",
"version": "1.0.0",
"website": ""
}
| Key | Type | Written by |
|---|---|---|
title | string | Project wizard (set to the project name) |
version | string | Wizard; defaults to "1.0.0" |
description, developer, website | string | Wizard identity step; always present, "" when unset |
genre | string | Wizard identity step |
tags | array of strings | Wizard identity step |
visibility | string | Wizard identity step, when set |
starting_map | string | Game Settings; a map directory name under assets/maps/ |
start_x, start_z | int | Game Settings; the player spawn tile |
The Game Config editor's branding, cover, screenshot and loading-screen settings are stored in a separate INI, game_config.cfg, at the project root ([basic], [branding], [gameplay], [platform] sections). That file is editor state for the platform listing, not something the client reads.
Other root files#
| File | Purpose |
|---|---|
prop_library.json | The Map Editor's model Library: props[] with id, name, path, folder, is_permanent, added_date (and source_path for models added from a linked folder), plus folders[] |
interface_library.json | GUI editor folder assignments for interface scenes (assignments, folders) |
recent_interfaces.cfg | Recently opened interfaces; editor convenience only |
Client data: assets/data/#
items.json (client)#
A JSON array, one object per item. Visual and inventory fields only.
| Field | Type | Default | Notes |
|---|---|---|---|
item_id | int | allocated | Join key with the server copy |
name, description | string | "" | Shown in the inventory |
is_equippable | bool | false | |
slot | string | "" | One of right_hand, left_hand, head, body, hands, legs, boots, amulet, ring; empty when not equippable |
equip_mode | string | "" | How the item attaches: attach_right_hand, attach_left_hand, replace_head, replace_head_jaw, overlay_head, overlay_head_jaw, attach_head, replace_body, replace_body_arms, overlay_body, overlay_body_arms, replace_hands, replace_legs, overlay_legs, and so on per slot. replace_ hides the body part, overlay_ draws over it, attach_ hangs the model off one bone. |
attach_bone | string | "" | Bone name for attach_ modes |
model_asset_id, model_path | string | "" | Asset ULID and path of the equipped model |
icon_mesh_asset_id, icon_mesh_path | string | "" | Model the inventory icon was rendered from |
icon_path, icon_path_48, icon_path_32 | string | "" | assets/icons/64/N.png and the 48 and 32 px variants |
editor_properties | object | {} | editor_position, editor_rotation, editor_scale (composed equip transform as "(x, y, z)" strings), optional equip_base_*, and the icon camera (icon_orbit_yaw, icon_orbit_pitch, icon_orbit_distance, icon_orbit_target, icon_model_rotation) |
interact_options | array of strings | [] | Right-click labels only, for example ["Equip", "Use", "Drop"] |
weapon_animation_overrides | object | {} | Animation name to animation id |
equipment_stats | object | {} | Same block as the server copy, so the equipment tab can sum bonuses |
items.json (server)#
| Field | Type | Default | Notes |
|---|---|---|---|
item_id, name, is_equippable, slot, equip_mode, attach_bone | Same as client | ||
stackable | bool | false for new items | Units merge into one slot. Rows that predate the key are treated as stackable. |
tradeable | bool | true | Can be offered in a trade |
droppable | bool | true | Can be dropped on the ground |
interact_options | array of objects | [] | {id, label, script_path}; id is the slugified label. The drop option may carry drop_mode: "drop" spawns a floor item, "destroy" removes it outright. |
script_path | string | "" | Item script |
equipment_script_path | string | "" | Script run when equipped |
properties | object | {} | Custom properties declared in property_manifests.json (for example value, weight) |
requirements | array | [] | {skill, level} pairs |
equipment_stats | object | See below | |
weapon_animation_overrides | object | {} | Animation name to animation id (integers) |
equipment_stats keys: accuracy_melee, accuracy_ranged, accuracy_magic, strength_melee, strength_ranged, strength_magic, defence_melee, defence_ranged, defence_magic (all default 0), attack_speed (default 6), attack_distance (default 1), combat_type (0 melee, 1 ranged, 2 magic), attack_anim_id, hit_gfx_id, projectile_id (default 0). See Items.
npcs.json (client)#
| Field | Type | Default | Notes |
|---|---|---|---|
npc_id, name, description | |||
tiles | int | 1 | Footprint size |
model_name, model_asset_id, model_path, extracted_mesh_path | string | "" | Project-relative model paths |
icon_path | string | "" | |
options | array of strings | [] | Right-click labels only, for example ["Talk-to"] |
animation_base | string | "" | Animation base id from animations.json |
idle_animation_id, walk_animation_id, run_animation_id | int | 0 | |
idle_animation_speed, walk_animation_speed, run_animation_speed | float | 1.0 | |
position_x/y/z, rotation_x/y/z | float | 0.0 | Model offset and rotation |
scale, scale_x/y/z, scalex/scaley/scalez | float | 1.0 | Uniform and per-axis scale (the three spellings are kept in step for older clients) |
clickbox | object | {} | shape_type (box, capsule or mesh), size{x,y,z}, offset{x,y,z}, capsule_radius, capsule_height |
combat_level | int | 1 | Copied from the server combat block for display |
npcs.json (server)#
| Field | Type | Default | Notes |
|---|---|---|---|
npc_id, name, tiles | |||
options | array of objects | [] | {id, label} plus dialogue_id when an option opens a dialogue |
script_path | string | "" | NPC script |
animations | object | {} | Animation name to animation id |
properties | object | {} | Declared custom properties, such as attackable |
combat | object | See below |
combat fields and the server's defaults: attackable (false), aggressive (false), aggro_radius (5.0), aggro_leash (12.0), combat_level (1), hitpoints (100), max_hit (0), attack_speed (0), attack_distance (0.0), accuracy_melee/ranged/magic (75/70/65), strength_melee/ranged/magic (1), defence_melee/ranged/magic (1), respawn_ticks (0), combat_script_path (""). The editor also writes combat_type, max_hit_melee, max_hit_ranged and max_hit_magic for its own UI.
objects.json (client)#
| Field | Type | Default | Notes |
|---|---|---|---|
object_id, name, description | |||
model_asset_id, model_path | string | "" | |
tile_width, tile_depth | int | 1 | Footprint in tiles |
interact_actions | array of strings | [] | Right-click labels only |
offset_x/y/z, rotation_x/y/z | float | 0.0 | Model transform |
scale, scale_x/y/z | float | 1.0 | |
clickbox | object | {} | Same shape as NPCs |
idle_animation | string | "" | Clip name the client loops on the model |
render_on_minimap | bool | true |
objects.json (server)#
| Field | Type | Default | Notes |
|---|---|---|---|
object_id, name | |||
collision_type | string | "obstacle" | obstacle (blocks movement and line of sight), wall (same), ground_deco (fully passable), door, low_fence (blocks movement, projectiles pass over), transparent (blocks movement, line of sight passes through). The Data Editor writes obstacle, ground_deco or low_fence from its two clip checkboxes. |
tile_width, tile_depth | int | 1 | Used for movement blocking and interaction range on every map that places the object |
interact_distance | int | 2 | Tiles a player must be within |
interact_actions | array of objects | [] | {id, label} |
script_path | string | "" | Object script |
properties | object | {} | Declared custom properties, such as interactable |
animations.json#
Client only. An object with version and bases[]. Each base is a shared skeleton:
{ "version": 1, "bases": [ {
"id": "humanoid", "display_name": "humanoid",
"skeleton_path": "assets/skeletons/humanoid_skeleton.tscn",
"animations": [
{ "id": 1.0, "name": "idle", "length_sec": 2.0, "in_place": false,
"resource_path": "assets/anims/humanoid/anim_1.tres", "retarget_hint": "", "tags": [] }
]
} ] }
Animation ids are what idle_animation_id, NPC animations and weapon_animation_overrides reference. See Animations.
vfx_database.json#
Client only: { "vfx": [ { "vfx_id": 1, "name": "fire", "scene_path": "res://assets/vfx/fire.tscn", "scalex": 1.0, "scaley": 1.0, "scalez": 1.0 } ] }. The effect itself is the self-contained scene at assets/vfx/<name>.tscn. See VFX.
tab_config.json#
The client's tab bar. Two rows of up to six tabs each, twelve tabs maximum.
{
"version": 1,
"layout": {
"rows": [
{ "tab_width": 48, "spacing": 2 },
{ "tab_width": 48, "spacing": 2 }
]
},
"tabs": [
{ "index": 0, "name": "Combat", "icon": "1.png", "interface_name": "combat",
"interface_id": 13, "enabled": true, "row": 0 }
]
}
| Field | Type | Notes |
|---|---|---|
layout.rows[n].tab_width | int | Default 48; allowed 16–150, and capped per row so the row never exceeds the 300 px bar |
layout.rows[n].spacing | int | Default 2; allowed 0–32 |
tabs[].index | int | Position; the editor re-indexes on save |
tabs[].name | string | Tooltip / display name |
tabs[].icon | string | File name in assets/sprites/ (the numbered 1.png–12.png ship with every project) |
tabs[].interface_name | string | The tab's interface scene name. inventory, equipment and skills are drawn by the client itself and are protected from removal. |
tabs[].interface_id | int | Injected from interface_database.json on save |
tabs[].enabled | bool | |
tabs[].row | int | 0 top row, 1 bottom row |
A legacy flat layout: {tab_width, spacing} still loads and applies to both rows. See Tabs.
map_area_config.json#
The HUD stat orbs and the up to two map buttons next to the minimap.
{
"version": 1,
"progress_bars": [
{ "name": "HealthBar", "stat_key": "health", "icon": "1.png",
"fill_color": [1.0, 0.0, 0.0, 1.0], "default_value": 50.0 }
],
"bottom_buttons": []
}
| Field | Type | Notes |
|---|---|---|
progress_bars[] | array, max 4 | One orb per entry; stat_key is one of health, stamina, mana, energy |
progress_bars[].name | string | Defaults HealthBar, StaminaBar, ManaBar, EnergyBar |
progress_bars[].fill_color | [r, g, b, a] floats | |
progress_bars[].default_value | float | Health 50, stamina 20, mana 80, energy 60 by default |
bottom_buttons[] | array, max 2 | {name, action_key, icon}; the client draws two and ignores more |
Position and size are computed by the client; only the fields above are read. See Game screen and HUD.
skills_config.json#
Present in both assets/data/ and server/data/ with slightly different rows.
| Field | Client | Server | Notes |
|---|---|---|---|
version | yes | yes | 1 |
skills[].skill_id | yes | yes | Numeric id |
skills[].name | yes | yes | |
skills[].category | yes | yes | For example combat |
skills[].max_level | yes | yes | 99 in the template, 120 for Defence |
skills[].icon | yes | no | Sprite file name |
skills[].ordinal | yes | no | Display order in the skills tab |
skills[].script_path | no | yes | Optional skill script |
The client builds its skills panel from this file, so the skills you define really appear in game. See Skills.
interface_database.json#
Both sides, ids must match: { "interfaces": [ { "id": 1, "name": "Shop", "path": "res://assets/interfaces/screens/Shop.tscn", "type": "gamescreen" } ] } with type one of gamescreen, tab, chatbox. The editor regenerates it by scanning assets/interface/screens/ on save and export; the server opens interfaces by these integer ids. See Custom interfaces.
locations.json, wisdom_config.json#
locations.json is the same on both sides: { "locations": [ { "name": "Wilderness", "script_path": "" } ] }. wisdom_config.json (prayers/buffs) is on both sides but with a different row on each. Server: { "version": 1, "drain_interval_ticks": 4, "prayers": [ { "prayer_id", "name", "category", "level_required", "drain_rate", "effect_type", "effect_value", "overhead_icon", "conflicts_with": [] } ] }. Client: { "version": 1, "categories": ["overheads", "combat", "utility", "misc"], "prayers": [ { "prayer_id", "name", "category", "level_required", "icon_asset_id", "ordinal" } ] } — display fields only, no drain or effect data. prayer_id is a string and joins the two.
Server-only data: server/data/#
systems.json#
Written whenever you create a project or change systems in Game Config. The server honours it at module-load time, which matters because some template modules also exist as pre-compiled platform copies that deleting a file cannot remove.
{
"version": 1,
"enabled_systems": ["dialogue", "quests", "tasks", "skills_xp", "wisdom", "crafting", "shops",
"banking", "trading", "social", "combat", "aggro", "specials", "buffs", "bosses", "drops"],
"disabled_systems": [],
"disabled_modules": []
}
enabled_systems mirrors [systems] enabled in project.vastopia; disabled_modules lists the module ids the server must refuse to load. See Templates and gameplay systems.
property_manifests.json#
Declares the custom typed properties available under each entity's properties block. type is bool, int, float or string.
{ "items": [ {"name": "value", "type": "int", "default": 0}, {"name": "weight", "type": "float", "default": 0} ],
"npcs": [ {"name": "attackable", "type": "bool", "default": false} ],
"objects": [ {"name": "interactable", "type": "bool", "default": false},
{"name": "render_on_minimap", "type": "bool", "default": true} ] }
script_bindings.json#
Entity id to script path, rebuilt from the script_path fields when a definition that binds a script is saved: { "items": {}, "equipment": {}, "npcs": {}, "npc_combat": {}, "objects": {"3": "server/scripts/objects/Obstacle.cs"} }.
world_settings.json#
Engine defaults for respawn and death rules; every key is the engine default, so deleting the file changes nothing. Keys: respawn_region, respawn_x, respawn_y, respawn_z, drop_items_on_death, drop_equipment_on_death. Keys starting with _ are comments.
Schema-backed content: server/schemas/#
Shops, recipes, tasks, loot tables, dialogue, bosses, buffs and combat formulas are not in server/data/. Each lives as a pair under server/schemas/<Category>/<name>/: <name>.json (the schema the Script Editor uses for autocomplete and table editing) and <name>_data.json (the rows). Drop tables, for example, are server/schemas/Drops/npc_drops/npc_drops_data.json, with npc_table_drops, npc_wheel_drops and the item_*, object_* and misc_* variants alongside. The Game Tools pages document each: Loot tables, Shops, Recipes, Tasks, Dialogue.
Map files: assets/maps/<map>/#
Saving a map writes the files below into its folder and re-derives the server exports in the same pass.
| File | Format | Purpose |
|---|---|---|
<map>_region.tscn | Godot text scene | The map scene: root node, the Terrain3D node, environment |
data/ | Terrain3D binary .res files | Heightmap, control and color maps, one file per terrain region. Do not hand-edit; terrain can only be authored in the editor or with the MCP terrain tools. |
regions.json | JSON | Placed region elements: NPC spawners, interactable objects, trigger zones, location bounds |
props.bin | binary, version 2 | Every decorative prop and painted mesh instance |
props_meta.json | JSON, version 1 | Optional sidecar: per-instance surface-follow and align-to-slope records. Absent when no prop carries metadata. |
height_layers.terrain | binary HTLR, version 3 | Height layers and their per-tile offsets |
terrain_data.json | JSON | Bounds and blocked tiles in world units, for tools that want a quick read |
water.json | JSON, version 1 | Water plane settings |
dungeon.json | JSON, version 1 | Only on dungeon-style maps: carved tiles, voided tiles, wall set and corner style |
tile_flags.json | JSON, version 1 | Legacy per-tile walkability ({tiles: [{x, z, layers: {"0": true}}]}). The editor still loads one if present; walkability is exported to the server binary below. |
regions.json#
One object with version, next_id and four arrays. Every element has id, name and type.
| Array | type | Element fields |
|---|---|---|
npc_spawners | npc_spawner | position [x, y, z], npc_id, rotation_y (degrees), spawn_radius, spawn_count, respawn_time, behavior (idle, wander, patrol), wander_intensity (0 low, 1 medium, 2 high), spawn_condition, height_layer_id (-1 terrain, 0+ layer), patrol_path [[x, y, z], ...] |
interactable_objects | interactable | position_2d [x, z] (height is derived on load), height_layer_id, object_id, model_rotation_y, interaction_type, interaction_radius, script_on_interact, is_persistent, custom_properties {} |
trigger_zones | trigger_zone | position [x, y, z], polygon [[x, z], ...], trigger_type, script_on_trigger, audio_to_play, one_shot, cooldown, enabled |
location_bounds | location_bounds | position [x, y, z], tiles [[tx, tz], ...], color [r, g, b, a], audio_path, script_path, music_path, ambient_track, enter_sound, leave_sound, fog_enabled, fog_color [r, g, b], fog_density, fog_height, ambient_light_color, ambient_light_energy, sun_light_color, sun_light_energy, weather_type, difficulty_modifier |
Model paths, scale and footprint are never stored here; they come from the NPC and object definitions, so changing a definition changes every map that uses it. See Regions and elements.
props.bin#
Little-endian binary. Version 2 is the only version the client and the MCP accept, which is why editor-only metadata lives in the sidecar instead.
magic "PROP" (4 bytes)
version u8 = 2
model_count u32
per model:
flags u8 bit0 = batched (multimesh), bit1 = has scale
path_len u16
path UTF-8, project-relative, e.g. assets/models/<ULID>_tree/tree.scn
inst_count u32
per instance:
position 3 x f32 (world units)
rotation 3 x f32 (radians)
scale 3 x f32 only when the model's has-scale flag is set
The has-scale flag is per model: if any instance of a model is scaled, every instance of that model writes three scale floats. The same path may appear twice, once batched and once as individual props.
props_meta.json#
{ "version": 1, "models": { "<path>": [ {...}, null, ... ] } }. Keys are the same model paths as props.bin, with #mm appended for the batched entry; each array runs parallel to that model's instances. A record may hold follow (re-seat on terrain edits; on a batched entry it only ever appears as false, the single opt-out, carried in by batching a Fixed prop), surface (terrain or layer), layer (1-based height layer id), offset (height above the surface), align (tilt to the surface normal) and normal. An empty record means "fixed" for an individual prop and "sit exactly on the surface" for a batched copy. If the sidecar is missing or the counts no longer match, that model's metadata is ignored and its instances are treated as fixed. See Props and model painting.
height_layers.terrain#
magic "HTLR" (4 bytes)
version u8 = 3
layer_count u8
active_layer u8
tile_size f32 (0.5)
tiles_per_region u16 (64 for a standard region)
region_count u16
regions region_count x (x: i16, z: i16)
per layer:
layer_id u8
name_len u8, then name bytes (UTF-8)
color 4 bytes r, g, b, a (0-255)
default_height f32
per region: tiles_per_region^2 x f32 height offsets above the terrain (0.0 = on the ground), row by row
Only active terrain regions are stored, so the file stays small on sparse maps. See Height layers.
water.json#
{ "version": 1, "enabled": false, "level": -0.01, "water_color": [r, g, b, a], "deep_color": [r, g, b, a], "highlight_color": [r, g, b, a], "fresnel_power": 3.0, "highlight_strength": 0.2, "roughness": 0.3, "metallic": 0.0 }. The default level sits just under zero so the surface never z-fights terrain standing at exactly height 0.
terrain_data.json#
{ "bounds": { "min_x", "min_z", "max_x", "max_z" }, "blocked_tiles": [[world_x, world_z], ...], "height_levels": [] } with bounds and blocked tiles in world units. It is a convenience export; the server reads the binary flag file instead.
Server map exports: server/data/maps/#
Saving a map writes four per-map files here. They are derived, so edit the map rather than these files.
npc_spawns/<map>.json#
{ "version": 1, "map_name": "home", "spawns": [
{ "spawn_id": 275, "npc_id": 1, "x": -0.5, "z": 7.0, "rotation_y": 0.0,
"spawn_radius": 1.0, "spawn_count": 1, "respawn_time": 0, "behavior": "idle",
"wander_intensity": 1, "patrol_path": [], "spawn_condition": "" } ] }
x and z are world units (tile times 0.5); the server derives height from the terrain. patrol_path entries are {x, y, z} objects.
object_spawns/<map>.json#
{ "version": 1, "map_name": "home", "spawns": [
{ "spawn_id": 300, "object_id": 3, "x": 6.0, "z": -1.0, "height_layer_id": 0,
"rotation_y": 0.0, "scale": 1.0, "interaction_type": "use", "interaction_radius": 2.0,
"is_persistent": true, "script_on_interact": "", "custom_properties": {} } ] }
Footprint is deliberately absent: the server reads tile_width and tile_depth from the object definition.
location_bounds/<map>.json#
{ "version": 2, "map_name", "locations": [ { "location_id", "name", "tiles": [[tx, tz], ...], "audio": { "ambient_track", "music_path", "enter_sound", "leave_sound" }, "environment": { "fog_enabled", "fog_color", "fog_density", "ambient_color", "ambient_energy", "sun_color", "sun_energy" }, "weather_type", "difficulty_modifier", "script_path" } ] }.
flag_data/<map>.terrain#
The walkability file the server loads. Little-endian, sparse version 2:
magic "REGN" (4 bytes)
version u8 = 2
layer_count u8
tile_size f32 (0.5)
tiles_per_region u16
region_count u16
regions region_count x (x: i16, z: i16) active region grid cells
blocked_count u32
blocked tiles blocked_count x (tile_x: i16, tile_z: i16, layer_mask: u8)
layer_mask is a bitfield: bit N set means the tile is blocked on height layer N. Only tiles inside an active region are walkable at all, so activating regions in the Map Editor is what defines the playable area. Version 1 files (bounds-based: tile_size, min_x, min_z, max_x, max_z instead of the region list) still load. On dungeon maps every un-carved tile is written as blocked on the ground layer. See Map files and export.
Other project files#
assets/data/building_prefabs.json#
{ "version": 1, "prefabs": [ { "name", "width", "depth", "floors", "pieces": [...] } ] }. width, depth and floors describe the plot; stamping reads only name and pieces. Each piece has dx, dz (tile offset from the plot's south-west corner), rotation_y, height_layer_id (the floor) and optional off_x, off_z for free-placed pieces, then either:
- a model piece:
kind: "prop",prop_id,model_path,name,tile_width,tile_depth, stamped as a plain prop; or - an interactable object:
object_id,interaction_type: "use",interaction_radius: 2.0,script_on_interact,is_persistent: true,custom_properties: {}, stamped as a region element.
See Building prefabs.
assets/AssetLibrary.json#
The registry of every imported asset, keyed by ULID.
{ "version": 1, "assets": [
{ "id": "01KY39MGW5AJAXFWX4Q2836T4N", "name": "humanoid_skeleton", "type": 7,
"path": "skeletons/humanoid_skeleton.tscn", "original_filename": "humanoid_skeleton.tscn",
"import_date": "2026-07-21T17:34:44", "tags": [], "metadata": {},
"cloud_id": 0, "sha256": "" } ] }
| Field | Type | Notes |
|---|---|---|
id | string | 26-character ULID, time-sortable |
name | string | Display name |
type | int | 0 sprite, 1 3D model, 2 audio SFX, 3 audio music, 4 texture, 5 animation, 6 UI scene, 7 skeleton, 8 other |
path | string | Relative to assets/ |
original_filename | string | Name of the file that was imported |
import_date | ISO-8601 string | |
tags | array of strings | |
metadata | object | Type-specific; sprites carry width, height, format |
cloud_id | int | Public asset id on the cloud library, 0 when not uploaded |
sha256 | string | Hash of the uploaded source file, "" when not uploaded |
Type folders under assets/: sprites in sprites/, models in models/<ULID>_<name>/ (converted to a self-contained .scn on import), SFX in audio/sfx/, music in audio/music/, textures in textures/, animations in anims/<base>/, UI scenes in UI/, skeletons in skeletons/, anything else in misc/. Recognised import extensions: .png .jpg .jpeg .webp .svg (sprites), .glb .gltf .obj .fbx (models), .wav .ogg (SFX), .mp3 (music). See Asset manager.
Quick index#
| File | Side | Shape |
|---|---|---|
project.vastopia | root | INI |
game_config.json | root, shipped | object |
game_config.cfg | root, editor only | INI |
assets/data/items.json, npcs.json, objects.json | client | array |
server/data/items.json, npcs.json, objects.json | server | array |
assets/data/animations.json, vfx_database.json | client | object |
assets/data/tab_config.json, map_area_config.json | client | object |
skills_config.json, wisdom_config.json, locations.json, interface_database.json | both | object |
server/data/systems.json, property_manifests.json, script_bindings.json, world_settings.json | server | object |
server/schemas/<Category>/<name>/<name>_data.json | server | array |
assets/maps/<map>/regions.json, water.json, terrain_data.json, props_meta.json | client | object |
assets/maps/<map>/props.bin, height_layers.terrain | client | binary |
server/data/maps/npc_spawns/, object_spawns/, location_bounds/ | server | object per map |
server/data/maps/flag_data/<map>.terrain | server | binary |
assets/data/building_prefabs.json | editor | object |
assets/AssetLibrary.json | editor | object |
