Skip to content

File Structure ​

A single ATLAS game can be authored as one .yaml/.json file (see Game Examples), or split across multiple files in a project directory. This page describes that directory convention.

Why split files at all? ​

Formats like Ludii's .lud let you factor a definition into reusable pieces, but that reuse is powered by an implicit, global library of ludeme macros ("HopCapture", "DraughtsEquipment", "BlockWin", ...) defined outside the file you're reading. Understanding any single .lud file requires prior knowledge of that external library — the file is not self-contained.

ATLAS's directory convention solves the same "my definition is too big for one file" problem differently: every file you split out is a plain, complete fragment of the same strict atlas.v1.Game schema — no macros, no external ludeme references, nothing to look up elsewhere. Reading entities/red_checker.yaml on its own tells you everything about that entity, because it is an Entity object, verbatim.

Directory layout ​

An ATLAS project directory recognizes a fixed set of file names. Each contributes exactly one top-level field of the merged Game:

FileRequiredContributes
game.yamlYesid, name, global_properties
players.yamlNoplayers
zones.yamlNozones
setup.yamlNosetup
actions.yamlNoroot-level actions
entities.yamlNoentities (as a single list)
entities/ (directory)Noentities (one file per Entity)
presentation.yamlNoloaded separately — see Presentation & assets below

.yml and .json extensions are accepted everywhere .yaml is shown above.

Self-contained, not implicit

There is no $include/import directive to chase down. The loader discovers files purely by their fixed names in the directory you point it at — nothing references anything outside that one folder.

The entities/ directory ​

Instead of one entities.yaml containing a list, you can give each Entity blueprint its own file under entities/. Unlike every other split file, an entity file's top-level fields are the Entity object itself — no wrapping entities: key:

yaml
id: red_checker
entity_type: PIECE
tags: [RED, CHECKER]
string_properties:
  name: "Peon"
  owner: "Red"
numeric_properties:
  is_king: 0
yaml
id: black_checker
entity_type: PIECE
tags: [BLACK, CHECKER]
string_properties:
  name: "Peon"
  owner: "Black"
numeric_properties:
  is_king: 0

entities.yaml and an entities/ directory are mutually exclusive — pick one form per project.

Merge rules ​

The loader merges every recognized file into a single payload and then validates it exactly as it would a single-file game, so nothing about the strict schema changes because you split your files:

  1. Every field comes from exactly one file. If zones shows up in both game.yaml and zones.yaml (or entities in both entities.yaml and entities/), loading fails immediately with an error naming both sources.
  2. game.yaml is required. A directory with no game.yaml/.yml/.json fails to load.
  3. Unrecognized files are rejected. A stray zone.yaml (missing the s) or any other file not in the table above fails loading with an explicit error — the same strict, "reject unknown fields" philosophy the loader already applies to unknown properties now also applies to unknown files.

This keeps splitting deterministic: there's exactly one valid way to read a project directory back into the same Game you'd get from a hand-merged single file.

Presentation & assets ​

atlas.proto's Game schema deliberately has no fields for sprites, bitmaps, colors, or any other rendering concern — Zones and Entities describe data topology and state only. Visual assets instead live in an optional, separately-validated presentation.yaml, loaded against its own presentation.proto contract and never merged into Game:

yaml
game_id: checkers
entities:
  - entity_id: red_checker
    assets:
      - kind: bitmap
        path: "assets/red_checker.png"
        variant: "default"
      - kind: bitmap
        path: "assets/red_checker_king.png"
        variant: "king"
    style_properties:
      color: "#B22222"
zones:
  - zone_id: main_board
    assets:
      - kind: bitmap
        path: "assets/board_8x8.png"
    style_properties:
      board_style: "Chess"
  • entity_id/zone_id match the corresponding Entity.id/Zone.id in the rules definition — no other coupling exists between the two files.
  • assets is a list of { kind, path, variant } references (a sprite, bitmap, 3D model, icon, etc.); variant distinguishes states like a promoted/kinged piece.
  • style_properties is a free-form string map for renderer hints (color, scale, board skin name, ...) that don't warrant their own asset file.
  • A game can have multiple presentations (e.g. a presentation.yaml and a presentation.isometric.yaml loaded on demand) since nothing about Game ever references one.

Because rules and presentation are validated independently, a rules engine, AI, or headless server never needs to load — or even have access to — art assets at all.

Loading a project directory ​

Both language bindings expose a directory-aware loader alongside the existing single-file one:

csharp
using Atlas.Core.Loading;

var projectLoader = new AtlasProjectLoader();
GameBundle bundle = projectLoader.LoadFromDirectory("examples/checkers-split");

Game game = bundle.Game;
Presentation? presentation = bundle.Presentation; // null if no presentation.yaml
ts
import { AtlasProjectLoader } from "atlas-core-ts";

const projectLoader = new AtlasProjectLoader();
const { game, presentation } = await projectLoader.loadFromDirectory("examples/checkers-split");
// presentation is undefined if no presentation.yaml exists

The validation CLIs also accept a directory in place of a single file:

sh
dotnet run --project src/Atlas.Cli -- examples/checkers-split
node dist/cli.js examples/checkers-split   # from src/atlas-core-ts

See examples/checkers-split in the repository for a complete working project directory built this way, split out of the single-file Checkers example.