# Radiant Shmup community level format (draft 0.2)

A level is **one Markdown file**. Anyone can write one in a text editor; the game (and the validator) parse it with a
few simple rules. Nothing in a level is executable: it is data, links and ASCII art.

```
# Level name                      <- H1: the level's name
> intro text, up to 4 lines       <- blockquote: the intro card
## Meta                           <- key: value list
## Resources                      <- named links: spritecards, music, sfx
## Legend                         <- optional: custom glyphs
## Waves                          <- ### Wave @ time, then a ```wave grid
## Ground                         <- structures and ground turrets, ```ground grids
## Boss                           <- hull and pods
```

Unknown sections are ignored, so authors can add notes. Text that is not in one of the shapes described below is a
comment. `<!-- ... -->` comments are fine anywhere.

The 10 official levels are shipped in this format in [`official/`](official/): copy one and change it.

## Grid and time

The playfield is 360 x 800 world units, portrait.

* **1 column = 10 units**: a row is **36 columns** wide. Column 0 is the left edge; world x = `column * 10 - 175`.
* **1 row = 20 units**: 40 rows fill one screen.
* **Grids are drawn as they appear on the screen**: the top line is the top of the picture and the **bottom line enters first**
  (a wedge that points down has its leader on the bottom line). Time runs from the bottom of a grid to its top.
* `.` or a space is empty. Rows must have the same width; short rows are padded with `.` (a row wider than 36 is an error).
* Time is in **seconds from the end of the intro card**. `@ 12s` or `@ 12.5s`. Everything is timed from there; the
  boss arrives at its own time (see Boss).
* Positions inside paths (`x=`, `y=`) are **world units**: `x` from the screen centre (negative = left), `y` **down from
  the top edge** (so `y=200` is the screen's middle).

A ruler helps while drawing; it is not part of the level:

```
0         1         2         3
012345678901234567890123456789012345
```

## Meta

```
## Meta
- author: Jane Doe <https://github.com/janedoe>
- version: 1
- license: CC0
- terrain: ice            # space | jungle | ice | desert | sea | lava | swamp | city
- weather: snow           # clear | mist | snow | dust | rain | embers | spores
- music: stage-2          # built-in track (stage-0..9) or a Resources name
- boss-music: boss-2      # built-in track (boss-0..4) or a Resources name
- scroll: normal          # slow 30 | normal 42 | fast 60 units/s (ground and ground units)
- difficulty: 3           # 1-5, the author's own rating
- place: Kryos           # optional: the planet or location, shown on the level card
- outro: The wyrm falls.  # optional: shown after the boss; use ` / ` for line breaks
- player: ships:hero       # optional: your ship's look, a card:row drawn facing UP (cosmetic, same hit box)
```

`license` is required: it covers the level and everything linked from it.

## Resources

Art and audio are **links**, never embedded. Each gets a short name you can use in the Legend.

```
## Resources
- fleet: https://example.com/art/fleet.png  [spritecard cell=64x64 grid=4x3 views=front,left,right,hit rows=drifter,weaver,gunner]
- stage-theme: https://example.com/music/theme.ogg  [music]
- boom: https://example.com/sfx/boom.wav  [sfx]
```

### Spritecards

Sprites are supplied as **spritecards**: one PNG that holds several enemies, each drawn from several views.

```
 views ->   front   left   right   hit
 row 0      [ d1 ]  [ d1 ]  [ d1 ]  [ d1 ]      <- one enemy per row ("drifter")
 row 1      [ w2 ]  ...                          <- next enemy ("weaver")
 row 2      [ g3 ]  ...
```

* `cell=64x64`: pixel size of one frame. `grid=4x3`: columns x rows. The image must be exactly `cell * grid`.
* `views=` names the **columns**, left to right. The game uses `front` normally, `left` / `right` while the enemy is
  moving sideways (banking) and `hit` for a flash when damaged. Any of them may be omitted; a column that isn't named is
  ignored. With no `views=`, the card has one view per column and only column 0 is used.
* `rows=` names the **rows**, top to bottom (optional; rows can also be addressed by number).
* Reference a sprite as `card:row`: `fleet:weaver` or `fleet:1` (rows count from 0). For a single frame use
  `card:rRcC`, for example `fleet:r2c1`.
* Cells are drawn facing **down** (towards the player); the game rotates them when a path needs it.
* Boss hulls use a spritecard with one cell the size of the whole boss: `[spritecard cell=220x160 grid=1x1]`.

### Rules

* `https` only. An optional `sha256=<64 hex>` in the brackets pins the exact file.
* PNG only, at most 1024 x 1024 and **512 KB**. Music OGG at most 3 MB. Sound effects WAV at most 200 KB.
* Files are downloaded once and cached; the game works offline after the first play.
* Don't link art you don't have the rights to. Levels that do will be removed.

## Legend

**Built-in glyphs** mean a simple level needs no legend. A `## Legend` table can add to them or override them.

