DEMIENGINEDOCUMENTATION Everything is free
Project & file formats · Start here

Every project field explained, from scenes to build metadata.

demi.project.json

The project file is the root of every DemiEngine game. It names the game, lists its scenes, and holds the settings that apply everywhere: input, physics, timing, performance, debug overlays, and build metadata.

Minimal project

{
  "format_version": 1,
  "name": "My Game",
  "scenes": [
    { "id": "scene://game/main", "path": "scenes/main.scene.json" }
  ]
}

That is enough to validate and run. Everything below is optional and can be added when you need it.

Identity and scenes

{
  "format_version": 1,
  "name": "Skyforge",
  "main_scene": "scene://game/main",
  "scenes": [
    { "id": "scene://game/main" },
    { "id": "scene://game/menu", "path": "scenes/menu.scene.json" }
  ]
}
FieldMeaning
format_versionAlways required. Documents without it are rejected.
nameHuman-readable game name. Used in the editor and generated desktop entry.
main_sceneScene URI launched when you run the game without a scene override.
scenesEvery scene the game owns. Each entry has an id (scene://…) and an optional path.

When path is omitted, the file location is inferred from the ID: scene://game/main → scenes/main.scene.json. An explicit path always wins. The scene list is what the editor registers and what demi validate reaches.

Packages and registry

{
  "package_registry": "https://demiengine.de",
  "packages": {
    "demi.gameplay.health": "^1.0.0",
    "kenney.textures.prototype": "^1.0.0"
  }
}
  • package_registry — where demi package add resolves packages from.
  • packages — direct dependencies with semver constraints. Resolved versions are pinned in demi.packages.lock.json.

See Installing packages.

Startup assets

"assets": [
  "asset://ui/theme",
  "asset-group://startup"
]

This array is startup preload only. Items listed here are loaded before the first scene runs. Everything else loads on demand in Lua or with the scene that references it. Listing a large folder here will not implicitly load its contents — use an asset group when you want a batched load.

Input

"input": {
  "presets": ["wasd_arrows", "confirm"],
  "actions": {
    "jump": {
      "type": "button",
      "context": "gameplay",
      "bindings": [{ "input": "key:space" }]
    }
  }
}

presets pull in ready-made action sets. actions declares your own. Each action has a name, a type, a context, and a list of bindings — the physical inputs that feed it.

Bindings are covered in full on Input bindings: what vector does, how deadzones work, and what a virtual input is.

Physics layers

"physics": {
  "layers": {
    "goal": ["player"],
    "player": ["goal", "wall"],
    "wall": ["player"]
  }
}

An object collision matrix of up to 16 layers. Each key is a layer name; the value is the list of layer names it collides with. Both sides must accept each other. Colliders pick one of these names. Details: Physics layers.

Simulation

"simulation": {
  "fixed_timestep": 0.016666667,
  "random_seed": 1,
  "maximum_fixed_steps_per_frame": 4
}
FieldDefaultWhy it exists
fixed_timestep0.016666667 (1/60 s)Physics and on_fixed_update run at this step regardless of frame rate, so results are reproducible.
random_seed1Seeds the deterministic RNG. Two runs with the same seed and inputs produce the same rolls.
maximum_fixed_steps_per_frame4 (max 16)Safety valve: if a frame takes a long time, at most this many catch-up steps run before the game slows down instead of spiraling.

Why specify it? Because you want replays, E2E tests, and network prediction to match. The defaults are fine for most games; change the timestep only if you need a different simulation rate. See Simulation & seeds.

Display

"display": {
  "vsync": true
}

vsync caps presentation to the monitor refresh. Frame limiting and vsync are coordinated — you do not need a separate sleep.

Performance budgets

"performance_budgets": {
  "maximum_frame_ms": 16.7,
  "maximum_draw_calls": 800,
  "maximum_resident_assets": 256
}

Budgets are targets you measure against, not automatic quality knobs. They never change what the game renders. Their value:

  • demi asset budget reports current usage against each limit.
  • Profiling sessions and qualification runs compare numbers to a declared intent instead of “it felt fine”.
  • CI or playtests can fail when a level blows the budget you set for it.

Full list and workflow: Performance budgets.

Debug overlays

"debug": {
  "colliders": false,
  "contacts": false,
  "grid": false,
  "entity_ids": false,
  "draw_order": false,
  "ui_bounds": false,
  "profiler_hud": false
}

Set a flag to true during development to draw that overlay. Override at run time with --debug-overlays colliders,contacts. Ship with all flags false.

Network contract

"network_contract": "asset://network/arena_contract"

Points at the NetworkContract asset that defines multiplayer rules. Required only for networked games. See Sessions & contracts.

Build metadata

"build": {
  "application_id": "com.example.skyforge",
  "display_name": "Skyforge",
  "executable_name": "skyforge",
  "version_name": "0.1.0",
  "version_code": 1,
  "icon": "asset://branding/icon",
  "splash": "asset://branding/splash",
  "window": { "width": 1280, "height": 720, "mode": "windowed" },
  "android": { "orientation": "landscape", "permissions": [] }
}

application_id is required before packaging (reverse-DNS form). Details: Build & platform settings.