# Start here: making a game with Sindri

Sindri AI knowledge pack, file `00-start-here.md`. Read this file first, all of
it. It tells you what Sindri is, how a game is laid out, how to write its files
the way the Sindri editor writes them, which file in this pack answers which
question, and the mistakes models most often make.

This pack describes Sindri engine 0.1.0, scene format version
10. Sindri is pre-alpha and changes quickly. When this
pack and the user's own project disagree, the project wins: copy what its
files already do. When you are unsure and can browse, the source of truth is
<https://github.com/vardirhq/sindri-engine>.

## What Sindri is

Sindri is a 2D and 3D game engine with a visual editor. A game is **not** Rust
code. A game is a folder of text files:

- **Scenes** (`.scene`) and **prefabs** (`.prefab`): JSON describing entities and
  the components on them (sprites, shapes, cameras, physics bodies, text, UI).
- **Decay scripts** (`.decay`): gameplay code in Decay, Sindri's own statically
  typed language. Decay looks a little like Rust but is **not** Rust, and not
  JavaScript, C# or GDScript.
- **Weave stylesheets** (`.weave`): CSS-like styling and layout for screen UI.
- **`sindri.toml`**: the project file.
- Assets: images (`.png`), sounds (`.wav`, `.ogg`, `.mp3`), fonts (`.ttf`),
  models (`.glb`).

The editor is one way to write those files. You writing them is another, and it
is supported: the files are the interface.

## Which file answers which question

| File | What it answers |
| --- | --- |
| `00-start-here.md` | Read first: the workflow, project layout, rules and traps. |
| `01-decay-language.md` | How Decay is written: syntax, types, fields, functions, events, state, enums, structs, lists, maps, what is surprising and what does not exist. |
| `02-decay-api.md` | Every name a Decay script may use to reach the engine: `this`, globals such as `World`, `Input`, `Physics`, `Audio`, `Scene`, and every type and member, with exact signatures. Generated from the engine. |
| `03-scripting-guide.md` | How Decay scripts drive a Sindri game: attaching a script to an entity, exported properties, other entities, spawning, physics, input, audio, UI, timers, events, state, lifecycle, and the agent checklist. |
| `04-components.md` | Every component a `.scene` or `.prefab` entity may carry: its exact `sindri.*` name, its fields with default values, what each field means, and a real use from an example project. |
| `05-projects-scenes-prefabs.md` | What a project is (`sindri.toml`), how a `.scene` file is shaped and versioned, how asset paths resolve, prefabs, profiles, and the component registry. |
| `06-2d.md` | How 2D works in Sindri: the 2D model, depth and draw order, cameras, grids, tilemaps and terrains, sprite shaders and masks, 2D lights, particles, nine-slice, world text and transparency. |
| `07-physics-and-movement.md` | 2D and 3D physics: bodies, colliders, sensors, layers and masks, joints, materials, collision events, and character controllers. |
| `08-weave-ui.md` | Screen UI and HUDs: Weave stylesheets, responsive layout, and the full Weave language reference. |
| `09-3d.md` | 3D lighting and shadows, imported GLB models, materials, and day/night. |
| `10-export-and-web.md` | Exporting a project to a static web page and how the browser host runs it. |
| `11-what-works.md` | The capability inventory: for each feature, whether the engine, editor and Decay support it, and its known limitations. Check here before building on a feature. |
| `12-examples-2d.md` | Complete, working example projects, each showing one capability. Every file is shown whole, with its path. |
| `13-examples-3d.md` | Complete, working 3D example projects: transforms, orbit camera, character movement, lighting, model collision, day/night. |
| `14-example-wiring-flappy.md` | Flappy, the smallest complete game in the repository, shown whole. Use it to see how a scene, several scripts, shared state and a HUD fit together, not as a model of game design. |

How to search this pack:

- **The exact name of anything a script can call** (`World.spawn`,
  `Input.just_pressed`, `Physics.set_velocity`, `this.transform`): search
  `02-decay-api.md`. It is generated from the engine, so a name that is not
  there does not exist. Never use an API from memory.
- **The exact name and fields of a component** (`sindri.sprite`,
  `sindri.physics2d.collider`): search `04-components.md` for the type name.
- **How to do a thing** ("jump", "spawn", "score", "sound", "button"): search
  the examples (`12-`, `13-`, `14-`) for a script that already does it, then
  `03-scripting-guide.md`.
- **Whether something is supported at all**: search `11-what-works.md`. A ❌
  means it does not exist; do not build on it.

## How a project is laid out

This is the folder the editor's **New Project** writes:

```text
my-game/
├── sindri.toml        the project file
├── main.scene         the first scene
├── scripts/           .decay files
├── textures/          .png files
└── fonts/             .ttf files
```

`sindri.toml`, as the editor writes it:

```toml
format_version = 1

[project]
name = "My Game"
main_scene = "main.scene"
```