Glyphs are per grid kind: `wave` grids and `ground` grids have separate tables, so the same letter can mean different
things in each.

### `wave` grids

| Glyph | Enemy | HP | Score | Behaviour |
|---|---|---|---|---|
| `d` | drifter | 1 | 10 | 35% shoot back |
| `w` | weaver | 2 | 20 | 60% shoot back |
| `s` | strafer | 12 | 80 | aimed 3 x 0.5 shells every 0.95 s |
| `m` | mine-layer | 14 | 90 | drops a mine every 0.85 s |
| `g` | gunner | 12 | 60 | holds and fires aimed fans, cycling 2 patterns |
| `v` | diver | 2 | 30 | hangs, flashes red, then dives at the player |
| `E` | elite | 45 | 250 | holds 14 s, cycles fans and rings |
| `C` | carrier | 60 | 250 | an elite that also launches 2 divers every 3.2 s |
| `a` | wing ship | 5 | 50 | aimed 1 orb every 2.1 s |
| `A` | wedge leader | 8 | 90 | aimed 3 x 0.45 shells every 1.9 s |
| `q` | wall ship | 4 | 45 | aimed 1 orb every 2.0 s |
| `b` | bruiser (fan) | 26 | 220 | aimed 5 x 0.8 shells every 1.9 s |
| `B` | bruiser (ring) | 26 | 220 | ring of 10 orbs every 2.7 s |
| `F` | flower core | 32 | 300 | ring of 12 orbs every 2.4 s |
| `o` | flower guard | 2 | 30 | orbits the nearest `F` at 52 units, 1.5 rad/s |
| `T` | tank | 7 | 60 | ground vehicle, aimed fire (use a `drive` path) |
| `H` | heavy tank | 28 | 160 | ground vehicle (use a `drive` path) |
| `S` | gunship | 6 | 50 | sea vehicle (use a `drive` path) |
| `Z` | heavy gunship | 18 | 110 | sea vehicle (use a `drive` path) |

HP and scores shown are base values; the game scales HP with the player's difficulty setting.

### `ground` grids

Ground grids scroll with the ground at the level's `scroll` speed (default 42 units/s, so 2.1 rows per second).

| Glyph | Meaning | Footprint |
|---|---|---|
| `#` | wall block | one cell; adjacent cells merge into one solid block |
| `H` | house | solid (kills on contact), drawn by terrain |
| `R` | ruin | solid |
| `B` | bunker | solid |
| `K` | fortress (large bunker) | solid |
| `^` | high rock, cliff | solid, drawn as rock |
| `%` | invisible barrier | solid, not drawn: for walls and cliffs that are already in the ground picture |
| `t` / `u` / `U` | light / medium / heavy turret | shoots, 4 / 9 / 16 HP |

Solid cells **kill on contact**. The art is chosen from the level's `terrain`.

A **house, ruin, bunker or fortress** is one building however many cells you draw: neighbouring cells of the same glyph merge,
the building's hit circle is fitted to the drawn footprint (half the longer side of its bounding box), and the art is the
terrain's building that fits that size best (scaled between half and double). So **draw buildings at the size you want them**:
about 7 x 4 cells for a house, 6 x 3 for a ruin or bunker, 12 x 6 for a fortress. Walls (`#`) are separate blocks, one per cell.

### Custom glyphs

```
## Legend
| Glyph | Type  | Sprite       | HP | Score | Gun                       |
|-------|-------|--------------|----|-------|---------------------------|
| Q     | enemy | fleet:weaver | 12 | 300   | ring 8 orb every 2        |
```

* `Type`: `enemy`, `structure` or `pickup`. `Sprite` is a `card:row` reference from Resources (empty = built-in art).
* `Gun`: `aimed <count> <arc-radians> <orb|shell|cannonball>` or `ring <count> <orb|shell|cannonball>`, then
  `every <seconds>` (0.5 to 10). Leave empty for no gun.
* Limits: `HP` 1-60, `Score` 0-1000, so a level can't break the leaderboard.
* A custom glyph can reuse a letter only if it isn't built in for that grid kind, or it overrides it for the whole level.

## Waves

