Fio levels are stored as plain .json files. The level format is the editor's native scene representation and is consumed directly by the engine.
A Fio level contains:
version — format versionbrushes — world geometrythings — entities and gameplay objectsterrain_data — optional procedural terrain configurationlightmap_scene_hash — optional lightmap bake identifierlogic_graph — optional saved logic-graph node positionsA minimal Fio V3 level is:
{
"version": 3,
"brushes": [],
"things": []
}
Additional sections are written only when the corresponding level data exists.
version"version": 3
The current level format version is 3 (July 2026)
The loader treats a missing version as version 1 for backwards compatibility and performs legacy migration when necessary.
brushesbrushes is an array containing the level's world geometry.
The normal brush representation is an axis-aligned box:
{
"pos": [0.0, 64.0, 0.0],
"size": [256.0, 128.0, 256.0],
"textures": {
"north": "default.png",
"south": "default.png",
"east": "default.png",
"west": "default.png",
"top": "default.png",
"down": "default.png"
},
"is_trigger": false,
"id": "..."
}
| Field | Type | Description |
|---|---|---|
pos |
array | Brush centre [x, y, z] |
size |
array | Brush dimensions [x, y, z] |
textures |
object | Texture assigned to each face |
is_trigger |
boolean | Marks the brush as a trigger volume |
id |
string | Stable unique identifier |
uv_scale |
object | Optional per-face UV scaling |
geometry |
object | Optional convex geometry for angled/clipped brushes |
name |
string | Optional brush name |
lock |
boolean | Optional editor lock state |
hidden |
boolean | Optional visibility state |
shader |
string | Optional brush shader |
is_door |
boolean | Optional door behaviour |
is_mover |
boolean | Optional moving-brush behaviour |
lightmap_static |
boolean | Optional participation in lightmap baking |
Not every brush contains every optional field.
The textures object assigns a texture path to the six standard faces:
"textures": {
"north": "default.png",
"south": "default.png",
"east": "default.png",
"west": "default.png",
"top": "default.png",
"down": "default.png"
}
Texture paths are stored as project-relative asset paths.
For example:
"textures": {
"north": "Dev/512.jpg",
"south": "Dev/512.jpg",
"east": "Dev/512.jpg",
"west": "Dev/512.jpg",
"top": "Dev/512.jpg",
"down": "Dev/512.jpg"
}
uv_scaleuv_scale optionally stores independent UV scaling for individual faces.
"uv_scale": {
"north": [1.625, 0.125],
"south": [1.625, 0.125],
"east": [1.5625, 0.125],
"west": [1.5625, 0.125],
"top": [1.625, 1.5625],
"down": [1.625, 1.5625]
}
The value is [u_scale, v_scale].
A brush may contain an empty uv_scale object or omit it entirely.
V3 supports brushes whose geometry is no longer represented solely by pos and size.
An angled or clipped brush can contain a geometry object:
"geometry": {
"planes": [
{
"n": [1.0, 0.0, 0.0],
"d": 768.0,
"texture": "default.png",
"face": "east"
},
{
"n": [-1.0, 0.0, 0.0],
"d": -432.0,
"texture": "default.png",
"face": "west"
},
{
"n": [0.0, 1.0, 0.0],
"d": 208.0,
"texture": "default.png",
"face": "top"
}
]
}
Each plane contains:
| Field | Description |
|---|---|
n |
Plane normal [x, y, z] |
d |
Plane distance/offset |
texture |
Texture assigned to the plane |
face |
Optional association with a standard brush face |
The plane representation allows the editor to retain arbitrary convex brush geometry created by clipping and rotation.
The renderer and geometry systems attach temporary internal data to brushes while the application is running.
These runtime caches are deliberately removed when the level is serialized. This prevents OpenGL/GLM/NumPy objects and other non-JSON data from being written into map files.
Therefore, a saved .json file represents the persistent brush state rather than the renderer's runtime caches.
thingsthings contains all placeable entities in the level.
The common structure is:
{
"type": "light",
"pos": [128.0, 96.0, 0.0],
"properties": {},
"io_connections": []
}
| Field | Type | Description |
|---|---|---|
type |
string | Entity type used when loading the object |
pos |
array | World position [x, y, z] |
properties |
object | Entity-specific persistent properties |
io_connections |
array | Output/input connections |
Every normal Thing has a stable UUID stored in its properties:
"properties": {
"type": "light",
"name": "Light_1",
"id": "53e6fc4d-aef0-4109-98cf-7695c304ee85"
}
properties is deliberately type-dependent.
The entity class determines which properties are available.
For example, a light may contain:
"properties": {
"type": "light",
"name": "Light_1",
"colour": [255, 255, 255],
"intensity": 1.0,
"radius": 512.0,
"state": "on",
"show_radius": false,
"casts_shadows": false,
"id": "..."
}
A player start contains different properties:
"properties": {
"type": "playerstart",
"name": "PlayerStart_1",
"angle": 0.0,
"id": "..."
}
The format therefore does not impose one fixed property schema on every entity.
V3 uses persistent UUIDs to identify brushes and entities.
For Things, the ID is stored inside properties:
"id": "e107abe6-ff24-4ac6-bf74-264baeb3e5bf"
The ID survives save/load and allows systems such as I/O and Big World to refer to the same object independently of its position in the things array.
I/O connections are stored separately from the entity's ordinary properties.
Example:
"io_connections": [
{
"output": "OnTrigger",
"target": "main_door",
"input": "Open",
"parameter": "",
"delay": 0.0,
"fire_once": false,
"target_id": "..."
}
]
| Field | Description |
|---|---|
output |
Output event emitted by the source |
target |
Target entity name |
input |
Input called on the target |
parameter |
Optional parameter passed to the input |
delay |
Delay before the connection fires |
fire_once |
Whether the connection may fire only once |
target_id |
Stable UUID of the target entity |
io_connections is always emitted for Things, including when the array is empty.
Brush I/O is also serialized into io_connections when a brush has I/O connections.
Procedural terrain is optional and is stored at the root level as terrain_data.
A terrain-enabled level therefore has:
"terrain_data": {
"enabled": true,
"solid": true,
"seed": 42,
...
}
The terrain data stores the procedural terrain configuration rather than a baked mesh.
The configuration includes the terrain's biome and generation parameters, including values such as:
For example, current maps contain values such as:
"terrain_data": {
"enabled": true,
"solid": true,
"seed": 42,
"name": "Low Poly Valley",
"base_height": 10.0,
"height_scale": 250.0,
"hills_scale": 0.004,
"hills_intensity": 0.8
}
Terrain is independent of the brush list; a level can contain brushes without having terrain_data at all.
When a lightmap bake has produced a scene hash, it may be stored at the root:
"lightmap_scene_hash": "..."
The hash identifies the scene state associated with the saved lightmap data.
When a map is loaded, the saved hash is restored into the lightmap bake state. A newly loaded scene is still treated as requiring verification/rebaking before its first Play session.
The visual logic graph can persist node positions independently of the underlying I/O connections.
When graph positions exist, the root contains:
"logic_graph": {
"node_positions": {
"entity-uuid": {
"x": 320.0,
"y": 180.0
}
}
}
The key is the entity's stable ID.
The positions are editor layout data; they do not define the actual I/O behaviour of the level.
Fio uses Cartesian 3D world coordinates.
X = horizontal
Y = vertical
Z = depth
Positions and dimensions are stored as numeric JSON values.
The editor serializes the live EditorState directly into the level dictionary before writing the .json file.
The normal save operation is effectively:
json.dump(self.state.get_level_data(), f, indent=4)
The serialized level therefore contains persistent scene data rather than a separate compiled or baked level representation.
Runtime-only renderer and geometry cache fields are stripped before serialization.
The loader accepts older map versions.
If version is absent, the map is treated as V1.
V1 maps can be migrated automatically, including legacy target properties used by the earlier I/O system.
V2 and later maps use the separate io_connections representation.
Missing brush IDs are also backfilled with UUIDs when older data is loaded.
{
"version": 3,
"brushes": [
{
"pos": [0.0, 64.0, 0.0],
"size": [256.0, 128.0, 256.0],
"textures": {
"north": "default.png",
"south": "default.png",
"east": "default.png",
"west": "default.png",
"top": "default.png",
"down": "default.png"
},
"is_trigger": false,
"id": "00000000-0000-0000-0000-000000000001"
}
],
"things": [
{
"type": "playerstart",
"pos": [0.0, 128.0, 0.0],
"properties": {
"type": "playerstart",
"name": "PlayerStart_1",
"angle": 0.0,
"id": "00000000-0000-0000-0000-000000000002"
},
"io_connections": []
}
]
}
Original level format.
io_connections representationtarget properties were used for entity/brush communicationIntroduced the modern I/O connection representation and entity IDs.
Current format.
version: 3terrain_datalightmap_scene_hashgeometryuv_scaleshader, lightmap_static, doors, movers and triggers