Optional fields: `scenes = ["level2.scene"]` lists every other scene the game
can go to with `Scene.go(...)` (a scene not listed is not exported), and
`[assets] include = ["audio/jump.wav"]` lists files a script uses by name that
no scene mentions, such as sounds played from code. Details:
`05-projects-scenes-prefabs.md`.

**The rule that breaks most hand-written projects:** every path inside a scene
(a texture, a script, a font, a sound) is relative to **the folder the scene
file is in**, not to the project root. If the scene is `assets/level.scene`
and the image is `assets/textures/hero.png`, the scene names it
`"textures/hero.png"`. Writing `"assets/textures/hero.png"` loads nothing.

Some projects keep their scene in an `assets/` folder (the examples in this
pack do); then `main_scene = "assets/level.scene"` and every path inside the
scene is relative to `assets/`.

## Writing files the way the editor does

The editor writes every file in one canonical form. A file you write does not
have to be byte-identical (whitespace does not matter to the loader), but it
must have the same shape, and following these rules means the editor opens it
cleanly and its next save changes nothing important.

### A new scene

The editor's **New Scene** writes a world camera and a sun. Built from the
engine's registered defaults, that is:

```json
{
  "format_version": 10,
  "metadata": {
    "name": "Main"
  },
  "entities": [
    {
      "id": "sun",
      "name": "Sun",
      "transform_3d": {
        "position": [0.0, 10.0, 0.0],
        "rotation": [-0.408218, 0.23457, 0.109382, 0.875426],
        "scale": [1.0, 1.0, 1.0]
      },
      "components": {
        "sindri.light": {
          "color": [1.0, 0.95, 0.86],
          "enabled": true,
          "inner_angle": 0.349066,
          "intensity": 1.0,
          "kind": "directional",
          "outer_angle": 0.523599,
          "range": 8.0
        }
      }
    },
    {
      "id": "world-camera",
      "name": "World Camera",
      "transform_3d": {
        "position": [0.0, 0.0, 9.0],
        "rotation": [0.0, 0.0, 0.0, 1.0],
        "scale": [1.0, 1.0, 1.0]
      },
      "components": {
        "sindri.camera": {
          "far": 100.0,
          "near": 0.1,
          "projection": "perspective",
          "vertical_fov_degrees": 60.0
        }
      }
    }
  ]
}
```

- The camera is perspective, for 3D. **A 2D game uses an orthographic
  camera**: set `"projection": "orthographic"` and give it a
  `"vertical_size"` (how many world units tall the view is), as every 2D
  example does. A scene with no camera draws nothing.
- A 2D game made only of sprites and shapes does not need the sun; it lights 3D
  models.
- `format_version` must be exactly 10; any other number is
  refused.
- `metadata.name` is the scene's name.
- `entities` is a list; order does not matter (the editor sorts by `id`).

### An entity

| Key | Meaning |
| --- | --- |
| `id` | Required, unique in the scene, never changed once other things refer to it. The editor names new ones `game-object-1`, `game-object-2`, …; readable ids such as `"player"` or `"coin-3"` are better and equally valid. |
| `name` | What the editor's hierarchy shows, and what `World.find("Name")` looks for. |
| `parent` | The `id` of the parent entity. The child's transform is then relative to the parent. |
| `transform_3d` | `position` `[x, y, z]`, `rotation` a quaternion `[x, y, z, w]` (no rotation is `[0, 0, 0, 1]`), `scale` `[x, y, z]`. 2D games use it too: x right, y up, z for depth. |
| `components` | An object: component type name to its data. See `04-components.md`. |
| `disabled` | `true` to switch the entity and everything under it off. Omit when false. |
| `prefab` | Instead of `components`: this entity is a copy of a prefab. See `05-projects-scenes-prefabs.md`. |