One `###` heading per wave, then an optional list of attributes, then a ```` ```wave ```` grid.

````
## Waves

### Wave 1 @ 3s
- path: down speed=70
```wave
..................d.................
................d...d...............
..............d.......d.............
```

### Wave 2 @ 11s
- path: sine x=-40 amp=90 freq=2 speed=100
- gap: 0.24s
- mirror: yes
```wave
dddddddddddd
```
````

A wave is **a formation** (no `gap`) or **a stream** (`gap`):

* **Formation**: every cell spawns together just above the top edge, keeping its position relative to the others, and
  the whole group follows `path`. Lines are 20 units apart; the bottom line enters first.
* **Stream**: the cells are spawned **one by one in reading order**, one every `gap`, all at the start of `path`. The
  glyph gives the type; its column does not matter, so use a single row. This is how snakes of drifters work.

Attributes (all optional except `path`, which defaults to `down speed=60`):

| Attribute | Meaning |
|---|---|
| `path:` | how it moves (below) |
| `gap:` | stream spacing in seconds (0.1 to 2) |
| `mirror: yes` | also spawn a left-right mirrored copy of a stream (twin snake) |
| `hp x1.5` | HP multiplier (0.5 to 3) |
| `fire x0.5` | fire-rate multiplier (0.25 to 3); smaller shoots less |
| `stagger: 0.3s` | for divers: each one hangs 0.3 s longer than the last, in reading order (default 0.3) |

### Paths

`path: <kind> key=value ...`. Missing keys use the defaults shown. **speed** is world units per second.

| Path | Keys (default) | Motion |
|---|---|---|
| `down` | `speed=60 amp=0 freq=1 phase=0 hold= for=0 exit=120` | Descends, optionally swaying `amp` units side to side. With `hold=<y>` it stops at that depth, hovers `for` seconds, then leaves downward at `exit`. |
| `sine` | `x=0 amp=80 freq=2 speed=100` | A stream path: starts at `x`, weaves while descending. |
| `sweep` | `dir=right y=200 amp=40 freq=2 speed=140` | Crosses the screen sideways (`dir` = `left` or `right`), undulating, drifting down slowly. |
| `loop` | `x=-40 y=120 radius=55 speed=160` | Dives, flies one loop-the-loop at depth `y`, then leaves downward. |
| `zigzag` | `x=0 amp=100 period=1.6 speed=100` | Descends in sharp zigzags. |
| `drive` | `x=0 y=0 dx=0 dy=-10` | A vehicle starting at (`x`,`y`) driving at (`dx`,`dy`) **relative to the ground**, which itself scrolls down. Use with `T H S Z`. |
| `dive` | `hover=210` | For `v`: descends to depth `hover`, telegraphs, then dives at the player. |

Divers can be placed anywhere across the grid; `stagger` sets how much longer each one hangs than the last, in reading order.

How the game plays a wave:

* A **formation** keeps its shape only on a `down` path (and on `drive` / `dive`, which have their own movement). On
  `sine`, `sweep`, `loop` or `zigzag` it is played as a stream, one ship every 0.12 s in reading order (the validator warns).
* With `drive`, `x` and `y` are where the **first cell** starts; the other cells keep their offsets from it, so a single
  `T` needs no particular column.
* With `down` and `hold=`, every cell of a formation holds at the same depth **plus its own height in the grid**, so a V keeps its shape.
* Gunners (`g`), elites (`E`) and carriers (`C`) use `hold` and `for` of a `down` path (default: depth 150, 6 s or 14 s) and
  ignore `speed`, `amp` and `exit`.
* `hp x` multiplies the HP of the wave's ships, `fire x` their rate of fire. The player's difficulty setting scales both again.
* A power-up in a `pickups` grid is dropped by the ship under it when that ship is **shot down** (not when it leaves).

### Pickups

```` ```pickups ```` with the same grid rules, placed over a wave's cells. A letter drops that power-up **when the enemy
underneath dies** (`W`eapon, `S`hield, `L`ife, `B`omb, `R`apid, `V`ulcan, `E` beam, `H`oming, `U` support wingman) or `*`
for a random one. It must sit over an enemy cell.

## Ground

````
## Ground

### Rocks @ 14s
```ground
.....#####.............RRR.........
.....#####.............RRR.........
.....#####......t...................
```
````

`@ 14s` is when the grid's **bottom line enters the top of the screen**, so it is also the grid's earliest moment. Several ground grids may be used and may overlap in
time. The validator keeps a route open: any block of solid cells must leave a passage **at least 3 columns wide** (a
ship plus margin) that connects row to row, shifting at most 2 columns per row.

## Boss

```
## Boss
- name: THE GLACIER WYRM
- style: curtain          # fans | spiral | curtain | sweeper | summoner | final
- hp: 320                 # 100 to 1200
- sprite: fleet:boss      # a card:row reference, or omit for the built-in hull
- radius: 60              # optional, 20 to 140: radius of the boss's hit circle
- arrives: @ 65s          # or "after-waves" (10 s after the last wave starts)
```

