Learn/Universes

Universes

A Universe is a folder of Spaces that share assets, recordings and tools. A Space is one world: a folder of readable files, the database Studio runs it from, and a git history that grows with every save.

Time15 min readLevelIntermediateUpdatedUpdated Sep 2026

01Overview

Universe, Space, Instance

A Space is one world: a scene with its parts, scripts, lighting and data, kept in one folder. A Universe is a folder of related Spaces plus the assets, recordings and experiment results they share. Everything inside a Space is an instance: a part, a folder, a model, a script, a light.

LevelWhat it isWhere it lives
Eustress folderHolds every UniverseDocuments/Eustress/
UniverseRelated Spaces and what they shareUniverse1/, a folder with a Spaces/ folder
SpaceOne world, its database and its historyUniverse1/Spaces/Space1/
InstanceOne object in the worldA folder with an _instance.toml, or a record in the database

Files, Database, History

A Space keeps its world in three places, and each has one job:

  • Files are the readable form: service settings, scripts and _instance.toml documents you can open in any editor.
  • The WorldDb (world.fjalldb/) is the form Studio runs from. Files are copied into it, and the scene is built from it.
  • Git history is a commit of the Space folder each time you save.
Space folderTOML, scripts, servicesedit in any editorworld.fjalldbtree: files by pathentities: binary partsmutations: op-lognever committed to gitScenewhat Studio showsand simulatesgit historyone commit per saveingestloadeditsCtrl+S, autosave
Files are read into the database when a Space opens and whenever you save one in another editor. Studio builds the scene from the database and writes edits back to it (and, for some parts, to their files). Each save commits the folder to git; the database folder is excluded.

The three are not copies of one another. Some edits exist only in the database, and the database folder is never committed, so it is worth knowing where each change goes. The rest of this page traces it.

A Place to Rehearse

The idea behind Universes is that a world should be as safe to change as a codebase: branch it, try the change, measure the result, then keep it or throw it away. Today that loop runs on git branches of a Space's files, experiment runs that save their results, and extra engines that run variants side by side. Branching the database itself exists in the storage library and is not yet wired into Studio; What's Next covers it.

02On Disk

The Eustress Folder

Every Universe lives in one Eustress folder, Documents/Eustress. On Windows that is the local %USERPROFILE%\Documents folder, not a OneDrive-redirected copy, because OneDrive rewrites file metadata and fights the file watcher. Set EUSTRESS_WORKSPACE to use another folder. When the folder is empty, Studio creates Universe1 with one Space, Space1.

Eustress folder
Documents/Eustress/
  .eustress/
    engine.port                     port of the last engine to start
    instances/<pid>.json            one record per running engine
  Universe1/                        a Universe
    .eustress/                      shared by the Universe's Spaces
    Spaces/
      Space1/                       a Space
      Space2/
  Universe2/

Studio remembers the last Space you opened (last_space_path in ~/.eustress_engine/settings.json) and opens it again on the next launch.

Inside a Universe

A Universe is any folder in the Eustress folder that holds a Spaces/ folder. Create one with File > New Universe... (Ctrl+Shift+N). Its .eustress/ folder holds what its Spaces share, so every Space in it sees the same default meshes, recordings and experiment results:

Universe1/.eustress/
engine.port                       bridge port of the engine working in this Universe
lsp.port                          Rune language server port
assets/parts/                     default part meshes: block.glb, ball.glb, ...
assets/meshes/                    shared meshes
knowledge/recordings/<Space>/     simulation recordings, one JSON file per run
experiments/                      results saved by run_experiment
telemetry.jsonl                   live watchpoint values, one line per second
runtime-snapshot.json             play state and sim values, 4 times a second
sim-commands.jsonl                commands queued by the MCP sim tools

Inside a Space

A Space is a folder under Spaces/. File > New Space (Ctrl+N) creates one with 19 service folders, a Baseplate and a Welcome Cube:

Space1/
Workspace/                        parts, models and folders
  _service.toml
  Baseplate/_instance.toml
  WelcomeCube/_instance.toml
Lighting/                         Sun, Moon, Sky, Atmosphere
SoulService/  MaterialService/  DataService/  ...   19 services in all
src/
space.toml                        name, author, version
simulation.toml                   simulation clock settings
header.bin                        world id, engine and schema versions
world.fjalldb/                    the WorldDb, created on first open
.git/                             history, created on first save
.gitignore
.eustress/
  project.toml  settings.toml  sync.toml  publish.toml  ...
  view.toml                       the 2D or 3D view the Space opens in
  local/                          user-local state, never committed
  trash/                          deleted instances, recoverable
  exports/instances/              database contents exported as TOML
  last_reconcile                  when files were last read into the database