When the editor adds a component, it writes the component's **default
payload**: the "Fields" block in `04-components.md`. Do the same, then change
only the fields you need. A component with no default (it names an asset, such
as a sprite's texture) must be given that asset.

### A script on an entity

A script is attached with the `sindri.script` component:

```json
"sindri.script": {
  "source": "scripts/player.decay",
  "script": "Player",
  "properties": { "speed": 6.0 }
}
```

`source` is the file (relative to the scene's folder, as above); `script` is
the name after `script` inside it; `properties` sets the script's `@export`
fields for this entity. A property the script does not declare as `@export` is
an error, not ignored.

The matching script:

```rust
script Player {
    @export let speed: number = 6.0;
    var jumps: number = 0.0;

    fn start() {
        // once, before the first update
    }

    fn update(dt: number) {
        let move = Input.axis("ArrowLeft", "ArrowRight");
        this.transform.position.x += move * speed * dt;
    }
}
```

Decay code blocks in this pack are marked `rust` only so they are coloured;
they are Decay.

## How to make a game: the workflow

1. **Start from a working example, not from nothing.** Find the example in
   `12-`, `13-` or `14-` closest to what is wanted, and copy its files whole.
   Change one thing at a time.
2. **Look up every name.** Every host call in `02-decay-api.md`, every
   component in `04-components.md`. If it is not there, it does not exist.
3. **Write whole files.** When giving the user a file, give the complete file
   with its path, not a fragment or a diff, unless they ask otherwise.
4. **Keep scripts small and single-purpose.** One script per behaviour
   (`player.decay`, `coin.decay`, `hud.decay`), with shared game values in a
   `state` (see `03-scripting-guide.md`, "Declared state").
5. **Ask the user to check and run it.** With a copy of the Sindri repository
   and Rust installed, these work from the repository folder:

   ```bash
   cargo run --quiet --package decay-lsp -- --check path/to/my-game   # checks every script
   cargo editor path/to/my-game                                       # opens it in the editor
   cargo run --release --package sindri-player -- path/to/my-game     # plays it in a window
   ```

   The checker's errors carry a stable code such as
   `decay-semantic/unknown-name`; `01-decay-language.md` lists them all. Fix
   every error, then check again; do not stop after the first.

## Decay: things models get wrong

Copied from the Decay language reference. Read both lists before writing any
Decay.

### Surprising behaviour

Things that are true and that most readers — human or model — will guess wrong.

1. **`this.method()` is not a method call in a script.** A container has no
   methods; it is a compile error telling you to call it by bare name. Host
   *types* and structs have methods, and those work — inside a struct's own
   method, `this` is the struct's value and `this.other()` calls another.
2. **There is no truthiness.** `if x` requires `x` to be `bool`.
3. **`"d" + 3.0` is `"d3"`, and `"d" + 1.0 + 2.0` is `"d12"`.** `+` groups
   left to right, so once text is on the left every `+` after it joins. Write
   `"d" + (1.0 + 2.0)` for `"d3"`.
4. **All numbers are floats.** `7 / 2` is `3.5`. There is no integer type and no
   integer division, and `%` is a remainder whose sign follows its left operand.
5. **Member types are checked only where the host described them.**
   `this.transfrom.position.x` is now a compile error against Sindri, because
   Sindri describes its transform — but a path into a type nobody described is
   still accepted and still fails at runtime.
6. **`let` fields are still settable by the host.** That is what `@export` means.
7. **A parameter shadows nothing** — parameters and body bindings share one
   scope, so reusing a parameter's name is a duplicate, not a shadow.
8. **A loop can be stopped by its budget.** A script that runs too long fails
   with `OperationBudgetExceeded` rather than hanging, and that failure is
   reported like any other.
9. **A range exists only in a `for`.** `for i in 0..3` walks `0, 1, 2`;
   `let r = 0..3;` is a parse error.
10. **A collection index is a float like every other number**, and a fractional
    one is refused rather than rounded.
11. **A vector is copied, not shared.** Assigning one and changing the copy
    leaves the original as it was.
12. **Two vectors do not multiply.** `a * b` is an error; write `a.dot(b)`.
13. **`v.length` is a property.** `v.length()` is an error, while `a.dot(b)`
    is a call.

### What does not exist

Do not write these. They are not unimplemented corners; they are absent from the
grammar, and every one of them is a parse error or a diagnostic.

**Control flow:** `loop`, `match` on anything but an enum, a block as a `match` arm's value, ternaries, labelled blocks, `break` or
`continue` with a label or a value. `for … in` walks a list or a range, and
nothing else.

**Data:** sets, tuples, a map a `for` walks directly (walk `keys()`), a map
authored by a scene, methods that change their struct,
defaults for fields of a type a constant cannot hold (a vector, a list, an
entity), enums with data
(`Some(x)`), a range as a value outside `for`, a step (`0..10 by 2`), negative
indices, slicing a list, `Result`, `?` as an operator that returns early.
`List<T>` exists; see "Lists". A value that may be missing is `T?`; see
"Optional values".

**Functions:** closures, lambdas, function values, default arguments, named
arguments, variadics, generics beyond `List<T>`, overloading, methods on
anything but a struct, `impl`, traits.

**Types:** integers, `f64`, unsigned types, characters, type aliases, casts,
inference beyond a binding's own initializer, nullable types, user-defined types
beyond `script`, `component`, `enum` and `struct`.

**Modules:** `import`, `use`, `mod`, `pub`, visibility of any kind, multiple
files. One file is one compilation unit and cannot refer to another by itself;
a host may describe other files' scripts, events, state and shared functions
to it, as Sindri does. There is no `import` line: in a Sindri project every
file already sees the others.

**Standard library:** `print`, `math.*`, format strings (`{:.2}`) — a number
is written with `fixed` and `padded` —, interpolation, parsing text into
numbers, conversion functions, list operations beyond the ones listed — no
`map`, `filter`, `sort` — time, randomness.

**Other:** operator overloading, macros, attributes other than `@export`, block
comments, doc comments, a `const` inside a container or a function, `static`,
exceptions, `try`, `panic`,
concurrency, `async`.