The hull grid is centred on the boss sprite, **1 cell = 10 x 20 units**:

````
```hull
.....P.....
....###....
..#######..
...#####...
....#.#....
```
````

* `#` is hull, `P` a destroyable weapon pod. Up to **3 pods**.
* The boss is hit inside a **circle**. Its radius is `radius:` if given, else 45% of the hull's longer side (60 with no hull).
  Draw the hull to match what the player sees and set `radius` if the default feels unfair.
* Each pod has `5%` of the boss's HP (minimum 6). When a pod is destroyed it reveals a **weak point** at the same
  spot (extra damage), slows the boss's fire, and triggers the break-up effects. There is no separate weak-point glyph.
* The official bosses place their pods at the left and right flanks and one low on the belly.

## Background (draft 0.3)

By default the ground is drawn from the level's `terrain`. A level can supply its own ground image and overlays. They are
**links like every other resource**; nothing is embedded in the file.

```
## Resources
- ice-tile: https://example.com/art/ice.png  [tile size=256x256]
- fog:      https://example.com/art/fog.png  [layer]

## Background
- ground: ice-tile                  # replaces the terrain's own ground, from the start
- change: @ 40s ground=lava-tile fade=3s    # from 40 s the ground is another tile, cross-fading over 3 s
- change: @ 80s ground=terrain:desert       # or one of the game's own grounds
- layer: fog speed=0.3 alpha=0.5    # up to 2 overlays above the ground
```

* `tile`: a PNG that **repeats seamlessly**, square, 64 to 512 pixels. It scrolls at the level's `scroll` speed. The
  validator checks the declared `size=`; the editor checks the seams in the image itself.
* **Several tiles in one ground:** `ground:` and `change: ground=` accept up to 6 tile names separated by commas, for
  example `ground: ice-a,ice-b,ice-c`. They are laid out so that neighbouring repeats differ and the ground does not look like
  one picture repeating. `mix=cycle` (default) takes turns; `mix=random` scatters them. Tiles of one ground should be the same
  size (they are drawn at the size of the first; the validator warns otherwise).
* **Laid out upward.** The tiles of a ground are laid upward in the order listed, starting at the bottom of the screen the
  level begins with (for a later section: at the moment it starts) and repeating after the last. A tile as wide as the field
  (360 px) has no neighbour to its side, so a **long level picture** can be cut into 360 x 360 tiles and listed in order: the
  ground then scrolls through the whole picture once. The editor does this with *Import a tall level picture*.
* `change`: stacks grounds along the level so one level can run through several landscapes. `@ <seconds>` is when the
  new ground starts to appear at the top of the screen; `ground=` is a `[tile]` resource or `terrain:<name>` (one of the
  game's eight); `fade=` is the cross-fade in seconds (0 to 10, default 2). Up to 8 changes; the lines may be in any
  order (the editor writes them by time).
* `layer`: a PNG with transparency, tiled vertically across the 360-unit field. `speed` is a factor of the scroll speed
  (0 to 1.5, default 1) and `alpha` its opacity (0 to 1, default 1).
* A background is never part of play: it has no collision and is drawn below everything the player interacts with.
* Same file rules as other images: https, PNG, 512 KB at most.
* **Status:** the validator, the editor and the game (desktop, pictures next to the level file) all draw it.

## Limits

| Item | Limit |
|---|---|
| File size | 64 KB |
| Grid width | 36 columns |
| Waves | 200 |
| Enemies in one formation | 60 |
| Enemies that can be alive at once | 80 (estimated, a warning) |
| Resources | 16 links |
| Level length | 30 s to 6 min (last wave or boss time) |

## The validator

```
shmup-level check path/to/level.md          # errors, warnings and a summary
shmup-level preview path/to/level.md        # also prints every grid
shmup-level compile path/to/level.md        # checks, then lists what the game will spawn and when
```

It checks every rule in this document **offline** (syntax, glyphs, limits, spritecard references against the declared
grid, passable ground routes). Fetching the links and verifying `sha256` is the next step and is not in this version.
The game will run the same checks when it loads a level, so a level that passes the validator plays.

## The official levels are a snapshot

The original game generates its waves randomly at run time. The files in `official/` are produced by
`tools/export_levels.py`, which replays the game's wave rules with a **fixed seed** per level and writes the result as
a level file. They have the same kinds of enemies, timings and difficulty ramp as the live game, but they are one
fixed sample, not a transcript of any real run. Players can edit them like any other level.

## Not in version 0.2

Scripting, custom bullet patterns beyond `aimed` and `ring`, multiple bosses, branching levels, custom player ships,
and checking the links themselves. Each can be added later as new keys; unknown keys are ignored.