Deleting a file-backed instance in the Explorer moves its folder into .eustress/trash/ instead of erasing it. Perspective explains view.toml, and Simulation explains the clock settings and recordings.

An Instance File

On disk, an instance is a folder named after it that holds an _instance.toml. This is the Welcome Cube every new Space starts with, a 4 m cube resting on the Baseplate:

Workspace/WelcomeCube/_instance.toml
[metadata]
class_name = "Part"
archivable = true
created = "2026-09-22T17:04:11.482019300+00:00"
last_modified = "2026-09-22T17:04:11.482019300+00:00"

[asset]
mesh = "parts/block.glb"
scene = "Scene0"

[transform]
position = [0.0, 2.0, 0.0]
rotation = [0.0, 0.0, 0.0, 1.0]
scale = [4.0, 4.0, 4.0]

[properties]
color = [0.388, 0.706, 1.0, 1.0]
transparency = 0.0
reflectance = 0.2
anchored = true
can_collide = true
locked = false

Lengths are meters. A file can name a different authoring unit with unit under [metadata] (for example cm or ft), and the loader converts its position and scale to meters once, as it loads.

03The WorldDb

One Database per Space

Each Space has one database, world.fjalldb/, opened when the Space opens. It is a Fjall store: a log-structured merge tree that appends writes to a journal and compacts them in the background, so a Space can grow past the point where one file per part stays practical. Eustress builds Fjall from its own copy in the repository, wrapped by the eustress-fjall crate, and everything reaches it through the WorldDb trait in eustress-worlddb.

Beside it, header.bin identifies the world. It starts with the bytes EUSWORLD and records a world id, the version of the engine that last wrote it and the schema version of the data inside.

Warning
world.fjalldb is not a cache

Some edits exist only in the database, and world.fjalldb/ is excluded from git. Back it up with the rest of the Space, and never delete it to force a reload: Studio would rebuild it from the files and every edit that lived only in the database would be gone. The .gitignore Studio writes says the same thing.

Partitions

Inside, the data is split into partitions, independent key spaces that compact separately:

PartitionWhat it holds
treeEvery file of the Space, keyed by its path relative to the Space folder, plus #bin records for simple instances
entitiesBinary instance cores, keyed by the spatial cell they sit in
entities_uuidThe same cores, keyed by instance UUID
path_to_uuid, uuid_to_pathLookups between file paths and UUIDs
class_indexWhich instances belong to each class
mutationsThe causal op-log
datasets, timeseriesData Platform datasets and recorded series
voxelsVoxel terrain chunks
datastore, datastore_ordLaid out for DataStore values; scripts do not write here yet
metaThe commit counter, the op-log sequence and the schema version

Opening a Space

Opening a Space runs the same steps every time:

  1. Studio opens world.fjalldb/ and writes header.bin if the Space has none.
  2. On the first open the database is empty, so Studio copies every file of the Space into tree, keyed by relative path. It skips .eustress/, .git/, world.fjalldb/ and other dot folders, and leaves the files where they are.
  3. On every later open it reconciles: each .toml, .rune, .luau, .soul or .md file changed since the time stored in .eustress/last_reconcile is copied in, and a .toml deleted from disk is removed from tree as long as its folder still exists. This runs on a background thread while the scene loads, and the changes are applied to the scene as they arrive.
  4. The scene is built from the database, not from the files: services, folders and instances from tree, binary parts from entities. A Space with more than 100,000 binary parts streams them around the camera instead of loading them all.
  5. While the Space is open, the file watcher copies every file you save in another editor into tree and applies it to the live scene.

If the database cannot be opened at all, Studio reads the files directly, so a Space always opens. Building covers streaming large Spaces.

Tip
Very large Spaces

The reconcile checks every file in the Space folder. On a Space with hundreds of thousands of files, set EUSTRESS_SKIP_DISK_SCANS=1 to skip it. Files edited while Studio was closed are then ignored until you clear the variable and reopen the Space.

Where an Edit Lands

Where a change is stored depends on how you made it:

ChangeStored asIn git
A part from Toolbox > Primitives with no Folder or Model selected, in Edit modeA binary core in entities, with no fileNo
A part from the Insert menu or Insert Object..., or from Primitives with a Folder or Model selectedA new folder with an _instance.tomlYes
A Part an agent creates over MCP while the engine runsA binary core in entities, with no fileNo
Save a simple part: no children, a built-in shapeA #bin record in tree; the file keeps its earlier valuesNo
Save a part that has children or a custom meshIts _instance.toml, rewritten on diskYes
Edit a file in another editorThe file, copied into tree by the watcher or the next openYes

A binary core is a compact record of one part that sits at the top level of the Workspace. Its path, such as Workspace/__bin_Part_<id>/_instance.toml, exists only in the database. Parts with children, custom meshes, scripts and GUI elements always keep a real folder, because a single record cannot hold their children or a relative mesh path.

