# UGE — specifikacija jezika opisa igre (engine V1.26)

Ovaj tekst se predaje jezičkom modelu **pre** opisa igre. Model iz njega zna ceo jezik: šta je
`nivo.js`, koji modovi postoje, koja polja i vrednosti engine prepoznaje i šta znače. Na tekstualni opis
igre model odgovara fajlom **`nivo.js`** (`window.NIVO = { ... };`, JSON opis nove igre) i slikama koje
igra koristi. Engine se ne menja: svaka igra je folder `index.html` + `nivo.js` + `engine_v1.26.js` + `assets/`.

**Kako čitati:** deo 1 važi za sve igre i za modove `topdown` i `platformer`; delovi 2 i 3 su dodaci za
`topdown` (borba, atributi, uslovi; trka); delovi 4–6 su posebni modovi. Za igru u jednom modu dovoljni su
deo 1 i deo tog moda.

## Sadržaj

1. Zajednički deo i modovi `topdown` i `platformer` (`v1.13.md`)
2. Magije, stanja, atributi i uslovi (uz `topdown`) (`arena.md`)
3. Trka: staza i auto u modu `topdown` (`racing.md`)
4. Mod `flash` (`flash.md`)
5. Mod `grid` (`grid.md`)
6. Mod `quiz` (`quiz.md`)


---

# 1. Zajednički deo i modovi `topdown` i `platformer`

> Izvor: `v1.13.md`

## v1.13 — UGE 1.13 (Universal Game Engine): uputstvo za jezički model koji pravi nivo