04History

Every Save Is a Commit

Ctrl+S (File > Save Space) writes the scene, then runs git add -A and commits in the Space folder with the message manual save <timestamp>. Git runs on a background thread, so the editor never waits on it, and the first save runs git init. When nothing changed since the last commit, no commit is made. The File menu shows the time of the last snapshot.

Autosave does the same on a timer, every 300 seconds by default, with the message autosave <timestamp>. A successful autosave shows the toast Auto-saved (git); a failed one shows an error instead of failing quietly. Commits carry your Eustress user name when you are signed in. Otherwise they use the repository's own identity, which Studio sets to Eustress Engine Autosave when it creates the repository.

~/.eustress_engine/settings.json
{
  "auto_save_enabled": true,
  "auto_save_interval": 300.0
}

The interval is in seconds. Change these two keys, with Studio closed, to turn autosave off or change its pace.

What Git Captures

A commit holds the files in the Space folder, which is not the whole Space. Autosave adds world.fjalldb/, .eustress/trash/ and *.bak-* to the Space's .gitignore, and a new Space already ignores .eustress/local/.

Part of the SpaceIn git
Services, scripts and GUI definitionsYes, as files
Parts from the Insert menu or Insert Object, and parts with children or custom meshesYes, as files
Binary cores: parts from Toolbox > Primitives at the top level, and Parts agents create over MCPNo, database only
Saved changes to simple parts (#bin records)No, database only
world.fjalldb/Never

To put binary parts into history, export them first. The MCP tool export_instances_toml writes every binary core as an ordinary _instance.toml under .eustress/exports/instances/, and the next save commits that folder. The loader and the file watcher both skip .eustress/, so an export is never loaded back as a second copy of the scene.

MCP
{ "tool": "export_instances_toml", "arguments": { "layout": "single_file" } }

// layout "folders" (default): one loadable <Name>_<id>/_instance.toml per core
// layout "single_file": one instances.toml, easier to read and diff
// limit: 5000 by default, 200000 at most; class and region narrow the export

Going Back

History is plain git, so any git client reads it. To bring back an earlier version of a file, check it out of an older commit:

Bash
cd ~/Documents/Eustress/Universe1/Spaces/Space1
git log --oneline
git checkout <commit> -- Workspace/WelcomeCube/_instance.toml

Studio treats the restored file like any other edit made outside the editor: the file watcher applies it while the Space is open, and the reconcile reads it in on the next open. A checkout restores files only. Binary cores and #bin records are in no commit, so they keep their current state.

05Branching

Git Branches

A branch of a Space is a git branch of its folder. You can use any git client, or let an agent use the git tools of the MCP server and of Studio's Workshop panel. They run in the Space's own repository, the nearest .git at or above the Space folder.

ToolWhat it does
git_branchlist, create, switch, delete or merge (merges with --no-ff)
git_commitStages all changes, or the files you list, and commits
git_status, git_diff, git_logRead the working tree and recent history
feedback_diffDiffs two branches, commits or tags, optionally as a summary

Experiments

run_experiment turns a what-if into a recorded run. It sets the simulation values you give it, runs the simulation for duration_s simulated seconds, waits for the run to finish (300 s of wall time at most, by default), summarizes the telemetry and saves the result as JSON in the Universe's .eustress/experiments/ folder. With create_branch it first creates and switches to a git branch named exp/<name>-<timestamp>.

MCP
{
  "tool": "run_experiment",
  "arguments": {
    "name": "high_voltage_4v3",
    "sim_values": { "cell_voltage": 4.3 },
    "duration_s": 60,
    "time_scale": 100,
    "create_branch": true
  }
}

list_experiments lists saved results, newest first, and compare_runs shows the change in every metric between two of them (latest and latest-1 work as names). Merge the winning branch with git_branch and delete the rest. Simulation covers the clock, time compression and watchpoints these runs use.

Advanced
A git branch does not branch the database

Switching branches changes the files. State that lives only in world.fjalldb/ stays as it is on every branch. The sim_values are applied to the running simulation and are not files either, so git diff does not show them; the experiment JSON records them under config. Put a design change you want to compare in a file, and it travels with the branch.

Variants in Parallel

To run variants at the same time, give each one its own Space and its own engine. eustress open <space> --headless starts a windowless engine on a Space and prints its process id and bridge port; run each variant's experiment against that engine, then compare the saved results. CLI & Headless covers the commands, and the next sections cover how the engines find each other.

06The Op-Log

What It Records

Git records states; the causal op-log records the events between them. It lives in the mutations partition as an append-only list, one record per create or delete, numbered by a sequence the database assigns and that survives restarts. Every record carries:

FieldMeaning
seqPosition in the log
tx_idTransaction the change belongs to
ts_nanosWall-clock time in nanoseconds
actorWho caused it: User, Script:<name>, Mcp:<tool>, Importer, FileWatcher or System
opCreate, Update or Delete
class, uuid, rel_pathWhich instance, and its path in the Space
has_before, has_afterWhether the record holds the instance before and after the change

Today the log captures binary cores: created from Toolbox > Primitives or through the bridge, deleted in the editor, through the bridge or by undo, and recorded as System. It also captures new _instance.toml files that appear while the Space is open, recorded as FileWatcher. Property edits, moves and new GUI elements are not recorded yet, and no record carries a before-image, so the log can say what was created and deleted but cannot yet rewind it.

Reading It

Read the tail of the log from a running engine with the oplog_tail MCP tool, the oplog.tail bridge method, or the CLI. The default is the last 50 records, the maximum 1,000, oldest first:

Bash
eustress bridge --universe ~/Documents/Eustress/Universe1 oplog --limit 20
Response shape
{
  "count": 1,
  "mutations": [
    {
      "seq": 42,
      "tx_id": 7,
      "ts_nanos": 1790000000000000000,
      "actor": "System",
      "op": "Create",
      "class": "Part",
      "uuid": "9f2c4e1ab7d34c0e8a61f0b2c3d4e5f6",
      "rel_path": "Workspace/__bin_Part_00000000a1b2c3d4/_instance.toml",
      "has_before": false,
      "has_after": true,
      "parent_tx": null,
      "reason": null
    }
  ]
}

07Engines & Tools

Several Engines

Every running engine, windowed or headless, opens a bridge on 127.0.0.1 and writes its port to engine.port in its Universe's .eustress/ folder, with a copy in the Eustress folder. That file has one slot, so with two engines in one Universe it names only one of them. Each engine therefore also writes a record of its own, keyed by process id:

Documents/Eustress/.eustress/instances/18244.json
{
  "pid": 18244,
  "port": 53120,
  "kind": "headless",
  "space": "C:\\Users\\me\\Documents\\Eustress\\Universe1\\Spaces\\VariantA",
  "universe": "C:\\Users\\me\\Documents\\Eustress\\Universe1",
  "started_at": "2026-09-22T18:02:41.512934100+00:00"
}

eustress instances lists these records and removes any whose port no longer answers, eustress bridge --pid <pid> ... drives one engine by its record, and eustress close shuts engines down cleanly by pid, by Space or all at once.

Warning
One engine per Space

Fjall keeps no lock between processes, and nothing stops two engines from opening the same Space, so nothing coordinates their writes to its database. Give every engine its own Space, and close the engine on a Space before an offline tool writes to its database.

Inspect Without the Engine

eustress-space reads a Space's database without linking the engine. It takes a Space folder or its world.fjalldb/ and works on the binary instance cores:

Bash
eustress-space open   <space>                 # world id, engine version, core count, bounds, classes
eustress-space verify <space>                 # validates every core; exits non-zero if any fail
eustress-space export <space> [--out <dir>]   # one readable .instance.toml per core

Export writes to export_toml/ inside the given path unless you pass --out. The tool is built from its crate in the repository: cargo run -p eustress-space -- open <space>.

Repair Tools

Two more command-line tools repair a Space's database while no engine has it open:

  • purge_tree_path removes every record matching --match from every partition: the tree key and its #bin twin, the identity lookups and both cores. It is a dry run unless you pass --apply, and with it every value is first copied to .eustress/trash/db-purge-<unix-seconds>/.
  • reseed-space-subtree copies one folder's .toml files from disk into tree and drops their #bin records, for when the database holds an older version of files you trust.
Bash
cargo run -p eustress-worlddb --bin purge_tree_path -- --space <space> --match Aureole
cargo run -p eustress-worlddb --bin purge_tree_path -- --space <space> --match Aureole --apply
cargo run -p eustress-engine --bin reseed-space-subtree -- --space <space> --subdir Workspace

08What's Next

Database Branches

The storage library already contains the branch that git cannot provide. A BranchHandle forks any database in constant time: writes go to an in-memory overlay, reads fall through to the parent, commit replays the overlay into the parent and discard drops it. Branches nest, and batch_rollout runs many of them forward in parallel with no rendering and returns a digest of each. Both are tested in eustress-worlddb, and nothing in Studio, the CLI or the MCP server calls them yet. Wiring them in will let a what-if fork the whole Space, database included, and throw the losers away at no cost to the original.

Complete History

Two gaps separate history from the whole Space. The op-log will record property edits and new GUI elements, attribute each change to the agent or person that made it, and keep before-images, which lets the replay and rewind functions the storage library already has step a Space back. And a versioned export of the database will let a commit carry the parts that live only in world.fjalldb/.

Branch it. Run it. Keep what works.

Loading Eustress Engine...