> **Kome je namenjeno:** osobi koja pravi igru i jezičkom modelu (GPT, Claude…) koji od opisa igre
> treba da napravi **`nivo.js`** (opis igre) i **slike** tako da igra **radi iz prve**.
> Model treba da pročita ceo dokument pre pisanja JSON-a i da na kraju prođe kroz
> [Spisak provera](#12-spisak-provera-pre-predaje) iz sekcije 12.

---

### 1. Kako UGE radi (u tri rečenice)

1. **Engine** (`engine_v1.26.js`) je jedan JavaScript fajl koji je isti za sve igre i nikad se ne menja.
2. **Igra** je jedan JSON dokument (nivo) u fajlu `nivo.js`, koji opisuje svet, junaka, ukrase, prepreke, ostale likove i pravila.
3. **Slike** su obični `.svg` ili `.png` fajlovi u folderu `assets/`, na koje JSON pokazuje putanjom.

Igra **nema nijednu liniju koda**. Sve što igra radi bira se iz zatvorenog spiska vrednosti
opisanog u ovom dokumentu. Polje ili vrednost koja nije ovde opisana **ne postoji** — engine je ignoriše.

---

### 2. Folder igre i pokretanje

```
moja_igra/
  index.html         ← uvek isti, prepiši ga (menja se samo naslov)
  nivo.js            ← opis igre: window.NIVO = { ...JSON... };
  engine_v1.26.js     ← kopija engine-a, ista za sve igre
  assets/
    junak.svg
    pod.svg
    ...
```

**`index.html` — prepiši tačno ovako** (promeni samo `<title>`):

```html
<!doctype html>
<html lang="sr">
<head>
  <meta charset="utf-8">
  <title>Moja igra</title>
  <style>
    html, body { margin: 0; background: #222; display: flex; align-items: center; justify-content: center; height: 100%; }
    canvas { box-shadow: 0 0 20px rgba(0,0,0,0.5); }
  </style>
</head>
<body>
  <canvas id="game"></canvas>
  <script src="nivo.js"></script>
  <script src="engine_v1.26.js"></script>
</body>
</html>
```

**`nivo.js`** — ceo JSON nivoa, sa `window.NIVO = ` ispred i `;` iza:

```js
window.NIVO = {
  "meta": { "title": "Moja igra", "formatVersion": "1.13" },
  "world": { "mode": "topdown", "width": 960, "height": 540 },
  ...
};
```

- Igra se pokreće **duplim klikom na `index.html`** — ne treba server, internet ni bilo kakav alat.
- Izmena igre: promeni `nivo.js`, snimi, **osveži stranicu** (F5).
- Redosled u `index.html` je obavezan: **prvo `nivo.js`, pa engine**. `id="game"` na platnu je obavezan.
- Zašto `.js`, a ne `.json`: pregledač ne dozvoljava stranici otvorenoj sa diska da pročita
  `.json` fajl, ali dozvoljava da učita `.js` fajl. Zato je JSON „upakovan" u jedan red JavaScript-a.
- Ekran igre je uvek **960 × 540** piksela.
- Folder je samostalan: može se zipovati i poneti; engine se ne traži van foldera.

---

### 3. Koordinate i veličine — najvažnije pravilo

- Koordinatni početak `(0, 0)` je **gornji levi ugao sveta**. `x` raste udesno, **`y` raste nadole**.
- Sve dužine su u **pikselima**, brzine u **pikselima u sekundi**, vremena u **sekundama**, uglovi u **stepenima**.
- **`x`, `y` junaka, likova i ukrasa = CENTAR njihove slike.**
- **`x`, `y` prepreka (`colliders`) = GORNJI LEVI UGAO pravougaonika.**
- Svet (`world.width × world.height`) može biti veći od ekrana; kamera prati junaka.
  Preporuka: svet bar 960 × 540, tipično 1600–3000 × 900–1600.
  **Arena** (borba sa bosovima, ceo teren uvek na ekranu): svet tačno 960 × 540.
- **Slika se crta u svojoj prirodnoj veličini.** Za SVG to su atributi `width` i `height`.
  Likovi i junak **ne mogu** da se uvećaju iz JSON-a — veličinu određuje sama slika
  (izuzetak: lik sa `shrink` se smanjuje kako gubi život, vidi 5.7.4).
  Samo ukrasi imaju `scale`. Preporučene veličine: junak 48–90 px, likovi 30–100 px,
  predmeti 24–48 px, boss do 180 px.

---

### 4. Struktura JSON-a — pregled

```json
{
  "meta":        { ... },
  "intro":       { ... },   // uputstvo na početku
  "world":       { ... },   // OBAVEZNO
  "background":  { ... },
  "player":      { ... },   // OBAVEZNO
  "decorations": [ ... ],
  "colliders":   [ ... ],
  "entities":    [ ... ],
  "rules":       { ... },
  "ui":          { ... }
}
```

Ovaj objekat stoji u `nivo.js` posle `window.NIVO = ` (i završava se sa `;`).
JSON mora biti **strogo ispravan**: bez komentara (`//`), bez zareza posle poslednjeg elementa,
navodnici samo `"`. (Komentari u primerima ove dokumentacije služe samo za objašnjenje.)

---

### 5. Sekcije, polje po polje

Oznaka **(obavezno)** znači da bez tog polja igra ne radi. Ostala polja imaju podrazumevanu vrednost (u zagradi).

#### 5.1 `meta`

| Polje | Opis |
|---|---|
| `title` | naslov igre |
| `author` | autor |
| `formatVersion` | upisati `"1.13"` |

#### 5.1a `intro` — uputstvo na početku igre

```json
"intro": { "title": "Sila — kako se igra", "formula": "F = m · a",
           "text": "Kretanje: strelice ← ↑ → ↓\nPucanje: klik mišem ili Space\n\n1. Pobedi masu m...\n\nSkupi dragulje m, a i F." }
```

Prozor se otvori **čim se igra pokrene**; igra stoji dok ga igrač ne zatvori (klik, Enter, Space, Esc).
Polja su ista kao kod poruke na dodir (5.8): `title`, `formula`, `text`. U tekst stavi: **kako se kreće**,
**kako se puca**, **koga treba pobediti / šta skupiti** i redom faze. Do ~9 redova.

#### 5.2 `world` (obavezno)

| Polje | Vrednosti | Opis |
|---|---|---|
| `mode` **(obavezno)** | `"topdown"` ili `"platformer"` | `topdown` = pogled odozgo, kretanje u 8 pravaca. `platformer` = pogled sa strane, gravitacija i skok. **Svaka druga vrednost se tretira kao platformer.** |
| `width`, `height` **(obavezno)** | broj | veličina sveta u pikselima |
| `gravity` | (1200) | samo za `platformer` |

#### 5.3 `background`

| Polje | Opis |
|---|---|
| `asset` | slika koja se **ponavlja** preko celog sveta (pločica, npr. 128×128 trava) |
| `layers` | umesto `asset`: niz `{ "asset": "...", "scrollFactor": 0.3 }` — slojevi koji se pomeraju sporije od kamere (paralaksa); `0` = nepomično nebo, `1` = prati kameru |

Pozadina se **uvek ponavlja** (polje `tile` ne utiče ni na šta). Pločica treba da bude bešavna.

#### 5.4 `player` — junak (obavezno)

| Polje | Podrazumevano | Opis |
|---|---|---|
| `asset` **(obavezno)** | — | slika junaka, **nacrtana okrenuta udesno** |
| `start` **(obavezno)** | — | `{ "x": 200, "y": 300 }` početni centar junaka; **ne sme biti unutar prepreke** |
| `speed` **(obavezno)** | — | brzina, tipično 160–260 |
| `collider` | krug r=10 | telo za sudare (vidi 5.9) |
| `controls` | strelice | tasteri, vidi tabelu ispod |
| `jumpSpeed` | 500 | jačina skoka (samo `platformer`) |
| `health` | nema | broj srca (1–10). **Bez ovog polja junak nema srca, a udarci oduzimaju poene.** |
| `invulnerable` | 1 | sekunde neranjivosti posle udarca (junak trepće) |
| `attack` | nema | oružje junaka (vidi ispod); bez njega junak ne puca |
| `pointer` | nema | kretanje mišem/dodirom (vidi 5.4.2); bez njega klik ne radi ništa |
| `label` | nema | natpis iznad junaka (vidi 5.10) |
| `shadow` | nema | `{ "enabled": true, "offsetY": 20, "opacity": 0.3 }` senka ispod |

**`controls`** — vrednosti su nazivi tastera po standardu `KeyboardEvent.code`:

| Polje | Podrazumevano | Primeri drugih vrednosti |
|---|---|---|
| `up`, `down`, `left`, `right` | `ArrowUp`, `ArrowDown`, `ArrowLeft`, `ArrowRight` | `KeyW`, `KeyS`, `KeyA`, `KeyD` |
| `jump` (platformer) | `Space` | `ArrowUp`, `KeyW` |
| `attack` | `KeyX` | `Space`, `KeyJ`, `Enter` |

U `platformer`-u **ne stavljati isti taster za skok i napad**.

**Više tastera za isto (V1.4):** umesto jednog naziva može se upisati niz — radi bilo koji od njih.
Preporuka za sve igre — i strelice i WASD:

```json
"controls": { "up": ["ArrowUp", "KeyW"], "down": ["ArrowDown", "KeyS"],
              "left": ["ArrowLeft", "KeyA"], "right": ["ArrowRight", "KeyD"], "attack": "Space" }
```

##### 5.4.1 `attack` — oružje junaka

```json
"attack": {
  "cooldown": 0.35,
  "projectile": { "asset": "assets/magija.svg", "speed": 450, "damage": 1, "radius": 9, "lifetime": 1.0 }
}
```

| Polje | Podrazumevano | Opis |
|---|---|---|
| `cooldown` | 0.4 | sekunde između hitaca; držanje tastera = automatska paljba |
| `aim` | nema | `"mouse"` = **pucanje mišem**: levi klik (ili držanje) puca ka strelici miša; umesto strelice se crta nišan, junak se okreće ka cilju. Tada se junak kreće samo tastaturom (`pointer` ne stavljati) |
| `projectile.asset` **(obavezno)** | — | slika hica, **nacrtana okrenuta udesno** (engine je rotira u pravcu leta) |
| `projectile.speed` | 300 | brzina hica |
| `projectile.damage` | 1 | koliko života skida |
| `projectile.radius` | 8 | veličina pogotka |
| `projectile.lifetime` | 1.5 | sekunde leta → domet = `speed × lifetime` |

Taster za napad puca u pravcu u kom se junak poslednji put kretao (8 pravaca na tastaturi,
bilo koji ugao pri kretanju mišem). Sa `"aim": "mouse"` klik puca pod bilo kojim uglom ka mišu, a taster i dalje radi.
Hitac leti pravo, staje na prepreci i pogađa **samo likove koji imaju `health`** (ili `damageTo`, 5.7.5).

**Mač iz blizine (V1.11)** — umesto (ili pored) `projectile`:

```json
"attack": { "cooldown": 0.35,
  "melee": { "range": 80, "height": 64, "damage": 1, "duration": 0.18, "pose": "assets/junak_udarac.svg" } }
```

| Polje | Podraz. | Opis |
|---|---|---|
| `range` | 60 | koliko daleko ispred junaka seče |
| `height` | 50 | visina pogođene zone (sa strane); odozgo je zona kvadrat `range × range` u pravcu gledanja |
| `damage` | 1 | šteta |
| `duration` | 0.18 | koliko traje zamah; svaki lik je pogođen najviše jednom po zamahu |
| `pose` | nema | slika junaka dok zamahuje (npr. ispružen mač); ista veličina kao obična slika |
| `color` | `#e3f2fd` | boja luka zamaha |

Zamah se crta kao svetao luk ispred junaka. Udarac u oklopljen lik (`armored`) daje iskru i zvuk `block`.

##### 5.4.2 `pointer` — kretanje mišem ili prstom

```json
"pointer": { "move": "path" }
```

| Polje | Podrazumevano | Opis |
|---|---|---|
| `move` | nema | `"path"` = junak **obilazi** prepreke (traži najkraći put). `"stop"` = ide **pravo**, uz prepreku klizi ili staje. |
| `trail` | true | trag do cilja: zelene strelice i krstić na mestu klika |
| `trailColor` | `"#4cff4c"` | boja traga |
| `cell` | 16 | preciznost traženja puta (manje = preciznije, sporije); ne dirati bez razloga |

Tastatura i dalje radi i ima prednost. U `platformer`-u klik bira samo cilj levo-desno.

#### 5.5 `decorations` — ukrasi (niz)

Slike bez tela: ne zaustavljaju junaka ni hice, nemaju akciju.

```json
{ "asset": "assets/drvo.svg", "x": 400, "y": 300, "scale": 1, "rotation": 0, "depth": true }
```

| Polje | Podrazumevano | Opis |
|---|---|---|
| `asset`, `x`, `y` **(obavezno)** | — | slika i centar |
| `scale` | 1 | uvećanje |
| `rotation` | 0 | okret u stepenima |
| `depth` | false | `false` = leži na zemlji, uvek ispod svega (put, cveće, mrlje). `true` = ima visinu: junak može da prođe **iza** (drvo, planina, kuća, zamak) — samo u `topdown` |
| `label` | nema | natpis iznad (vidi 5.10) |

**Ukras koji treba da zaustavi junaka** (drvo, stena, kuća) = ukras + pravougaonik u `colliders` na istom mestu (vidi 5.6).

#### 5.6 `colliders` — nevidljive prepreke (niz)

```json
{ "type": "rect", "x": 380, "y": 330, "w": 40, "h": 24 }
```

- **`x`, `y` = gornji levi ugao**, `w`, `h` = širina i visina. Samo pravougaonici.
- U `topdown`: zaustavljaju junaka (klizi uz njih).
- U `platformer` (V1.11): prepreke su **čvrste sa svih strana** — pod, zid, plafon. `"oneWay": true` = platforma
  na koju se staje samo odozgo (kroz nju se skače odozdo). Arena sa strane: pod `{ "x": 0, "y": 470, "w": 960, "h": 70 }`
  + levi i desni zid.
- **Zaustavljaju sve hice.**
- **Ostali likovi prolaze kroz prepreke** (i jure kroz zidove).
- Za drvo u pogledu odozgo kolajder stavi **samo oko stabla** (donji deo slike), ne oko krošnje.
  Primer: drvo 80×100 sa centrom (x, y) → `{ "x": x-16, "y": y+30, "w": 32, "h": 22 }`.
- Za stenu/planinu: pokri donje dve trećine slike.

#### 5.7 `entities` — ostali likovi i predmeti (niz)

Sve što nije junak, a nešto radi: protivnici, saigrači, zlato, kosti, napici, objekti koji prikazuju poruku.

```json
{
  "id": "goblin1",
  "asset": "assets/goblin.svg",
  "x": 900, "y": 480,
  "collider": { "type": "circle", "radius": 16 },
  "behavior": { "type": "chase", "speed": 105, "detectRadius": 240 },
  "health": 1,
  "onCollide": { "action": "damage", "amount": 1, "cooldown": 1 },
  "onDeath": { "score": 50 }
}
```

| Polje | Podrazumevano | Opis |
|---|---|---|
| `id` | nema | jedinstveno ime. **Prefiks id-ja služi za grupe u pravilima pobede** (`zvezda1`, `zvezda2` → grupa `zvezda`) |
| `asset`, `x`, `y` **(obavezno)** | — | slika (**okrenuta udesno**) i početni centar |
| `collider` | krug r=10 | telo za dodir (vidi 5.9) |
| `behavior` | `static` | kretanje (5.7.1) |
| `onCollide` | `none` | šta se desi kad ga junak dodirne (5.7.2) |
| `health` | nema | broj pogodaka koje izdrži. **Bez `health` hici junaka prolaze kroz lik** — tako treba za zlato, saigrače, kamenje |
| `attack` | nema | pucanje na daljinu (5.7.3) |
| `onDeath` | nema | `{ "score": 100 }` poeni kad ga junak pobedi |
| `label` | nema | natpis iznad (5.10) |
| `shadow` | nema | senka ispod |
| `spawnAfter` | nema | lik **čeka** i pojavi se tek kad nestanu svi likovi čiji id počinje ovim tekstom (5.7.4) |
| `spawn` | nema | gde se pojavljuje: `{ "at": "far" }` ili `{ "at": "<id>" }`, plus `delay`, `margin` (5.7.4) |
| `lives` | 1 | koliko puta mora biti pobeđen; posle svakog poraza nestane i vrati se pun (5.7.4) |
| `respawnDelay` | 1 | sekunde između gubitka života i ponovnog pojavljivanja |
| `shrink` | nema | `{ "minScale": 0.35 }` — slika i telo se smanjuju sa izgubljenim životom |
| `loseMessage` | nema | poruka poraza kad **baš ovaj lik** obori junaka (5.11) |

Lik sa `health` većim od 1 automatski dobija **traku života** iznad glave.
Slika se sama okreće u pravcu kretanja; lik koji puca gleda ka junaku.

##### 5.7.1 `behavior` — kako se lik kreće (tačno jedno)

| `type` | Parametri (podrazumevano) | Šta radi |
|---|---|---|
| `static` | — | stoji |
| `wander` | `speed`, `radius` (50) | luta nasumično u kvadratu ±`radius` oko početne tačke |
| `patrol` | `speed`, `path` | obilazi tačke `[{ "x":…, "y":… }, …]` redom, u krug (posle poslednje ide na prvu) |
| `chase` | `speed`, `detectRadius` (150) | juri junaka kad mu je bliže od `detectRadius` |
| `flee` | `speed`, `detectRadius` (150) | beži od junaka kad mu je bliže od `detectRadius` |
| `zigzag` | `speed`, `turnEvery` (0.7), `margin` (40), `area` | ide pravo, pa na oko `turnEvery` sekundi naglo skrene (60–120°) naizmenično levo-desno — **cik-cak po celom svetu**; odbija se od ivica |
| `charge` | `speed`, `acceleration` (600), `windup` (0.8), `startSpeed` (0), `steer` (0), `margin` (40), `area` | **zaletanje sa ubrzanjem**: stoji `windup` sekundi i trepće (nišani), pa krene ka junaku i ubrzava (`acceleration` px/s²) do `speed`, sve dok ne udari u ivicu; onda opet nišani. `steer` (radijani/s) > 0 = skreće za junakom dok juri (1–2 = prati, 0 = ide pravo pa se može izmaći) |

`area` = `{ "x": 0, "y": 0, "w": 960, "h": 540 }` — oblast kretanja; bez nje ceo svet. `margin` = koliko daleko od ivice oblasti lik staje.
Za `charge`: `speed` je **najveća** brzina (tipično 400–750), a `acceleration` koliko brzo je dostiže (350–1100).
Igrač ga izbegava tako što se skloni dok lik trepće.

| `script` (V1.12) | `intro`, `phases`, `phaseBy`, `steps` | **boss sa šablonom napada** — vidi 5.7.5 |

**`speed` nema podrazumevanu vrednost za kretanje — bez njega se lik ne pomera.**
Tipične brzine: spor 30–60, normalan 80–130, brz 150–200. **Lik koji juri treba da bude sporiji od junaka**, inače mu se ne može pobeći.
Prva tačka `patrol` putanje treba da bude početni `x, y` lika.

##### 5.7.2 `onCollide` — šta se desi na dodir sa junakom

| `action` | Parametri (podrazumevano) | Šta radi |
|---|---|---|
| `collect` | `score`, `remove` | daje poene. **Uvek stavi `"remove": true`** — inače daje poene 60 puta u sekundi |
| `damage` | `amount` (1), `cooldown` (1) | skida srca (ili poene ako junak nema `health`); isti lik ponovo udara tek posle `cooldown` |
| `heal` | `amount` (1), `remove` | vraća srca do maksimuma; ako je junak pun, predmet ostaje |
| `message` | `title`, `formula`, `text`, `once` (false) | otvara prozor sa porukom — tekst, uputstvo, formula (vidi 5.8) |
| `win` | `message` | odmah pobeda sa porukom |
| `none` | — | ništa (saigrač, ukras koji se dodiruje) |

Dodatna polja uz bilo koju akciju:

| Polje | Opis |
|---|---|
| `hud: true` (uz `collect`) | skupljeni predmet ostaje prikazan **gore desno** (dragulji, ključevi, slova formule) |
| `player` | menja junaka pri dodiru — isto kao `onDeath.player` (5.7.4) |
| `loseMessage` (uz `damage`) | poruka poraza ako ovaj udarac obori junaka |
| `shake` (uz `damage`, V1.5) | udarac **drma junaka**: veličina mu ide gore-dole (1 ± `amount`), a dok traje ne može da se kreće (puca može). `true` ili `{ "duration": 0.9, "amount": 0.1, "speed": 14 }` — sekunde, ±10 %, treptaja u sekundi. Za struju, munju, eksploziju |
| `tag` (uz `damage`, V1.8) | vrsta udarca, npr. `"struja"`. Junak sa `player.resist` koji sadrži tu reč ne gubi srce i ne trese se — protivnik samo odskoči (`recoil`) |
| `recoil` (uz `damage`, V1.5) | protivnik posle udarca **odskoči** od junaka toliko piksela (npr. `220`); lik sa `charge` zatim ponovo nišani. Bez toga protivnik ostaje zalepljen uz junaka |

Junak sa `player.resist: ["shake"]` (5.7.4) se ne trese.

Primer — struja koja protrese i odskoči:

```json
"onCollide": { "action": "damage", "amount": 1, "cooldown": 1.2,
               "shake": { "duration": 0.9, "amount": 0.1 }, "recoil": 220 }
```

Polja `title`, `formula`, `text` mogu se dodati i uz `collect`, `heal`… — tada se pri dodiru
izvrši akcija **i** otvori prozor (npr. skupljeni svitak sa tekstom).

##### 5.7.3 `attack` — lik koji puca

```json
"attack": {
  "range": 380, "interval": 2.0, "count": 3, "spread": 30,
  "projectile": { "asset": "assets/vatra.svg", "speed": 240, "damage": 1, "radius": 10, "lifetime": 2 }
}
```

| Polje | Podrazumevano | Opis |
|---|---|---|
| `range` | 300 | puca samo kad je junak bliže od ovoga |
| `interval` | 2 | sekunde između napada |
| `count` | 1 | broj hitaca odjednom |
| `spread` | 0 | ukupna širina lepeze u stepenima (3 hica u 30° = vatra zmaja) |
| `projectile` | — | isto kao kod junaka (5.4.1); `asset` je obavezan. Može imati i `loseMessage` (npr. „Kamenje te je pobedilo.") |
| `lead` | 0 | nišanjenje **unapred**: 0 = gađa gde je junak sad, 1 = gde će biti kad hitac stigne (0.5–0.8 = „nezgodno baca") |
| `projectile.tag` (V1.7) | vrsta hica, npr. `"munja"`. Junak sa `player.resist` koji sadrži tu reč (`["munja"]`) je otporan: hitac se na njemu ugasi bez štete. Npr. izolacione rukavice štite od munja |

Nišani tamo gde je junak **sada**. Lik može da puca uz bilo koje `behavior` (i `static` i `patrol`).
Hici protivnika ne povređuju druge protivnike i staju na preprekama.

##### 5.7.4 Bosovi u fazama, životi, nagrade

Za igre gde se protivnici smenjuju (prvo jedan, pa kad padne — drugi), na primer
**masa → ubrzanje → sila**, svi likovi se upišu u `entities` od početka, a redosled se zadaje sa `spawnAfter`.

| Polje | Opis |
|---|---|
| `spawnAfter: "masa"` | lik je skriven i pojavi se kad **nestanu** (pobeđeni ili skupljeni) svi likovi čiji `id` počinje sa `"masa"` |
| `spawn.at: "far"` | pojavi se u vidnom polju, **što dalje od junaka** |
| `spawn.at: "<id>"` | pojavi se **na mestu gde je taj lik nestao** — tako boss „ostavlja" nagradu (dragulj) |
| `spawn.delay` | sekunde čekanja pre pojavljivanja (0.8–1 = igrač stigne da se pripremi) |
| `spawnAfterTalk: "majstor2"` (V1.9) | pojavi se tek kad je junak **pročitao poruku** lika čiji id počinje tim tekstom (savet → predmet). Može zajedno sa `spawnAfter` |
| `despawnAfter: "struja"` (V1.9) | lik **tiho nestane** kad nestanu svi likovi te grupe — npr. zaključan prekidač, zatvorena vrata, stražar koji ode. Ne računa se kao pobeđen |
| `spawn.margin` | koliko daleko od ivice ekrana (60) |
| `lives: 4` | lik se pobeđuje 4 puta: kad mu `health` padne na 0, nestane i posle `respawnDelay` se vrati pun (sa `spawn.at: "far"` — na novom mestu daleko od junaka). Ispod trake života su žute tačkice = preostali životi |
| `shrink: { "minScale": 0.35 }` | sa svakim pogotkom lik je manji: pun život = prirodna veličina, poslednji pogodak = 35 %. Telo za sudare se smanjuje zajedno sa slikom |
| `onDeath.player` | nagrada kad je lik **konačno** pobeđen (posle poslednjeg života) — menja junaka |

`onDeath.player` (i `onCollide.player`):

```json
"onDeath": { "player": { "health": "full",
                         "projectile": { "asset": "assets/hitac_jak.svg", "damage": 5, "radius": 13 },
                         "cooldown": 0.2 } }
```

| Polje | Opis |
|---|---|
| `health` | `"full"` = sva srca vraćena; broj = toliko srca dodato |
| `projectile` | nova svojstva hica junaka; navedena polja zamenjuju stara (ostala ostaju) — „5 puta jači hici" = `damage: 5` |
| `cooldown` | novo vreme između hitaca |
| `canShoot` (V1.10) | `false` = junak **gubi pucanje** (nišan nestaje, vidi se obična strelica miša); `true` = pucanje se vraća. Npr. boss pri padu „pregori alat" (`onDeath.player.canShoot: false`), a predmet ga vrati (`collect` sa `player.canShoot: true`) |
| `shield` (V1.8) | vidljiv **štit** oko junaka (providan plavi mehur koji pulsira): `true` ili `{ "color": "#40c4ff", "radius": 40 }`. Samo izgled — zaštitu daje `resist` |
| `resist` (V1.6, V1.7) | otpornost: `"shake"` = junak se više ne trese (udarac i dalje skida srce); bilo koja vrsta hica (`projectile.tag`, npr. `"munja"`) = ti hici mu ne mogu ništa. Npr. izolacione rukavice: `collect` sa `"player": { "resist": ["struja"], "shield": true }`, a Struja ima `projectile.tag` i `damage.tag` = `"struja"` — tada joj ništa ne može |

**Obrazac prekidača (V1.9):** na istom mestu tri lika — `zakljucan_x` (`despawnAfter: "boss"`, poruka „prvo pobedi…"),
`x` (`spawnAfter: "boss"`, `collect` + `remove`, poruka) i `upaljen_x` (`spawnAfter: "x"`, samo slika);
sve što se pali (lampe) ima `spawnAfter: "x"`. Pazi da se prefiksi ne preklapaju (`x` ne sme biti početak `x_upaljen`).

**Obrazac lanca:** `boss1` → nagrada sa `spawnAfter: "boss1"`, `spawn.at: "boss1"` → `boss2` sa
`spawnAfter: "<id nagrade>"`, `spawn.at: "far"` → … Pobeda: `collectAll` sa prefiksom zajedničkim
svim nagradama (`dragulj_m`, `dragulj_a`, `dragulj_F` → `"target": "dragulj"`).
Likovi koji još čekaju računaju se kao „nisu gotovi", pa pobeda ne dolazi prerano.

**Prefiksi id-jeva moraju biti različiti**: `spawnAfter: "sila"` bi čekao i lik `silak1`.

##### 5.7.5 Boss sa šablonom napada (V1.12)

Za bosove kao iz pravih igara (Zelda, Cuphead): boss izvodi **tačan redosled poteza** — najavi napad, udari,
zaglavi se, pa je tada ranjiv. Primer: Minos u `animacija/iskusaj/` (ogroman boss iza nivoa + dve ruke).

```json
"behavior": { "type": "script", "phaseBy": "minos",
  "intro":  [ { "wait": 3.6 }, { "y": 150, "speed": 600 } ],
  "phases": [
    { "untilHealth": 11, "speed": 1.0, "steps": [
        { "x": "player", "y": 110, "speed": 320, "time": 1.2 },
        { "wait": 0.5, "flash": true },
        { "y": 422, "speed": 1800, "time": 0.3, "quake": 0.25 },
        { "wait": 0.5, "sound": "tresak" },
        { "y": 110, "speed": 500, "time": 0.6 } ] },
    { "untilHealth": 8, "speed": 1.5, "steps": [ ... ] },
    { "steps": [ ... ] } ] }
```

- `intro` — koraci koji se izvedu **jednom**, na početku (boss se diže, prvi udarac).
- `phases` — faze po zdravlju lika `phaseBy` (bez njega: sopstveno). Faza važi **dok je zdravlje > `untilHealth`**;
  poslednja faza bez `untilHealth`. Koraci faze se ponavljaju u krug. Čim zdravlje padne ispod praga, odmah počinje sledeća faza.
- `speed` faze ubrzava sve korake (kretanje brže, čekanja i `time` kraći) — „posle toga se Minos ubrza".
- `steps` — umesto faza, jedan spisak koraka u krug.

**Korak:**

| Polje | Opis |
|---|---|
| `x` | broj = idi na tu x koordinatu; `"player"` = **prati junaka** levo-desno (uz pomak `dx`) |
| `y` | idi na tu visinu |
| `speed` | brzina kretanja (300) |
| `time` | korak traje toliko sekundi (i ako je stigao ranije). Bez `time` korak traje dok ne stigne; sa `x: "player"` `time` je obavezan |
| `wait` | stoji toliko sekundi |
| `flash` | **najava napada**: crveni sjaj iza lika koji treperi |
| `vulnerable` | u ovom koraku oklopljen lik **može da se udari** (žuti krug oko njega) |
| `harmless` | u ovom koraku dodir ne povređuje (zaglavljena ruka, pognuta glava) |
| `quake` | sekunde **podrhtavanja ekrana** na kraju koraka (udarac u zemlju); `quakeAmount` = jačina (8) |
| `say` | uzvik iznad lika dok traje korak („RRRAAAH!") |
| `asset` | druga slika dok traje korak (otvorena šaka, zaglavljena pesnica); bez toga — osnovna slika |
| `collider` | drugo telo dok traje korak (široka šaka) |
| `sound` | zvuk na početku koraka (ime iz `sounds`, 5.13) |

**Polja lika uz skriptu:**

| Polje | Opis |
|---|---|
| `armored: true` | hici i mač ne prolaze (iskra, zvuk `block`), osim u koraku sa `vulnerable` |
| `damageTo: "minos"` | pogodak u ovaj lik ide **drugom liku** — ruke su delovi Minosa, šteta ide Minosu. Lik sa `damageTo` može da se pogodi i bez sopstvenog `health` |
| `bossBar: "MINOS"` | velika traka života na vrhu ekrana sa imenom (umesto male iznad lika) |
| `onDeath.fall: true` | posle smrti lik ne nestane odmah, nego **padne** i izađe sa ekrana |
| `z` | redosled crtanja: manji = dalje. Važi i za `decorations` i za `player.z`. Npr. boss `-2`, zid sa rupom `-1`, ruke `1`, junak `2` |
| `flip: true` | slika okrenuta ulevo od početka (leva ruka = desna ruka preslikana); lik iz skripte se sam ne okreće |

**Usklađivanje dva lika** (dve ruke napadaju zajedno): koraci oba lika neka imaju `time`/`wait`, pa im je trajanje
poznato; razlika u čekanjima određuje ko kada udara. Faze oba lika vezati za istog `phaseBy`.

#### 5.8 Poruka na dodir (`"action": "message"`)

Bilo koji lik ili predmet može da prikaže poruku kad ga junak dodirne: kamen, svitak, znak pored puta,
starac, škrinja. Izgled određuje samo njegova slika; ponašanje je uvek isto. Primer ispod je kamen
mudrosti sa formulom.

```json
{
  "id": "kamen1",
  "asset": "assets/kamen_mudrosti.svg",
  "x": 600, "y": 690,
  "collider": { "type": "circle", "radius": 24 },
  "behavior": { "type": "static" },
  "label": "Kamen mudrosti",
  "onCollide": {
    "action": "message",
    "title": "Kamen mudrosti",
    "formula": "F = m · a",
    "text": "Sila je jednaka proizvodu mase i ubrzanja.\nDa bi stekao Silu, moraš da savladaš masu m i ubrzanje a."
  }
}
```

| Polje | Opis |
|---|---|
| `title` | naslov prozora |
| `formula` | jedna formula krupnim slovima ispod naslova (kratko, do ~30 znakova) |
| `text` | tekst; dugi redovi se sami prelamaju; `\n` = novi red; `\n\n` = prazan red |
| `once` | `true` = prikaži samo prvi put |

- Prozor se otvara **kad junak dodirne** predmet; dok je otvoren, **igra stoji**.
  Zatvara se klikom, Enter, Space ili Esc. Ponovo se otvara tek kad junak ode i vrati se.
- Tekst do ~8 redova. Formule pisati **Unicode znakovima**: `·` `×` `÷` `²` `³` `√` `π` `≈` `≤` `≥` `→` `Δ` `₂` (H₂O).
- Predmet sa porukom **ne sme biti unutar prepreke** — junak mora moći da ga dodirne.

#### 5.9 Telo za sudare — `collider`

| Oblik | Polja |
|---|---|
| krug | `{ "type": "circle", "radius": 16, "offsetX": 0, "offsetY": 0 }` |
| pravougaonik | `{ "type": "rect", "w": 30, "h": 40, "offsetX": 0, "offsetY": 0 }` |

Telo je centrirano na `x, y` lika, pomereno za `offsetX/offsetY`. Poluprečnik stavi na oko
**40 % manje dimenzije slike** (slika 48 px → `radius` 18–20). Za junaka koji je visok (konjanik),
`offsetY` pozitivan spušta telo ka nogama.

#### 5.10 Natpisi — `label`

Na junaku, bilo kom liku i bilo kom ukrasu:

```json
"label": "Heroj"
"label": { "text": "Crni zmaj", "color": "#ffb4a8", "size": 14, "background": true }
```

Crta se iznad slike, povrh svega. `\n` = više redova. Natpise koristiti štedljivo (junak, boss, važni predmeti).

#### 5.11 `rules` — kraj igre

```json
"rules": {
  "winCondition": { "type": "collectAll", "target": "zvezda" },
  "onWin":  { "message": "Bravo! Sve zvezde su sakupljene!" },
  "onLose": { "message": "Pokušaj ponovo..." }
}
```

| `winCondition.type` | Parametar | Pobeda kad… |
|---|---|---|
| `collectAll` | `target` | nestanu **svi** likovi čiji `id` **počinje** sa `target` |
| `defeatAll` | `target` | isto — za protivnike koje treba pobediti |
| `scoreTarget` | `value` | poeni dostignu `value` |
| `none` | — | igra se ne završava pobedom |

- `winCondition.target` može biti i **niz** prefiksa (V1.9): `["dragulj", "prekidac"]` — pobeda tek kad nestanu sve grupe.
- `onWin.delay` (V1.9) — sekunde pre ekrana pobede, da igrač vidi šta se desilo (lampe zasvetle). Npr. `2.5`.
- `onWin.formula` — formula **krupno** na ekranu pobede, ispod poruke (npr. `"F = m · a"`).
- Poraz: kad junaku (koji ima `health`) srca padnu na 0. Poruka je `loseMessage` lika (ili njegovog hica)
  koji je zadao poslednji udarac; ako ga nema — `onLose.message` (podrazumevano „Poraz!").
  Tako igrač zna šta ga je pobedilo: „Kamenje te je pobedilo." / „Ubrzanje te je pobedilo."
- Posle kraja igre taster **R** pokreće igru iznova.

#### 5.13 Zvuk i muzika (V1.13)

```json
"sounds": { "attack": "zvuci/mac.ogg", "hit": "zvuci/pogodak.ogg", "block": "zvuci/blok.ogg",
            "hurt": "zvuci/jauk.ogg", "win": "zvuci/pobeda.ogg", "lose": "zvuci/poraz.ogg",
            "rika": { "asset": "zvuci/rika.ogg", "volume": 0.6 } },
"music": { "asset": "zvuci/muzika.ogg", "volume": 0.35 }
```

| Ime | Kada se pušta |
|---|---|
| `attack` | zamah mačem |
| `hit` | pogodak u lik |
| `block` | udarac u oklop (ne prolazi) |
| `hurt` | junak izgubi srce |
| `win`, `lose` | kraj igre |
| bilo koje drugo ime | korak skripte sa `"sound": "ime"` |

- Zvuci su pravi fajlovi (`.ogg`, `.mp3`, `.wav`) u folderu igre — rade i na dupli klik. Fajl koji ne postoji samo ćuti (igra radi).
- Pregledač pušta zvuk tek **posle prvog pritiska tastera ili klika** — zato muzika kreće kad igrač zatvori uvodni prozor.
- Taster **M** uključuje i isključuje sav zvuk. Na kraju igre muzika se utiša.
- **Ne stavljati tuđe pesme** (autorska prava) — samo svoje, slobodne (CC0) ili sintetizovane.

#### 5.12 `ui`

| Polje | Podrazumevano | Opis |
|---|---|---|
| `showScore` | false | prikaži poene gore levo |
| `scoreLabel` | `"Score"` | natpis uz poene („Zlato", „Kosti"…) |

Srca se crtaju sama kad junak ima `health` (gore levo). Predmeti skupljeni sa `hud: true` crtaju se gore desno.

---

### 6. Slike (assets)

Model koji pravi igru treba da napravi i slike kao **SVG fajlove** (tekst). Pravila:

1. Koreni element **mora imati `width` i `height`** u pikselima, i `viewBox` iste veličine:
   `<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64">`.
   Bez `width`/`height` slika se crta kao 300×150 i igra izgleda pogrešno.
2. **Providna pozadina** (bez pravougaonika preko cele slike) — osim pločice za pozadinu.
3. **Likovi, junak i hici nacrtani okrenuti udesno.** Engine ih sam okreće.
4. Pozadinska pločica: kvadrat (npr. 128×128), cela popunjena, bez detalja preseječenih ivicom (da bude bešavna).
5. Ispod lika može ići blaga senka (`<ellipse … fill="#000" opacity="0.3"/>`) pri dnu slike.
6. Svaka putanja iz JSON-a (`"assets/goblin.svg"`) mora da postoji **tačno tako napisana** (mala/velika slova!).

> **Ako nedostaje i jedna slika, igra se uopšte ne pokreće** (ostaje prazan ekran).

---

### 7. Zamke koje kvare igru

| Greška | Posledica | Kako izbeći |
|---|---|---|
| nedostaje slika ili je putanja pogrešno napisana | **prazan ekran** | proveri svaku putanju iz JSON-a |
| `world.mode` pogrešno napisan (npr. `"top-down"`) | igra postaje platformer, junak pada | samo `"topdown"` ili `"platformer"` |
| nema `player.speed` | junak se ne pomera | uvek upiši `speed` |
| `start` junaka unutar prepreke | junak zaglavljen | proveri da `start` nije u nijednom `colliders` pravougaoniku |
| `collect` bez `"remove": true` | poeni se množe 60× u sekundi | uvek `"remove": true` |
| `collectAll`/`defeatAll` sa prefiksom koji niko nema | igra se ne može dobiti | bar jedan `id` mora počinjati sa `target` |
| prefiks preklapa druge id-jeve (`target: "kost"` hvata i `kostur1`) | pobeda nemoguća ili prerana | prefiksi jasno različiti |
| `defeatAll` protivnici bez `health`, ili junak bez `attack` | protivnik se ne može pobediti | meta mora imati `health`, junak `attack` |
| lik bez `behavior.speed` | lik stoji | upiši `speed` za `wander/patrol/chase/flee` |
| `chase` brži od junaka | nemoguće pobeći | `chase.speed` < `player.speed` |
| predmet ili objekat sa porukom unutar prepreke | nedostupan | drži ih bar 40 px od prepreka |
| platformer bez poda u `colliders` | junak pada do dna sveta | dodaj pod i platforme; `start` iznad poda |
| isti taster za skok i napad (platformer) | skače kad puca | različiti tasteri |
| SVG bez `width`/`height` | pogrešna veličina | uvek upiši oba |
| komentari ili zarez na kraju u JSON-u | igra ne učitava nivo | čist JSON |
| više od ~300 ukrasa sa `depth` | igra seče | do ~250 |
| `nivo.js` bez `window.NIVO = ` ili engine učitan **pre** `nivo.js` | igra se ne pokreće | tačno šablon iz sekcije 2 |
| `spawnAfter` sa prefiksom koji niko nema | lik se nikad ne pojavi | prefiks mora biti početak nečijeg `id` |
| nagrada bez `spawn.at` (ili sa `x, y` = 0) | dragulj se pojavi u gornjem levom uglu | `spawn.at` = id bosa |
| slovo ili tekst nacrtan u SVG-u lika koji se kreće | slovo se vidi **naopako** kad lik ide ulevo (slika se preslikava) | tekst iznad lika ide u `label`; slova u slici samo za predmete koji stoje (dragulji) |
| `charge` u velikom svetu | zalet traje predugo, lik odleti van ekrana | `charge` u areni 960 × 540 ili sa `area` |
| `"aim": "mouse"` zajedno sa `pointer` | klik puca, kretanje mišem ne radi | sa pucanjem mišem — kretanje strelicama |

---

### 8. Minimalni ispravan nivo

```js
window.NIVO = {
  "meta": { "title": "Minimalna igra", "author": "", "formatVersion": "1.13" },
  "world": { "mode": "topdown", "width": 1200, "height": 700 },
  "background": { "asset": "assets/trava.svg" },
  "player": {
    "asset": "assets/junak.svg",
    "start": { "x": 150, "y": 350 },
    "speed": 200,
    "collider": { "type": "circle", "radius": 18 },
    "pointer": { "move": "path" }
  },
  "entities": [
    { "id": "novcic1", "asset": "assets/novcic.svg", "x": 600, "y": 300,
      "collider": { "type": "circle", "radius": 12 },
      "onCollide": { "action": "collect", "score": 1, "remove": true } }
  ],
  "rules": { "winCondition": { "type": "collectAll", "target": "novcic" },
             "onWin": { "message": "Bravo!" } },
  "ui": { "showScore": true, "scoreLabel": "Novčići" }
};
```

Ovo je ceo `nivo.js`. Potrebne slike: `assets/trava.svg`, `assets/junak.svg`, `assets/novcic.svg`.

---

### 9. Primeri

- **`animacija/iskusaj/`** — „Iskušaj, 1. nivo: Minos" (V1.13, autor Miroslav Vučković): **pogled sa strane**, arena sa zidom i rupom,
  junak sa **mačem** (`melee` + `pose`), Minos iza zida (`z: -2`, `bossBar`, `armored`, `onDeath.fall`) i dve ruke
  (`script` sa `intro` i 5 faza po Minosovom zdravlju, `damageTo: "minos"`, `flash`, `quake`, zaglavljena ruka `vulnerable`,
  otvorena šaka koja se prevlači po podu → preskače se), zvuk i originalna muzika.

- **`animacija/omov_zakon/`** — igra „Omov zakon" (V1.10, **U = R · I**): velika mapa sa tri četvrti, majstor i table sa porukama,
  bosovi Napon/Otpor/Struja, lampe koje zasvetle (`spawnAfter` na lampama), strelice + WASD.
- **`animacija/sila/`** — igra „Sila" (učenje obrasca **F = m · a**), isprobana, V1.3, u obliku iz sekcije 2.
  Arena 960 × 540, pucanje mišem, tri bosa u fazama:
  1. **masa** — `zigzag`, baca po 3 kamena sa `lead`, `health: 6`, `shrink` → ostavlja dijamant **m**;
  2. **ubrzanje** — `charge` sa ubrzanjem, `lives: 4`, `spawn.at: "far"` → rubin **a**,
     `onDeath.player`: srca puna + 5× jači hici;
  3. **sila** — `charge` sa `steer` (prati junaka) + baca kamenje → dragulj **F**.
  Dragulji `collect` + `hud` + poruka sa formulom; pobeda `collectAll "dragulj"` sa `onWin.formula: "F = m · a"`;
  `loseMessage` po bosu i za kamenje.
  Isti obrazac radi za bilo koju formulu: svaki simbol je boss, pobeda nad njim daje slovo, na kraju formula.
- `animacija/_engine/primer/` („Dolina mudrosti", V1.2) — velika mapa, kretanje mišem sa obilaskom,
  protivnici koji jure, patroliraju i pucaju, lečenje, kamen sa porukom, dubina drveća.

---

### 10. Kako opisati igru modelu (šablon prompta)

Nalepi ovu dokumentaciju, pa ispod:

```
Napravi igru za UGE 1.13 po dokumentaciji iznad.

Igra: <ime igre>
Pogled: odozgo / sa strane
Svet: <npr. livada sa rekom i šumom, 2000×1200>
Junak: <ko je, kako izgleda, da li ima srca, da li puca i čime; kretanje: tastatura / miš (stop ili path); pucanje: taster / miš>
Protivnici: <ko, koliko, kako se kreću (i cik-cak, zaletanje), da li pucaju, koliko pogodaka izdrže, koliko života>
Faze (ako se protivnici smenjuju): <redosled, šta ostaje posle svakog, nagrada junaku>
Saigrači / neutralni likovi: <...>
Predmeti: <šta se skuplja i koliko vredi, lečenje>
Poruke (objekti koji na dodir prikažu tekst): <koji objekat, gde, naslov, formula, tekst>
Ukrasi i prepreke: <drveće, stene, kuće...>
Cilj igre: <sakupi sve X / pobedi Y / skupi N poena>
Poraz: <da / ne>

Vrati:
1. ceo nivo.js (window.NIVO = čist JSON bez komentara;),
2. sadržaj svakog SVG fajla iz assets/ (sa width i height),
3. spisak provera iz sekcije 12 sa ✔ ili ✘ za svaku stavku.
```

---

### 11. Saveti za dobar nivo

- Junak počinje u mirnom delu mape; prvi protivnik bar 400 px dalje.
- Objekat sa porukom stavi **na putu** junaka, pre izazova na koji se odnosi (formula pre protivnika m i a).
- Predmete stavi tako da vode junaka kroz mapu.
- Prepreke grupiši (šumarci, stenje), ostavi prolaze široke bar 80 px.
- Za decu: junak 6 srca, protivnici spori, `damage.amount` 1, napici za lečenje.
- Drveće sa `depth: true` + kolajder oko stabla daje lep 3/4 izgled kao u HoMM3.
- Igra za učenje formule: svaki simbol formule = jedan boss sa natpisom (`label`) šta znači;
  pobeda nad njim ostavlja dragulj sa slovom i porukom (`title`, `formula`, `text`) koja objašnjava
  simbol i jedinicu; na kraju `onWin.formula`. Poslednji boss = cela formula (najteži).
- Za arenu sa bosovima: junak 6 srca, brzina 260; posle srednjeg bosa vrati srca i ojačaj hice.
- Brzine za odnos: junak 200; goblin koji juri 100; patrola 120–150; hitac protivnika 220–260; hitac junaka 400–450.

---

### 12. Spisak provera pre predaje

1. ☐ JSON je čist (bez komentara i zareza na kraju) i ima `world`, `player`.
2. ☐ `world.mode` je tačno `"topdown"` ili `"platformer"`; `width`, `height` postoje.
3. ☐ `player` ima `asset`, `start`, `speed`; `start` je unutar sveta i **nije** u prepreci.
4. ☐ Svaka putanja slike iz JSON-a ima odgovarajući SVG fajl (isto ime, ista mala/velika slova).
5. ☐ Svaki SVG ima `width` i `height`; likovi i hici su okrenuti udesno.
6. ☐ Svaki `collect` ima `"remove": true`.
7. ☐ Svaki lik sa `wander/patrol/chase/flee` ima `speed`; `chase` je sporiji od junaka.
8. ☐ `patrol.path` ima bar 2 tačke; prva je početni položaj lika.
9. ☐ `winCondition.target` je prefiks bar jednog `id`-ja i ne preklapa se sa drugim id-jevima.
10. ☐ Za `defeatAll`: mete imaju `health`, junak ima `attack` sa `projectile.asset`.
11. ☐ Svaki `attack.projectile` ima `asset`.
12. ☐ Predmeti i objekti sa porukom su dostupni (nisu u preprekama, ni zatvoreni preprekama).
13. ☐ Ukrasi koji treba da zaustave junaka imaju prepreku u `colliders`; drveće samo oko stabla.
14. ☐ Platformer: postoji pod u `colliders`, `start` je iznad njega, skok i napad su različiti tasteri.
15. ☐ Ako junak ima `health`, postoji `rules.onLose.message`; ako se skupljaju poeni, `ui.showScore` je `true`.
16. ☐ Postoji `intro` sa kretanjem, pucanjem i ciljem igre.
17. ☐ `index.html` je tačno šablon iz sekcije 2; `nivo.js` počinje sa `window.NIVO = ` i završava se sa `;`.
18. ☐ Svaki `spawnAfter` je prefiks bar jednog drugog `id`-ja; nagrade imaju `spawn.at` = id bosa.
19. ☐ Lanac faza se završava: poslednja nagrada je obuhvaćena `winCondition.target`.
20. ☐ Svaki `charge`/`zigzag` ima `speed`; `charge` je u areni ili ima `area`.
21. ☐ Likovi koji se kreću nemaju tekst nacrtan u SVG-u (natpis ide u `label`).


---

# 2. Magije, stanja, atributi i uslovi (uz `topdown`)

> Izvor: `arena.md`

## arena — UGE dodaci za borbu kao u LoL-u (od V1.17): uputstvo za jezički model

> Kratko uputstvo samo za **magije na tasterima, stanja i protivnike koji se poigravaju**.
> Važi za `world.mode: "topdown"`; ostatak nivoa (junak, ukrasi, prepreke, pravila) je isti kao u
> [`v1.13.md`](v1.13.md). Primer igre: `plamena_arena/` (junak sa 5 magija protiv tri protivnika odjednom).

### Ukratko

- Junak hoda **klikom miša** (`pointer.move: "path"`) ili strelicama; **magije idu ka strelici miša**.
- Magije su u `player.skills` — svaka na svom tasteru (Q W E R T…), sa punjenjem i, po želji, ograničenim brojem upotreba.
- Pogodak može ostaviti **stanje** (`effect`): ukopan, gori, ošamućen, odbačen — i sliku preko mete dok traje.
- Protivnici se **poigravaju** (`behavior: "kite"`): drže razdaljinu, kruže oko junaka, izmiču hicima, skaču.
- Protivnici imaju i svoje **veštine** (`entity.skills`): zalet kroz junaka, hitac, zid koji zaustavlja hice.
- `ui.healthBar: true` — traka života umesto srca + traka magija dole; `ui.damageNumbers: true` — brojevi štete.

### Magije junaka — `player.skills`

```json
"skills": [
  { "key": "KeyQ", "label": "Q", "icon": "assets/ikona_q.svg", "type": "projectile", "cooldown": 0.8,
    "projectile": { "asset": "assets/vatrena_kugla.svg", "speed": 560, "damage": 2, "radius": 13, "lifetime": 1.1 } },
  { "key": "KeyW", "label": "W", "icon": "assets/ikona_w.svg", "type": "zone", "cooldown": 5,
    "range": 520, "radius": 80, "delay": 0.6, "duration": 0.7, "damage": 3, "asset": "assets/plameni_stub.svg",
    "effect": { "root": 2, "asset": "assets/plameni_okov.svg", "at": "feet" } },
  { "key": "KeyE", "label": "E", "icon": "assets/ikona_e.svg", "type": "target", "cooldown": 4, "range": 560, "pick": 120,
    "projectile": { "asset": "assets/iskra_e.svg", "speed": 780, "damage": 1, "effect": { "burn": 1, "asset": "assets/plamen_na_meti.svg" } } },
  { "key": "KeyR", "label": "R", "icon": "assets/ikona_r.svg", "type": "projectile", "cooldown": 12,
    "projectile": { "asset": "assets/tornado.svg", "speed": 260, "damage": 3, "radius": 40, "lifetime": 2.4,
                    "pierce": true, "upright": true, "effect": { "push": 260, "stun": 0.4 } } },
  { "key": "KeyT", "label": "T", "icon": "assets/ikona_t.svg", "type": "blink", "cooldown": 1, "charges": 3, "range": 360 }
]
```

| `type` | šta radi | svoja polja |
|---|---|---|
| `projectile` | hitac leti od junaka ka mišu | `projectile` (vidi dole) |
| `zone` | na mestu miša (najdalje `range`) prvo se vidi krug najave, posle `delay` s izbije slika `asset` iz zemlje i pogodi sve u krugu `radius` | `range`, `radius`, `delay` (0.5), `duration` (0.6), `damage`, `asset`, `effect`, `color` (`"255,109,0"`) |
| `target` | navođeni hitac u protivnika najbližeg mišu (u krugu `pick` oko miša, najviše `range` od junaka); ako ga nema, piše `miss` i magija se ne troši | `range`, `pick`, `miss`, `projectile` |
| `blink` | teleport na mesto miša (najdalje `range`; ako je tamo stena, staje ispred nje) | `range`, `color` |

Zajednička polja: `key` (npr. `"KeyQ"` ili niz tastera), `label` (slovo na ikoni), `icon` (slika 64×64),
`cooldown` (sekunde punjenja), `charges` (koliko puta u **celoj igri**, npr. teleport 3), `sound`.
Jedan pritisak tastera = jedna magija (držanje ne ponavlja).

### Nova polja hica (`projectile`) — za junaka i za protivnike

| Polje | Značenje |
|---|---|
| `effect` | stanje na pogođenom (vidi dole) |
| `pierce: true` | hitac ne nestaje na pogotku, nego ide dalje i svakog pogodi jednom (tornado) |
| `upright: true` | slika stoji uspravno, ne okreće se u pravcu leta (tornado, vihor) |
| `spin` | okretanje u letu, rad/s (bumerang: 14) |
| `boomerang: { overshoot, home, turn }` | leti pravo `overshoot` px iza junaka, pa se okreće i `home` s juri junaka (skreće `turn` rad/s), pa se vraća vlasniku. Pogađa više puta |

### Stanja — `effect`

```json
"effect": { "root": 2, "burn": 1, "stun": 0.9, "push": 260, "asset": "assets/plameni_okov.svg", "at": "feet" }
```

| Polje | Na protivniku | Na junaku |
|---|---|---|
| `root` (s) | ne može da se kreće, ali puca | ne može da hoda (mreža) — magije radi |
| `burn` (s) | gori — **ne može da puca ni da koristi veštine** | — |
| `stun` (s) | ne radi ništa, lebdi | ne hoda i ne baca magije |
| `push` (px) | odbačen u pravcu udarca (luk kroz vazduh) | odbačen |
| `asset`, `at` | slika preko mete dok traje stanje; `at`: `"body"` (podrazumevano), `"feet"`, `"head"` | isto |

### Protivnik koji se poigrava — `behavior: "kite"`

```json
"behavior": { "type": "kite", "speed": 150, "keep": [230, 340], "strafe": 0.9, "turnEvery": 1.4, "dodge": 0.35,
              "hop": { "every": 0.75, "time": 0.45, "height": 22 } }
```

- `keep: [min, max]` — razdaljina od junaka: bliže od `min` beži, dalje od `max` prilazi, između samo kruži.
  Strelac: 230–380; mačevalac: 70–160.
- `strafe` (0–1) — koliko kruži oko junaka; `turnEvery` — na koliko sekundi menja smer kruženja.
- `dodge` (0–1) — verovatnoća da odskoči u stranu kad hitac junaka leti ka njemu.
- `hop` — kreće se skokovima (kengur): `every` s između skokova, `time` s u vazduhu, `height` px.
- Lik se zaustavlja na preprekama (`colliders`) i klizi uz njih.

### Veštine protivnika — `entity.skills`

```json
"skills": [
  { "type": "dash", "every": 2.8, "range": 380, "windup": 0.35, "speed": 950, "distance": 360, "through": 100, "damage": 2, "say": "Vetre!" },
  { "type": "projectile", "every": 5, "range": 420, "projectile": { "asset": "assets/vihor.svg", "speed": 340, "effect": { "stun": 0.9 } } },
  { "type": "wall", "every": 9, "range": 280, "length": 170, "duration": 3, "distance": 70, "asset": "assets/zid_vetra.svg" }
]
```

| `type` | šta radi |
|---|---|
| `dash` | zasvetli crveno (`windup` s — vreme da se izmakne), pa projuri kroz junaka (`through` px iza njega, najviše `distance`), ostavlja trag; pogodak = `damage` + `effect` |
| `projectile` | hitac ka junaku, isto kao `attack` (`projectile`, `lead`, `count`, `spread`) |
| `wall` | kad hitac junaka leti ka liku (bliže od `range`), digne zid `length` px na `distance` px ispred sebe; zid traje `duration` s i gasi hice junaka. Slika zida je vodoravna, engine je razvuče i okrene |

Zajednička: `every` (s između dva korišćenja), `range` (koliko blizu junak mora biti), `first` (s do prvog
korišćenja), `say` (uzvik iznad lika), `sound`. Lik koji **gori** ne koristi veštine; **ukopan** ne može u zalet.

### Saveti

- Protivnici koji napadaju odjednom: daj im različite razdaljine (`keep`) — jedan blizu, dvojica izdaleka.
- Slike likova 70–95 px, okrenute udesno; slike stanja nacrtaj providno (mreža, plamen), da se lik vidi ispod.
- Mapu sa preprekama najlakše je praviti skriptom koja crta pozadinu **i** ispisuje `colliders` iz istih brojeva
  (vidi `plamena_arena/napravi_assets.py`).

### Spisak provera

- [ ] Svaka magija ima `key`, `type`, `icon` i `cooldown`; `projectile`/`target` imaju `projectile` sa `asset`.
- [ ] `ui: { "healthBar": true }` kad junak ima mnogo života (npr. 40) — srca bi zauzela ceo red.
- [ ] Protivnici ne stoje u prepreci na početku (`x`, `y` van `colliders`).
- [ ] `index.html` učitava `nivo.js` pa `engine_v1.26.js` (ili noviji).

### Atributi — `stats` (od V1.20): procesor atributa

Svaki lik (junak ili protivnik) može imati proizvoljne atribute: život, manu, energiju, zlato…
Atribut je samo **ime sa brojem**; engine ga drži u granicama, obnavlja i crta. **Događaj nikad ne kaže
ko kome oduzima** — samo koji atribut CILJA se menja i za koliko.

```json
"player": {
  "health": 40,
  "stats": {
    "mana":  { "value": 100, "max": 100, "regen": 10, "show": "bar", "color": "#2979ff", "label": "Mana" },
    "zlato": { "value": 0, "max": 999, "show": "number", "label": "Zlato" }
  },
  "skills": [ { "key": "KeyW", "type": "zone", "cost": { "mana": 25 }, "...": "..." } ]
}
```

| Polje atributa | Podrazumevano | Značenje |
|---|---|---|
| `value`, `max`, `min` | max / value / 0 | početna, najveća i najmanja vrednost |
| `regen` | 0 | koliko se puni (ili prazni, ako je negativno) u sekundi |
| `show` | — (ne crta se) | `"bar"` = traka (dole na tabli i tanka ispod trake života), `"number"` = broj gore levo |
| `color`, `label` | po imenu (mana plava, energija žuta) | boja trake i natpis |
| `onZero` | `"none"` | `"die"` = lik nestaje (junak gubi igru), `"lose"` = junak gubi igru |

Kako se atribut menja (isto za junaka i protivnike):
- **`effect`** na hicu, magiji, zaletu ili dodiru: svaki broj koji nije stanje je promena atributa cilja:
  `"effect": { "root": 1.4, "mana": -10 }` (mreža koja ukopa i oduzme manu), `"effect": { "health": -1 }`.
- **`cost`** na magiji: `{ "mana": 25 }` — bez dovoljno mane magija se ne baca (natpis „nema dovoljno: Mana“,
  ikona plavo potamni).
- **Predmet** (`onCollide: collect`) sa `effect`: `{ "action": "collect", "remove": true, "effect": { "mana": 50 } }` = napitak.
- **`onDeath.player` / `onCollide.player`**: `"stats": { "zlato": 10 }` — nagrada.
- `health` je isti atribut kao stari `health`: promena ide kroz udarac (neranjivost, poraz, smrt lika);
  `stats.health` može dodati `regen` i `show`.

Primer: `plamena_arena/` — mana 100 (+10/s), magije Q/W/E/R koštaju 5/20/15/35, tri plave bočice daju po 50,
paukova mreža oduzima 10 mane.

### Uslovi kao izrazi (od V1.21)

Uslov je JSON stablo, ne kod (engine ga tumači, ništa ne izvršava). Važi u `topdown` i `platformer` modu.

| Deo | Tačno kad |
|---|---|
| `{ "all": [...] }`, `{ "any": [...] }`, `{ "not": {...} }` | svi / bar jedan / nijedan deo |
| `{ "gone": "neprijatelj" }` | nestali su svi likovi sa tim prefiksom (može i niz prefiksa) |
| `{ "count": "neprijatelj", "<=": 1 }` | broj živih likova sa prefiksom |
| `{ "stat": "player.mana", "<": 20 }` | atribut junaka (`player`), samog lika (`self`) ili lika po id-u |
| `{ "ratio": "self.health", "<": 0.5 }` | isto, kao deo od najveće vrednosti (0–1) |
| `{ "time": "<", "value": 120 }`, `{ "score": ">=", "value": 100 }` | sekunde igre (bez pauza), poeni |
| `{ "has": "burn", "who": "neprijatelj_pauk" }` | lik je u stanju `root`/`burn`/`stun`/`push` |
| `{ "distance": ["player", "neprijatelj_vetrogon"], "<": 150 }` | rastojanje dva lika |

Poređenja: `<`, `<=`, `=`, `!=`, `>=`, `>`. Nepoznat deo je netačan (engine upozori u konzoli).

Gde se koristi:
- `rules.winCondition` — izraz; stare vrste (`defeatAll`, `collectAll`, `scoreTarget`) i dalje rade kao skraćenice.
- `rules.loseCondition` — poraz kad uslov postane tačan (poruka iz `rules.onLose`).
- `rules.triggers: [{ "when": uslov, "banner": "tekst", "message": {...}, "player": {...}, "effect": {...}, "once": true }]`
  — okida se kad uslov **postane** tačan.
- `entity.spawnWhen: uslov` — lik čeka i pojavi se kad je uslov tačan (uz `spawn.at`).
- `entity.behaviorWhen: [{ "when": uslov, "behavior": {...} }]` — prvo tačno pravilo zamenjuje ponašanje
  (npr. oslabljen lik beži: `{ "ratio": "self.health", "<": 0.35 }`).

Ako uslov ne može da se sastavi: prvo pokušaj sa `all`/`any`/`not`; ako fali veličina, engine se proširuje
novim **delom** jezika (za sve igre), nikad JavaScript-om u `nivo.js`.


---

# 3. Trka: staza i auto u modu `topdown`

> Izvor: `racing.md`

## Trka — staza i auto u modu `topdown` (od V1.26): uputstvo za jezički model

> Od V1.26 **nema posebnog moda `racing`**. Trka je obična `topdown` igra: junak vozi auto
> (`player.control: "car"`), protivnici voze po stazi (`behavior.type: "track"`), a pobeda je uslov nad
> krugovima (`{ "lap": "player", ">=": 3 }`). Zato auto može sve što i junak: da puca, skuplja predmete,
> ima manu i srca. Primer igre: `trka/`. Stara igra iz V1.19 je u `trka/stara_v1.19/`.

### Kako igra teče
Odbrojavanje (`track.countdown`), „KRENI!“, pa vožnja `laps` krugova. Gore levo: krug, vreme, najbolji krug;
gore desno mesto; dole desno brzina („Van staze!“ na pesku). Igra se završava uslovom pobede; u poruci kraja
`{place}`, `{time}` i `{best}` postaju mesto, ukupno vreme i najbolji krug. R = nova trka.
Strelice ili W A S D: gas, kočnica (i unazad), volan.

### Šema
```js
window.NIVO = {
  "meta": { "title": "Trka", "formatVersion": "1.26" },
  "intro": { "title": "Trka", "formula": "↑ gas   ↓ kočnica   ← → volan", "text": "..." },
  "world": { "mode": "topdown", "width": 2400, "height": 1500 },
  "background": { "asset": "assets/pozadina.svg" },
  "track": { "points": [[300,1250],[300,1228], ...], "width": 130, "start": 150, "laps": 3, "sectors": 8,
             "offroad": 0.4, "kmh": 0.6, "countdown": 3, "view": "track", "turnSpeed": 1.6 },
  "player": { "asset": "assets/bolid_crveni.svg", "start": { "onTrack": -70, "offset": -28 }, "speed": 0,
              "collider": { "type": "circle", "radius": 20 }, "control": "car",
              "car": { "maxSpeed": 520, "accel": 360, "brake": 760, "reverse": 160, "drag": 140,
                       "turn": 3.0, "grip": 5, "steerRate": 15 },
              "controls": { "up": ["ArrowUp","KeyW"], "down": ["ArrowDown","KeyS"],
                            "left": ["ArrowLeft","KeyA"], "right": ["ArrowRight","KeyD"] } },
  "entities": [
    { "id": "bolid_plavi", "asset": "assets/bolid_plavi.svg", "x": 0, "y": 0,
      "collider": { "type": "circle", "radius": 20 },
      "behavior": { "type": "track", "speed": 470, "accel": 300, "start": -70, "offset": 28 },
      "onCollide": { "action": "bump" } }
  ],
  "decorations": [ { "asset": "assets/drvo.svg", "x": 900, "y": 700 } ],
  "rules": { "winCondition": { "lap": "player", ">=": 3 },
             "onWin": { "message": "Cilj! {place}. mesto · vreme {time} · najbolji krug {best}", "delay": 0.6 } }
};
```

| Polje | Značenje |
|---|---|
| `track.points` | tačke SREDINE staze, zatvorena petlja, redom u smeru vožnje (na ~20 px) |
| `track.width` | širina asfalta; dalje od pola širine od sredine = „van staze“ |
| `track.start` | linija cilja: toliko px od prve tačke duž staze |
| `track.laps` (3), `sectors` (8) | krug važi samo ako su prođeni svi sektori redom (nema prečice ni vožnje unazad) |
| `track.offroad` (0.45) | deo najveće brzine na pesku |
| `track.kmh` (0.5) | px/s → km/h za brzinomer |
| `track.countdown` | sekunde odbrojavanja; dotle auti stoje |
| `track.view` | `"track"` = kamera iza auta, okrenuta po pravcu **staze** 220 px ispred (`lookAhead`) — volan ne ljulja sliku levo-desno; `"car"` = po nosu auta (ljulja se); bez polja = obična kamera |
| `track.turnSpeed` (2) | koliko brzo se pogled okreće; manje = mirnije |
| `track.outside` | boja van slike pozadine (u okrenutom pogledu) |
| `player.start.onTrack`, `offset` | početak na stazi: px od cilja (negativno = iza), bočni pomak |
| `player.control: "car"` | junak se vozi kao auto; puca (ako ima `attack`) u pravcu nosa |
| `car.maxSpeed`, `accel`, `brake`, `reverse`, `drag` | px/s i px/s² |
| `car.turn` (2.8) | okretanje volana, rad/s (pri brzini ≥ 120 px/s) |
| `car.steerRate` (15) | volan ne skače na pun otklon nego se okreće postepeno — manje cimanja levo-desno |
| `car.highTurn` (0) | koliko manje volan okreće pri najvećoj brzini (0.2 = 20 % manje) |
| `car.grip` (6) | koliko brzo nestaje bočno klizanje — manje = više proklizavanja i tragova guma |
| `behavior: { "type": "track", "speed", "accel", "start", "offset" }` | lik vozi po stazi, u krivini usporava; slika se okreće po pravcu |
| `onCollide: { "action": "bump" }` | dodir: razmaknu se, junak izgubi četvrtinu brzine |
| uslov `{ "lap": "player", ">=": 3 }` | završeni krugovi (i za lik: `"lap": "bolid_plavi"`) |
| uslov `{ "place": "player", "<=": 1 }` | trenutno mesto u trci |

Prepreke (`colliders`) zaustavljaju auto (odbije se i uspori). Slike auta: nos **udesno**, oko 56×32 px.
Pozadinu i `points` pravi ista skripta (vidi `trka/napravi_assets.py`: kontrolne tačke → glatka kriva →
asfalt, ivičnjaci, šahovska linija cilja + ispis tačaka); skripta proverava da se staza ne preklapa sama sa sobom.


---

# 4. Mod `flash`

> Izvor: `flash.md`

## flash — UGE mod „blesni pa odgovori“ (od V1.14; treptanje više puta od V1.16): uputstvo za jezički model

> Kratko uputstvo samo za `world.mode: "flash"`. Ovaj mod **nema junaka, likove, prepreke ni hice** —
> polja `player`, `entities`, `colliders`, `decorations` se ne pišu (engine ih u ovom modu ne čita).
> Primer igre: `trepni_i_zapamti/`.

### Kako igra teče

1. **Spremi se**: na novom nivou piše „Nivo N — do X oblika“ (ili „X oblika“ kad je broj tačan, `spread: 0`) 1,6 s, inače krstić `+` (`ready` s).
2. **Blesak**: slike se pojave na nasumičnim mestima, bez preklapanja, na `show` sekundi. Sa `blinks: 3` ista slika
   trepne tri puta (između je prazan ekran `blinkGap` s).
3. **Odgovor**: za svaku vrstu slike po jedan red brojeva (`choices.from`…`choices.to`). Igrač mišem/prstom
   pipne broj u svakom redu, pa klikne **Proveri** (ili Enter).
4. **Rezultat**: tačni brojevi zeleni, pogrešan izbor crven. Tačno → poeni = broj slika, sledeća runda posle 1,4 s.
   Netačno → gubi se srce, klik za dalje (isti nivo).
5. Posle `levels.rounds` tačnih rundi → sledeći nivo. Kad nestanu sva srca → kraj igre (nivo, poeni, rekord);
   klik ili R = nova igra. Rekord se pamti u pregledaču.

### Folder

```
moja_igra/
  index.html          ← isti kao za druge igre, <script src="engine_v1.26.js">
  nivo.js             ← window.NIVO = { ... };
  engine_v1.26.js     ← kopija _engine/engine.js
  assets/  *.svg      ← po jedna slika za svaku vrstu (crta se u kvadratu, čuva razmeru)
  napravi_assets.py   ← skripta koja pravi slike
```

### Šema

```js
window.NIVO = {
  "meta": { "title": "Trepni i zapamti", "formatVersion": "1.16" },
  "intro": { "title": "...", "formula": "▲  ■  ●", "text": "uputstvo; \n = novi red" },   // opciono
  "world": { "mode": "flash", "width": 960, "height": 540 },
  "background": { "colors": ["#8f8f8f", "#e4e4e4"], "period": 7 },
  "flash": {
    "kinds": [
      { "id": "trougao", "asset": "assets/trougao.svg", "label": "Trouglova" },
      { "id": "kvadrat", "asset": "assets/kvadrat.svg", "label": "Kvadrata" }
    ],
    "show": 1.0,
    "blinks": 3,
    "blinkGap": 0.5,
    "ready": 0.9,
    "choices": { "from": 1, "to": 9 },
    "levels": { "startMax": 6, "stepMax": 1, "spread": 0, "rounds": 3, "minShow": 0.4, "showStep": 0.1 },
    "size": { "fill": 0.35, "min": 28, "max": 110 },
    "lives": 3,
    "unit": "oblika",
    "question": "Koliko je bilo?"
  },
  "rules": { "onLose": { "message": "Kraj igre" } }
};
```

| Polje | Podrazumevano | Značenje |
|---|---|---|
| `background.colors` | — | dve boje; pozadina **stalno polako luta** između njih (ne ritmično). Jedna boja: `"color": "#999"` |
| `background.period` | 6 | sekunde jednog sporog talasa promene boje |
| `flash.kinds` | — | vrste slika (1–4 reda staje na ekran); `label` = natpis reda kod odgovora |
| `flash.show` | 1 | sekunde koliko se slike vide |
| `flash.blinks` (V1.16) | 1 | koliko puta ista slika trepne pre odgovora |
| `flash.blinkGap` (V1.16) | 0.5 | sekunde praznog ekrana između dva treptaja |
| `flash.ready` | 0.9 | pauza sa krstićem pre bleska (u istom nivou) |
| `choices.from` / `to` | 1 / 9 | brojevi na dugmićima; svaka vrsta se pojavi **najmanje `from`, najviše `to`** puta |
| `levels.startMax` | 7 | najviše slika ukupno na 1. nivou |
| `levels.stepMax` | 3 | za koliko raste najveći broj slika po nivou (7, 10, 13, …) |
| `levels.spread` | 3 | najmanji broj slika = najveći − `spread` (nivo 1: 4–7); `0` = tačno `startMax` + (nivo−1)·`stepMax` slika (6, 7, 8, …) |
| `levels.rounds` | 1 | koliko tačnih rundi treba za sledeći nivo |
| `levels.minShow`, `showStep` | 0.4, 0.1 | kad slika više ne može (sve vrste na `to`), svaki dalji nivo skraćuje `show` za `showStep`, do `minShow` |
| `size.fill` | 0.35 | veličina slike u odnosu na prostor po slici — **više slika = manje slike** |
| `size.min` / `max` | 28 / 120 | granice veličine slike u pikselima |
| `lives` | 1 | srca = koliko grešaka sme; 1 = prva greška završava igru |
| `unit` | „slika“ | reč na ekranu „do 10 …“ |
| `question` | „Koliko je bilo?“ | naslov ekrana za odgovor |
| `rules.onLose.message` | „Kraj igre“ | naslov završnog ekrana |

### Saveti

- Slike neka imaju **približno istu površinu** (trougao veći od kvadrata u svom polju), da nijedna ne iskače.
- Slika se crta u kvadratu veličine `s`; ostavi malo prazno oko ivice u viewBox-u da obris ne bude odsečen.
- Svetle slike (beo krug) na pozadini koja ide ka beloj su namerno teže — za lakšu igru stavi tamniju drugu boju.

### Spisak provera

- [ ] `world.mode` je `"flash"`, postoji `flash.kinds` sa `asset` za svaku vrstu, fajlovi postoje u `assets/`.
- [ ] `levels.startMax` ≥ broj vrsta × `choices.from` i ≤ broj vrsta × `choices.to`.
- [ ] `index.html` učitava `nivo.js` pa `engine_v1.26.js` (ili noviji).


---

# 5. Mod `grid`

> Izvor: `grid.md`

## grid — UGE mod „tabla sa poljima“ (od V1.15): uputstvo za jezički model

> Kratko uputstvo samo za `world.mode: "grid"`. Ovaj mod **nema junaka, likove, prepreke ni hice**:
> polja `player`, `entities`, `colliders` i `decorations` se ne pišu. Igra je tabla sa poljima, na koju igrač
> stavlja pločice iz kutije. V1.15 donosi prvu vrstu table, **strujno kolo**: engine sam računa ukupan
> otpor, ispisuje formulu redne/paralelne veze i pušta tačkice struje. Primer igre: `otpornici/`.

### Kako igra teče

1. Igra ima više nivoa (`grid.levels`). Svaki nivo je jedna tabla. Uvod nivoa (`intro`) se pojavi
   kad nivo počne.
2. Desno je **kutija** (`tray`) sa pločicama. Igrač **prevuče** pločicu na prazno mesto ili **klikne** pločicu,
   pa klikne mesto. Na zauzetom mestu se pločice zamene.
3. Klik na postavljenu pločicu:
   - na **označenom mestu** (iz `legend`) vraća pločicu u ruku;
   - na **slobodnom mestu** (`_`) okreće pločicu za 90°. Prevlačenjem se pomera.
4. Desni klik, točkić miša ili Space okreće pločicu u ruci. Esc vraća pločicu u kutiju.
   Pločica spuštena na tablu sama se okrene ka susedima sa kojima može da se spoji.
5. Kad je kolo tačno, okvir table zasvetli zeleno i stigne poruka `solved`, pa sledeći nivo.
   Posle poslednjeg nivoa dolazi ekran pobede (`rules.onWin`), a klik ili R počinje novu igru.
6. Tačke gore levo su nivoi. Klikom se vraća na već pređen nivo. Dugme **↺ Ispočetka** vraća
   sve pločice u kutiju.

### Folder

```
moja_igra/
  index.html          ← isti kao za druge igre, <script src="engine_v1.26.js">
  nivo.js             ← window.NIVO = { ... };
  engine_v1.26.js     ← kopija _engine/engine.js
  assets/  *.svg      ← po jedna slika za svaku vrstu pločice, kvadrat 80 × 80
  napravi_assets.py   ← skripta koja pravi početne slike
```

### Tabla se crta znakovima

`map` je niz redova, a **jedan znak je jedno polje**. Svi redovi treba da budu iste dužine.

| Znak | Šta je |
|---|---|
| `─ │` | žica (vodoravna, uspravna) |
| `┌ ┐ └ ┘` | ugao |
| `┬ ┤ ┴ ├` | grananje (spoj tri žice) |
| `┼` | krst (spoj četiri žice) |
| `_` | **slobodno mesto**: tu ide bilo koja pločica iz kutije, klik je okreće |
| razmak | ništa (prazno, ne može se staviti pločica) |
| drugi znak | čita se iz `legend` (nivoa, pa zajedničkog `grid.legend`) |

Stavka u `legend` je jedno od ovoga:
- **označeno mesto**: `{ "slot": true, "name": "R₁", "accept": ["resistor"] }`. Ime (`R₁`) se vidi na
  praznom mestu i ulazi u formulu. `accept` ograničava koje pločice smeju na to mesto. Pločica se
  sama okrene po susedima, a `"rot": 0..3` zadaje okret;
- **nepomična pločica**: `{ "tile": "source", "volts": 12 }`, `{ "tile": "resistor", "value": 100, "name": "R₀" }`,
  `{ "tile": "meter" }`. Bez `rot` se sama okrene ka susednim žicama.

Dve susedne pločice su spojene ako obe imaju priključak na zajedničkoj ivici.

### Vrste pločica (`grid.tiles`)

Ime vrste određuje kako se pločica ponaša. Slika se uvek crta okrenuta za `rot · 90°` udesno.

| Vrsta | Priključci kad je `rot` 0 | Ponašanje |
|---|---|---|
| `wire` | levo, desno | žica |
| `corner` | desno, dole | ugao |
| `tee` | levo, desno, dole | grananje |
| `cross` | sve četiri strane | krst |
| `resistor` | levo, desno | otpornik, otpor `value` (Ω). Uz njega se ispisuju ime i otpor |
| `source` | levo (+), desno (−) | baterija, napon `volts`. Engine sam crta + i − |
| `meter` | levo, desno | ampermetar: kao žica (0 Ω), a na njemu piše struja kroz njega |

Za svaku vrstu se zadaje `{ "asset": "assets/x.svg", "label": "žica" }`. `label` je natpis u kutiji;
otpornik u kutiji umesto toga pokazuje svoj otpor. Može se dodati i nova vrsta sa svojim imenom:
`{ "asset": "...", "role": "wire", "ports": ["N", "E"] }`, gde `role` može biti `wire`, `resistor`, `source`
ili `meter`. Slika je kvadrat 80 × 80 sa priključcima na sredinama ivica. Ampermetar sa slovom A
crtati samo vodoravno, inače se slovo prevrne.

### Šema

```js
window.NIVO = {
  "meta": { "title": "Otpornici", "formatVersion": "1.15" },
  "intro": { "title": "...", "formula": "R = R₁ + R₂", "text": "uputstvo za celu igru" },   // opciono
  "world": { "mode": "grid", "width": 960, "height": 540 },
  "background": { "color": "#1d4232" },          // ili "asset": "assets/tabla.png" (rasteže se preko ekrana)
  "sounds": { "place": "zvuci/klik.ogg" },        // opciono: place, pick, rotate, solved, win
  "grid": {
    "cell": 84,                                    // najveća veličina polja; tabla se sama smanji da stane
    "unit": "Ω",
    "tiles": { "wire": { "asset": "assets/zica.svg", "label": "žica" }, "resistor": { "asset": "assets/otpornik.svg" } },
    "legend": {
      "B": { "tile": "source", "volts": 12 },
      "A": { "tile": "meter" },
      "1": { "slot": true, "name": "R₁", "accept": ["resistor"] }
    },
    "levels": [
      {
        "title": "Redna veza",                     // gore levo
        "formula": "R = R₁ + R₂",                  // dole, dok kolo još nije zatvoreno
        "map": [
          "┌─A─1─2─┐",
          "B       │",
          "└───────┘"
        ],
        "legend": { "2": { "slot": true, "name": "R₂", "accept": ["resistor"] } },
        "tray": [ { "tile": "resistor", "value": 100 }, { "tile": "resistor", "value": 200, "count": 2 } ],
        "target": 500,                             // traženi ukupni otpor; bez njega = dovoljno je zatvoriti kolo
        "tolerance": 0.001,                        // dozvoljeno odstupanje (deo cilja), podrazumevano 0,1 %
        "fillAll": true,                           // sva označena mesta moraju biti popunjena (podrazumevano)
        "intro":  { "title": "...", "formula": "...", "text": "..." },
        "solved": { "title": "Tačno!", "formula": "...", "text": "objašnjenje posle rešenja" }
      }
    ]
  },
  "rules": { "onWin": { "message": "Pobeda!", "formula": "R = R₁ + R₂      1/R = 1/R₁ + 1/R₂" } }
};
```

### Šta engine sam radi u strujnom kolu

- **Ukupan otpor** između + i − baterije računa po Kirhofovim zakonima. Zato radi za svako kolo,
  pa i za most koji nije ni redan ni paralelan.
- **Formula**: ako se kolo sastoji samo od rednih i paralelnih veza, ispisuje je dva puta. Prvo sa
  imenima (`R = R₁ + 1/(1/R₂ + 1/R₃)`), ako su sva mesta imenovana, pa sa brojevima i rezultatom.
  Čista paralelna veza se piše kao `1/R = 1/R₁ + 1/R₂ ⟹ R = …`. Za most piše samo rezultat.
- **Ispis stanja**: „Popuni sva prazna mesta“, „Kolo nije zatvoreno“, **„Kratak spoj!“** (crvene tačkice),
  „▲ Otpor je veći od cilja“ / „▼ manji“, „✔ Tačno!“.
- Ako baterija ima `volts`, ispisuje se i `I = U / R = …` i ampermetar pokazuje struju u amperima.
- **Tačkice struje** teku od + ka −, brzinom srazmernom struji. U rednoj vezi idu istom brzinom kroz
  sve otpornike, u paralelnoj se dele (kroz manji otpor brže). Kroz ogranak bez izlaza ne idu.
- Otpornik kome su oba kraja na istoj žici je kratko spojen i ne ulazi u formulu.

### Saveti za pravljenje nivoa

- Počni od jednostavnog (dve u redu), pa paralelno, pa mešovito, pa slobodna tabla (`_`).
- U kutiju stavi i otpornike koji **ne** vode rešenju, da učenik mora da računa. Proveri skriptom koliko
  rešenja ima. Više rešenja je u redu, samo to napiši u `solved`.
- Slobodna tabla: nepomične pločice (baterija, ampermetar, početak žica) + polje `_` + u kutiji
  otpornici, `wire`, `corner`, `tee`. Tu stavi `"fillAll": false`.
- Uspravni otpornici u susednim kolonama: ostavi kolonu razmaka (`1 2 3`), da natpisi ne pređu jedan
  preko drugog.

### Tabla sa brojevima — sudoku (od V1.22)

Nivo koji ima polje `sudoku` nije strujno kolo nego tabla sa brojevima. Nema `tiles`, `legend` ni `tray`.
Primer igre: `sudoku/` (6×6, kvadrati 3 × 2, četiri nivoa, poslednji sa dijagonalama).

```js
"grid": {
  "cell": 70,
  "sudoku": { "box": [3, 2], "symbols": "123456" },     // zajedničko za sve nivoe
  "levels": [
    { "title": "Lako", "sudoku": {}, "map": ["3..6.4", "...53.", ".43.56", "..1.42", "1.2.6.", "564.13"] },
    { "title": "Dijagonale", "sudoku": { "diagonals": true }, "formula": "i na obe dijagonale svaki broj jednom",
      "map": ["..5.1.", "3.....", ".5...1", "...6..", ".....6", "53...2"] }
  ]
}
```

- `map`: jedan znak je jedno polje. Znak iz `symbols` je zadat broj (ne može se menjati), `.` je prazno polje.
- `box: [širina, visina]` kvadrata: 4×4 → `[2, 2]`, 6×6 → `[3, 2]`, 9×9 → `[3, 3]`. `symbols` mogu biti i slova.
- `diagonals: true`: znak se ne sme ponoviti ni na dve dijagonale (crtaju se isprekidano).
- Igrač klikne polje, pa broj desno (ili ga otkuca). Strelice pomeraju izbor, Backspace briše, isti broj ponovo
  briše. Broj koji se ponavlja u redu, koloni, kvadratu ili na dijagonali pocrveni. Nivo je rešen kad su sva
  polja popunjena bez ponavljanja. Ostalo (`intro`, `solved`, tačke nivoa, ↺ Ispočetka) je kao kod kola.
- Zagonetka mora imati **tačno jedno rešenje**. Nivoe praviti i proveravati skriptom (`sudoku/napravi_nivoe.py`).

### Minolovac (od V1.23)

Nivo koji ima polje `mines` je minolovac. Primer igre: `minolovac/` (četiri table, od 6×6 do 16×9).

```js
"levels": [
  { "title": "Upoznavanje", "mines": {}, "map": ["......", ".*....", "......", "....*.", "*.....", "...*.."] },
  { "title": "Početnik", "mines": { "cols": 9, "rows": 9, "count": 10 } }
]
```

- Bez `map`: mine se rasporede nasumično **posle prvog klika** (prvo polje i njegovi susedi su bez mina).
  `noGuess` (podrazumevano `true`) bira raspored koji se od prvog polja rešava samo zaključivanjem
  (pravila: broj = zastavice → ostali susedi sigurni; broj = neotvoreni → sve su mine; podskup dva broja).
- Sa `map`: zadata tabla, `*` je mina, `.` prazno (za upoznavanje, uvek ista).
- Levi klik otvara, desni klik ili dugo držanje (dodir) stavlja zastavicu, dugme „🚩 Zastavice“ pretvara
  levi klik u zastavicu. Klik na broj oko koga su već sve zastavice otvara ostale susede.
- Mina = poruka `lost` (ili „Bum!“) pa ista tabla iznova, sa novim rasporedom. Gore desno: preostale mine i vreme.

### Hanojske kule (od V1.24)

Nivo koji ima polje `towers`. Primer igre: `hanojske_kule/` (3, 4, 5 i 6 diskova).

```js
"grid": {
  "towers": { "pegs": 3, "from": 0, "to": 2 },              // zajedničko
  "levels": [ { "title": "3 diska", "towers": { "disks": 3 }, "formula": "2³ − 1 = 7" } ]
}
```

- Na početku su svi diskovi na štapu `from`, cilj je štap `to` (označen sa ★).
- Klik na štap uzima gornji disk, klik na drugi ga spušta; može i prevlačenje ili tasteri 1–9 (Esc vraća disk).
  Veći disk na manji se ne može spustiti (disk se vrati, poruka crveno).
- Gore desno: broj poteza i, za 3 štapa, najmanji broj 2ⁿ − 1. Ispod rešenja piše da li je bilo „savršeno“.
- `showSolution` (podrazumevano `true`): dugme „▶ Pokaži rešenje“ vrati kulu na početak i odigra rekurzivno
  rešenje korak po korak; pokazano rešenje se ne računa, posle njega kula se vraća na početak.
- `pegs: 4` daje četiri štapa (tada se ne ispisuje najmanji broj poteza, a dugme pokazuje rešenje sa 3 štapa).


---

# 6. Mod `quiz`

> Izvor: `quiz.md`

## quiz — UGE mod „kviz“ (od V1.25): uputstvo za jezički model

> Kratko uputstvo samo za `world.mode: "quiz"`. Ovaj mod **nema junaka, likove ni tablu**: polja `player`,
> `entities`, `colliders`, `decorations` i `grid` se ne pišu. Igra je niz nivoa, a svaki nivo je jedno pitanje.
> Primer igre: `poredjaj/` (Poređaj po veličini: dužina, memorija, rezolucija, pitanje, planete, brojevi).

### Kako igra teče

1. Nivoi su u `quiz.levels`. Tačke gore levo pokazuju nivoe; klikom se vraća na već pređen nivo.
2. Kad je odgovor tačan, stigne poruka `solved` (objašnjenje), pa sledeći nivo. Posle poslednjeg dolazi
   ekran pobede (`rules.onWin`), a klik ili R počinje novu igru.
3. `intro` na vrhu nivoa je uputstvo za celu igru; `intro` u nivou se pokaže kad taj nivo počne.

### Folder

```
moj_kviz/
  index.html          ← isti kao za druge igre, <script src="engine_v1.26.js">
  nivo.js             ← window.NIVO = { ... };
  engine_v1.26.js     ← kopija _engine/engine.js
  assets/             ← samo ako pitanja imaju slike
```

### Vrste pitanja (`type`)

#### `order` — poređaj

```js
{ "title": "Dužina", "type": "order",
  "items": [ { "text": "1 mm", "note": "0,001 m" }, { "text": "1 cm", "note": "0,01 m" }, { "text": "1 m", "note": "1 m" } ],
  "from": "najmanje", "to": "najveće",
  "formula": "1 m = 100 cm",
  "solved": { "title": "Tačno!", "text": "objašnjenje" } }
```

- Stavke se pišu **u tačnom redosledu**. Engine ih sam promeša (najviše jedna ostane na svom mestu).
- Stavka može biti i samo tekst (`"1 mm"`). `note` je prava vrednost, a pokaže se tek kad je red tačan.
  `asset` dodaje sliku na karticu.
- `from` i `to` su natpisi na krajevima strelice. Podrazumevano je „najmanje“ i „najveće“, ali može i
  „najstarije“ i „najnovije“, „prvi korak“ i „poslednji korak“.
- Igrač prevlači kartice ili klikne dve da zamene mesta. „✔ Proveri“ (ili Enter) boji kartice: zelene su
  na mestu, crvene nisu. „↺ Promešaj“ meša ponovo. Najviše 8 kartica.

#### `choice` — ponuđeni odgovori

```js
{ "title": "Pitanje", "type": "choice",
  "question": "Koliko piksela ima slika 1920 × 1080?",
  "asset": "assets/slika.png",
  "answers": ["oko 2 miliona", "oko 3000", "oko 20 000", "oko 200 miliona"],
  "correct": 0,
  "shuffle": true }
```

- `asset` je opciona slika iznad pitanja.
- `correct` je redni broj tačnog odgovora u `answers`, od 0. Engine meša odgovore, osim ako je `"shuffle": false`.
- Odgovaraju 2 do 4 odgovora. Bira se klikom ili tasterima A–D (1–4). Pogrešan odgovor pocrveni i ne može se
  izabrati ponovo, a `formula` se pokaže dole tek posle tačnog.

### Šema

```js
window.NIVO = {
  "meta": { "title": "Poređaj po veličini", "formatVersion": "1.25" },
  "intro": { "title": "...", "formula": "...", "text": "..." },
  "world": { "mode": "quiz", "width": 960, "height": 540 },
  "background": { "color": "#1d4232" },
  "sounds": { "place": "zvuci/tacno.ogg", "pick": "zvuci/pogresno.ogg", "solved": "...", "win": "..." },
  "quiz": { "levels": [ /* order i choice, bilo kojim redom */ ] },
  "rules": { "onWin": { "message": "Bravo!", "formula": "..." } }
};
```

### Saveti

- Proveri svaku činjenicu i svaki broj u `note`; učenik ih vidi kao tačne.
- Za `order` biraj stavke koje se zaista mogu nedvosmisleno poređati. Bez dve iste vrednosti!
- `solved.text` je mesto za ono što učenik treba da zapamti (pravilo, a ne samo „tačno“).
