---
title: WarpEngine
url: 'https://wiki.teletypegames.org/hu/engines/warp-engine'
markdown: 'https://wiki.teletypegames.org/hu/engines/warp-engine.md'
lang: hu
date: '2026-08-06'
description: 'A WarpEngine mountolható Rails engine, amely bármelyik Rails-appból retró szoftverkatalógust csinál. Egy játékrepó egyetlen soros .woodpecker.yaml jelölőt visz, a motor kiszolgálja a teljes CI-pipeline-t, a buildek HTTP-n töltődnek fel, egy publikálási hívás pedig upserteli a katalógust. Publikus,…'
taxonomy:
  tag:
    - engine
    - highlighted
---

# WarpEngine

> A WarpEngine mountolható Rails engine, amely bármelyik Rails-appból retró szoftverkatalógust csinál. Egy játékrepó egyetlen soros `.woodpecker.yaml` jelölőt visz, a motor kiszolgálja a teljes CI-pipeline-t, a buildek HTTP-n töltődnek fel, egy publikálási hívás pedig upserteli a katalógust. Publikus, read-only JSON API és opcionális ActiveAdmin resource-ok is járnak hozzá. Minden osztálya, szolgáltatása és kontrollere dokumentálva van a forrásban: az **[API-referencia (RDoc)](https://wiki.teletypegames.org/rdoc/warp_engine/latest/)** minden `warp_engine-v*` taggel újraépül belőle.

# WarpEngine-hez kapcsolódó oldalak

| Oldal | Mi az | Szerep |
|---|---|---|
| [WECP — WarpEngine Catalog Protocol](https://wiki.teletypegames.org/hu/engines/warp-engine/wecp) | Az olvasó oldali HTTP/JSON szerződés végpontonként: amivel minden katalógusolvasó beszél. | protokoll |
| [WarpEngine Admin API](https://wiki.teletypegames.org/hu/engines/warp-engine/admin-api) | Az 1.0 óta: amit az admin tud, JSON API-ként, OpenAPI 3.1 leírással és MCP-szerverrel. Kikapcsolva érkezik. | protokoll |
| [WarpEngine Client](https://wiki.teletypegames.org/hu/stores/warp-engine-client) | Az asztali app: a katalógus kártyarácsként, telepítés, indítás, eltávolítás. Linux, macOS, Windows. | kliens |
| [warpstore](https://wiki.teletypegames.org/hu/engines/warpstore) | A shell-szkriptes áruházmotorok közös magja: egy fájl, amely ismeri a WarpEngine-katalógust. | kliens |
| [WarpEngine Batocera Store](https://wiki.teletypegames.org/hu/stores/warp-engine-batocera-store) | Bármelyik WarpEngine-katalógus egy Batocera-dobozon, saját EmulationStation-menü alatt. | kliens |
| [WarpEngine RetroArch Store](https://wiki.teletypegames.org/hu/stores/warp-engine-retroarch-store) | Bármelyik WarpEngine-katalógus RetroArch-lejátszási listaként, borítókkal. | kliens |
| [TTG Batocera Store](https://wiki.teletypegames.org/hu/stores/ttg-batocera-store) · [TTG RetroArch Store](https://wiki.teletypegames.org/hu/stores/ttg-retroarch-store) | A saját két áruházunk: egy-egy konfig a fenti motorok tetején. | áruház |
| [Nyilvános oldal](https://wiki.teletypegames.org/hu/services/public-site) | teletypegames.org, az első hoszt: a monorepó, amelyben a motor fejlődik, és a katalógus, amire a legtöbb olvasó mutat. | hoszt |
| [Teletype Orbit](https://wiki.teletypegames.org/hu/services/teletype-orbit) | Az áruházplatform a WarpEngine-en: vásárlás, jogosultságok, kapuzott letöltések, eszközös bejelentkezés. | hoszt |

# Amit kapsz

- **Katalógus domain** — `Software`, `Release`, `ReleaseAsset`, `ExternalLink`, `PlatformLink`, `SoftwareImage`, `Download`. Mind soft delete-tel, download tracking-gel.
- **CI-pipeline-szerver** — Woodpecker [configuration extension](https://woodpecker-ci.org/docs/usage/extensions/configuration-extension). A játékrepókban csak egy platform marker van. A builder image-ek szerver oldalon élnek, így egy image bump egyszerre ér el minden repót.
- **Pluggable CI** — 0.7-től minden CI-funkció egyetlen adapteren megy át. A Woodpecker az engine-nel érkezik `WarpEngine::CI::Woodpecker` néven, és ez a default; a `:none` kikapcsolja a CI-t, más szerver pedig a hoszt által adott objektum.
- **Build endpoint-ok** — `POST /build/upload` és `POST /build/publish`, shared secret-tel vagy adatbázisbeli application token-ekkel hitelesítve.
- **Publikus JSON API** — katalógus, kiemelt cím, platformonkénti build mátrix, download tracking, statikus fájlszerver a webes buildekhez.
- **A hoszt képtára** — 0.6-tól az `Image` modell, a fájljai, az `/api/image/:id` endpoint-ja és az admin oldala a hosztban él; az engine egy szoftvert egy képhez köt, és adapteren keresztül publikálja az URL-t. Ugyanaz a tábla ezután avatarokat vagy bármi mást is kiszolgálhat, ami a site-nak kell.
- **Pluggable access** — a hoszt ad egy policy-t, a katalógus pedig árakat, jogosultságokat és gated download-okat kap, és ezt az API-ban is közli, így egy kliens a fizetős címet fizetősként tudja mutatni ahelyett, hogy letöltéskor bukna el. Default: a nyílt katalógus.
- **Client sign-in** — RFC 8628 device authorization grant a hoszt user modelljén, böngésző nélküli klienseknek. Konfig nélkül kikapcsolva.
- **Opcionális admin** — ActiveAdmin resource-ok (katalógusszerkesztő, fájlkezelő, download statisztika, application token-ek). ActiveAdmin nélkül az engine headless módban fut.
- **CI-kezelés** — API token-nel az admin vezérli a CI-szervert: repo sync, pipeline history kézi trigger-rel, CI secret-ként kiosztott application token-ek.
- **Asset-ellenőrzőlista** — 0.8-tól a `rake warp_engine:builds:check` szoftverenként és kiadásonként kiírja, mely assetek vannak meg, és melyeket tudná még a platform pipeline-ja leszállítani. Lásd: *Mely assetek hiányoznak*.
- **Polcok** — 1.2-től egy platform legfrissebb buildjei egy lapos mappában, rögzített, verzió nélküli néven, egy `INDEX.TXT`-vel, olyan klienseknek, amelyek nem tudják olvasni a JSON API-t (a [WARP64U](https://wiki.teletypegames.org/hu/stores/warp64u) C64-es programtár sima HTTP-n olvassa a C64-polcot). Az 1.7-től egy polc címenként egy mappa is lehet, abban az elrendezésben, amelyet a [DOSINST](https://wiki.teletypegames.org/hu/dos/dosinst) olvas, így a DOS-bolt is a katalógusból épül. Lásd: *Polcok*.
- **Saját platformok** — 1.5-től a hoszt felvehet egy platformot a saját szolgáltatásával, kindjaival és pipeline-sablonjával; a beépítettek már nem az egyetlenek. Lásd: *Saját platformok*.
- **Közös verzió- és kiadási lépés** — 1.7-től az engine minden generált pipeline-hoz hozzáadja a `version` lépést, és ahol a hoszt kéri, a végére egy forge-os `release` lépést, bármelyik platformon. Lásd: *Lépések, amelyek minden pipeline-ban közösek*.
- **Konfigurációs DSL** — 1.8-tól az initializer hash-ek helyett nevesített beállítások blokkja (`shelf`, `admin_api`, `additional_resource`, `ci … do platform … end`); a régi `|c|`-s forma is működik. Lásd: *Telepítés egy hosztba*.
- **Kiadás a pipeline saját tagjeiből** — 1.9-től a push buildel, és a forrásban megjelenő új verzió a pipeline-tól kapja meg a `v<verzió>` tagjét; a tag pipeline-ja publikálja. Egy dev build az ága `dev-<ág>` tagjét mozgatja, így mindig az ág legfrissebb buildje van kiadva. Lásd: *Verziók és kiadások*.

## Támogatott platformok

Platformonként két lista, és a különbség számít. Az **elfogadott assetek** az, amit a frissítő befogad egy kiadás publikálásakor (`WarpEngine::Platform#expected_kinds`). A **pipeline ezeket építi** az, amit a Woodpecker-sablonok ténylegesen elő is állítanak. Ahol eltérnek, ott a maradék kézi feltöltés.

Az 1.5 óta minden elfogadott kind ezen felül **kötelező**, **várt** vagy **opcionális**. Kötelező kind nélkül a publikálás `422`-vel, a hiányzó kindok listájával válaszol, és nem rögzít semmit. Egy várt kind hiányozhat egy kiadásból, és az alábbi ellenőrzőlista jelzi. Egy opcionális kind jogosan hiányozhat, és nem számít hiánynak. A `GET /api/builds` platformonként mindhármat közli.

| Platform | Felirat | Elfogadott assetek | Kötelező | A pipeline ezeket építi |
|---|---|---|---|---|
| `godot` | Godot | html, win\_x86, win\_x64, linux\_x64, mac\_universal | html | mindet |
| `tic80` | TIC-80 | cartridge, source, html, docs, win\_x64, linux\_x64, mac\_x64 | cartridge | mindet |
| `bevy` | Bevy | html, win\_x64, linux\_x64, linux\_arm64 | html | mindet |
| `love` | LÖVE | html, win\_x64, linux\_x64, mac\_universal | html | mindet |
| `ebitengine` | Ebitengine | html, win\_x86, win\_x64, linux\_x64, linux\_arm64, mac\_x64, mac\_arm64 | html | html, win\_x86, win\_x64, linux\_x64, linux\_arm64 |
| `phaser` | Phaser | html | html | mindet |
| `c64` | C64 | cartridge, html, win\_x64, linux\_x64, mac\_arm64 | cartridge | mindet |
| `plus4` | Plus/4 | cartridge, html, win\_x64, linux\_x64, mac\_arm64 | cartridge | mindet |
| `dos` | DOS | cartridge (a telepíthető mappa zipben), win\_x86 (opcionális: a Win32-es build, ami csak egyes programoknak van) | cartridge | mindet |

Az Ebitengine az egyetlen hiányosság: egy macOS-bináris osxcrosst igényel, amit a linuxos builder image nem visz, így a `mac_x64` és a `mac_arm64` mindig csak kézzel érkezik.

## Mely assetek hiányoznak

Egy kiadás akkor teljes, ha minden olyan asset-típust visz, amit a platformja pipeline-ja elő tud állítani — és semmi nem kényszeríti ki. Elbukik egy buildlépés, egy platform új célt kap, egy címet laptopról publikálnak, és a katalógus hét assetből hárommal szolgál ki egy kiadást. A motor ellenőrzőlistát szállít hozzá:

```
$ bin/rails warp_engine:builds:check

WarpEngine release asset coverage — latest release per software
expected: the kinds a platform's updater registers · [ ] the CI builds it · [-] the CI does not, upload it by hand

rabbitroller  ebitengine  v1.1.1  3/7
  [x] html
  [x] win_x64
  [x] linux_x64
  [ ] win_x86
  [ ] linux_arm64
  [-] mac_x64
  [-] mac_arm64

The CI can build these and the release does not have them:
  rabbitroller v1.1.1 (ebitengine): win_x86, linux_arm64

12 softwares, 12 releases, 61/78 assets present, 11 missing from CI-built kinds
4 softwares are missing an asset the CI builds
2 assets are expected but this CI does not build them
```

| Jelölés | Jelentés |
|---|---|
| `[x]` | a kiadásnak megvan ez az assetje |
| `[ ]` | a platform elvárja, **és** a pipeline meg is építi — valódi hiány, és ez az a sor, amivel érdemes kezdeni valamit |
| `[~]` | ezen a platformon opcionális (a DOS `win_x86`-ja): hiányzik, és nem számít hiánynak |
| `[-]` | elvárt, de ez a CI nem tudja megépíteni (lásd a fenti táblázatot) — kézi feltöltés, vagy semmi |
| `[+]` | a kiadás olyan assetet visz, amit a platform nem vár el |

A beállítások környezeti változók, így shellből komponálhatók:

| Változó | Hatás |
|---|---|
| `NAME=rabbitroller` | egyetlen szoftver |
| `PLATFORM=ebitengine` | egyetlen platform |
| `RELEASES=all` | minden kiadás, nem csak a szoftverenkénti legfrissebb |
| `ONLY=missing` | csak azok a kiadások, amelyekből hiányzik egy CI által épített asset |
| `STRICT=1` | nem nulla kilépési kód, ha bármi hiányzik — ütemezett ellenőrzéshez |

A „legfrissebb" a legfrissebb nem `dev-` verzió — ugyanaz a szabály, amit a katalógus API is használ, amikor megnevezi egy cím legutóbbi kiadását. A számok is objektumok: a `WarpEngine::AssetCoverage` a `#rows`, `#missing_rows` és `#totals` metódusokra válaszol, a `WarpEngine::AssetCoverageChecklist` pedig kirendereli őket.

Mindkét hoszton elérhető: a [nyilvános oldalon](https://wiki.teletypegames.org/hu/services/public-site) és a [Teletype Orbitban](https://wiki.teletypegames.org/hu/services/teletype-orbit).

# Végpontok

## Nyilvános API

Ez az API olvasó oldala — a **WECP, WarpEngine Catalog Protocol**. Minden végpont kérés- és válaszalakja a [WECP-oldalon](https://wiki.teletypegames.org/hu/engines/warp-engine/wecp) van; ez a tábla a térkép.

| Végpont | Mire való |
|---|---|
| `GET /api/service` | Mi ez a telepítés: verzió, kapuz-e a katalógus, hogyan lehet belépni, és (1.7 óta) a polcai a címeikkel. |
| `GET /api/software` | A katalógus kiadásokkal, assetekkel, linkekkel, letöltésszámokkal, opcionális `category` mezővel és egy `access` blokkal — egészben, vagy laponként (`?page=`, `?per_page=`), `status`, `platform`, `category`, `q`, `owner_id` szűrőkkel. |
| `GET /api/software/facets` | Hány szoftvert mutatna az egyes státusz-, platform- és kategóriaértékek a többi szűrő mellett. |
| `GET /api/software/vocabulary` | A katalógus státuszai és kategóriái, címkével és sorrenddel. |
| `GET /api/software/highlighted` | A kiemelt cím. |
| `GET /api/builds` | Platformonként elvárt asset-típusok. |
| `GET /api/softwares/:name/builds` | Kiadásonként a meglévő és a hiányzó buildassetek. |
| `GET /api/image/:id` | Képek. 0.6 óta **a hoszt végpontja** — a motor közli a címet, a hoszt szolgálja ki. |
| `GET /api/download?path=` | Kiszolgál egy artifactot, és naplózza a letöltést. |
| `GET /file/*path` | Statikus buildkimenet (böngészőben játszható játékok, dokumentáció). |
| `POST /api/auth/device` | Elindít egy eszközbelépést, visszaadja a kódpárt. 404, ha nincs kliensidentitás beállítva. |
| `POST /api/auth/device/token` | Lekérdezi egy eszközbelépés tokenjét. |
| `DELETE /api/auth/token` | Visszavonja a kérésen érkező bearer tokent — kilépés. |

Minden olvasó végpont elfogad egy opcionális `Authorization: Bearer …` fejlécet; egyik sem követeli meg. A token csak azt változtatja meg, amit a hozzáférési szabályzattól kérdezünk. Az anonim hívó támogatott hívó.

Minden válasz visz egy `WarpEngine-Version` fejlécet, amely az akció lefutása előtt kerül rá — így a hibaválaszokon is rajta van, és egy kliens plusz kérés nélkül tud a motorverzió szerint ágazni.

```sh
$ curl -sI https://teletypegames.org/api/software | grep -i warpengine
WarpEngine-Version: 0.13.1
```

A hosztalkalmazás saját végpontjai (például az áruház-nyilvántartás) nem viszik — azok nem részei a motornak.

## Build / CI

| Végpont | Mire való |
|---|---|
| `POST /build/upload` | Egy artifact (multipart `file`, opcionális `sha256`). `X-Update-Secret` kell hozzá. |
| `POST /build/publish` | A feltöltött artifactok regisztrálása kiadásként. `X-Update-Secret` kell hozzá. Az 1.9 óta a már kiadott legújabbnál régebbi verzióra `409`-et ad. |
| `POST /build/config` | Woodpecker-konfigurációs kiterjesztés; a pipeline-YAML-t szolgálja ki. Aláírás-ellenőrzött (RFC 9421, ed25519). |
| `GET /build/config?platform=` | A generált YAML nyilvános előnézete. |

# Hozzáférés és identitás

Két seam került be a **0.5.0**-ban, mindkettő a storage adapter mintájára: dokumentált contract, a régi viselkedéssel azonos default, és egyetlen config kulcs a cseréjéhez. Ha egyiket sem állítod be, semmi nem változik.

## Ki mit láthat és tölthet le

```ruby
c.access_policy = MyStore::AccessPolicy.new   # :open (alapérték) = a katalógus, ahogy volt
```

A szabályzat három kérdésre válaszol:

| Metódus | Kérdés |
|---|---|
| `visible_software_scope` | mely címeket listázza a `GET /api/software` |
| `access_for` | mit tudunk meg egy kliensként az egyes címekről |
| `authorize_download` | átadható-e ez az artifact |

Az `authorize_download` kérdést az `/api/download` **és** a `/file/*` is felteszi, mielőtt bármit kiszolgálna, így egy hosztnak már nem kell árnyékolnia ezeket az útvonalakat.

Minden katalógusbejegyzés visz egy `access` blokkot, **a nyílt szabályzat alatt is** — egy kliensnek soha nem kell találgatnia, hogy a katalógus hallgat-e, vagy a cím egyszerűen nincs kapuzva:

```json
"access": { "gated": true, "entitled": false,
            "price": { "amountCents": 1490, "currency": "EUR" },
            "purchaseUrl": "https://shop.example/games/slug",
            "webUrl": "https://shop.example/play/slug" }
```

A mezőnevek szándékosan általánosak, mert egy kliens több katalógust olvas.

**Ha a policy exception-t dob, az elutasításnak számít**: üres katalógus, megtagadott letöltés, logolva. Egy artifactot soha nem szabad azért kiszolgálni, mert a gatekeeper crashelt.

## Client sign-in

Egy desktop kliensnek nincs cookie jar-ja és nincs böngésző session-je, ezért az engine login form helyett a device authorization grant-et (RFC 8628) használja.

```ruby
c.access_token_owner_class  = "User"      # nil (alapérték): sehol nincs belépés
c.identity_verification_url = "/devices"  # a te oldalad, ahol valaki beírja a kódot
c.subject_resolver = ->(request) { request.env["warden"]&.user }   # böngésző felismerése
```

1. a kliens `POST`-ol az `/api/auth/device` végpontra, és megmutatja a `userCode` értéket;
2. az ember megnyitja az ellenőrzőoldalt, és beírja azt a kódot;
3. **a hoszt oldala** meghívja a `WarpEngine::DeviceGrantService#approve(user_code:, subject:)` metódust — a jóváhagyáshoz session és HTML kell, ami nem az engine dolga;
4. a kliens következő lekérdezése elviszi a tokent. Pontosan egyszer adjuk át.

A token egy `WarpEngine::ApplicationToken` a `catalog` scope-pal, így a publish és a kliens credential-ök egyetlen táblában is szétválaszthatók maradnak. A `subject_resolver` fedi le a másik esetet: egy böngésző session-t visz, nem bearer token-t, és enélkül egy gated áruház a saját bejelentkezett látogatóit utasítaná vissza.

Ha az `access_token_owner_class` nincs beállítva, az `/api/auth/*` 404-et ad, az `/api/service` pedig `auth: null`-t jelent, így a kliens nem kínál sign-in-t.

# A képtár

A kép nem katalógusfogalom — ugyanaz a képtár szolgálja ki a borítókat, a képernyőképeket és az avatarokat —, ezért a **0.6.0**-tól a motoré csak a kapcsolat (`SoftwareImage`: sorrendezett, egy alapértelmezett), miközben a modell, a fájlok, a végpont és az adminoldal a hoszté. A `rails g warp_engine:install`kiír egy működő készletet (`app/models/image.rb`, `app/controllers/api/images_controller.rb`, egy `create_images` migrációt), a motor pedig adapteren keresztül éri el:

```ruby
c.image_class_name = "Image"          # alapérték
c.image_adapter    = MyLibrary.new    # vagy cseréld le teljesen az adaptert
```

| Metódus | Mire használjuk |
|---|---|
| `model_name` / `model` | az az osztály, amelyhez a `SoftwareImage#image` tartozik |
| `url_for(image_id)` | az `imageUrl` és az `images[].url` a katalógus JSON-jában, valamint az adminelőnézetek |
| `select_options` | a képválasztó a szoftverűrlapon |
| `build_from_upload(upload)` | „új kép feltöltése" ugyanezen az űrlapon |
| `label_for(record)`, `available?(record)` | az admin felirata és a „megvan-e a fájl" ellenőrzés |

Az alapértelmezett adapter az `/api/image/<id>` címet publikálja — azt, amit a kliensek mindig is használtak —, ezért a generált útvonal pontosan ezt szolgálja ki. **Az `images` tábla nem költözött**: a motor egyszerűen abbahagyta a létrehozását, és a `software_images.image_id` továbbra is ugyanazokra a sorokra mutat.

# A Woodpecker használata a WarpEngine-nel

Egy játékrepónak nincs saját pipeline-ja. A Woodpecker minden pipeline-indításkor POST-olja az egysoros jelölőt a `/build/config` végpontra, és visszakapja a teljes YAML-t.

1. A Woodpecker úgy van beállítva, hogy a WarpEngine a konfigurációs kiterjesztése.
2. A repó `.woodpecker.yaml` fájljában csak a `platform:` van (és a `name:`, ha a szoftvernév eltér a repó nevétől).
3. Egy push elküldi a jelölőt a `POST /build/config` végpontra.
4. A CI-adapter rendereli a `lib/warp_engine/ci/woodpecker/platforms/<platform>/pipeline.yaml.erb` sablont a beállított builder image-dzsel — a YAML-nyelvjárás a szolgáltatóé, tehát az adapterrel együtt él. Az 1.1-től a segédlépések (version, lint, minify, docs, upload, publish) a platform `tools` image-ében futnak, ha van ilyen, és semmit nem telepítenek; nélküle `alpine`-ban futnak, és minden futáskor `apk add`-dal (vagy `luarocks install`-lal) rakják fel, ami kell nekik.
5. A pipeline lefut. Az 1.9 óta push után: verzió → build → tag; a lépés által készített tagre: verzió → build → feltöltés → publikálás (→ kiadás, ahol a hoszt bekapcsolja). Lásd: *Verziók és kiadások*.

Egy repó úgy lép ki ebből, hogy valódi pipeline-YAML-t commitol: a nem jelölő konfiguráció `204 No Content` választ kap, és változatlanul fut.

## Egy játékrepó beállítása

```yaml
# .woodpecker.yaml
platform: godot
name: mygame     # opcionális, alapból a repó neve
```

```json
// metadata.json — a TIC-80-on kívül minden platformon ugyanez
{
  "name": "mygame",
  "version": "1.0.0",
  "title": "My Godot Game",
  "author": "You",
  "desc": "A cool game",
  "license": "MIT",
  "category": "puzzle"
}
```

A `category` opcionális szabad szöveg, amely szerint egy kliens csoportosíthatja a címeket — *puzzle*, *action*, *demo*. A 0.12-től a `Software` rekordra és a `GET /api/software` válaszába kerül; a WarpEngine Client kategória-tengelyt rajzol a szűrőjébe, amint egy katalógus publikál ilyet. A `site` és a `repo` is opcionális, belőlük külső link lesz.

| Platform | Forráselvárások | Mit épít a pipeline |
|---|---|---|
| `godot` | exportbeállítások: Web, Windows x86, Windows x64, Linux x64, Mac universal | `godot --headless --export-release` előbeállításonként |
| `bevy` | szabványos Cargo-projekt; az `assets/` a natív binárisok mellé kerül becsomagolva | WASM, linux-x64, win-x64 (mingw) |
| `love` | szabványos LÖVE-projekt | love.js webes build, Windows exe, Linux AppImage, univerzális macOS `.app` |
| `ebitengine` | szabványos Go-projekt | WASM, win x86/x64, linux x64, mac x64/arm64 |
| `phaser` | JS-fájlok a `src/` alatt | szintaxisellenőrzés, a Phaser-runtime letöltése, webes zip |
| `c64` | `main.asm`, vagy `build: make` mellett egy Makefile | ACME-fordítás `.prg` kazettává (vagy `make <name>.prg`, a builder image-ben cc65 is van), majd `commodore-pack` az `exporter` image-ben: EmulatorJS webes build a `vice_x64sc` core-ral és yapeSDL-csomagok linux-x64, win-x64 és mac-arm64 célra |
| `plus4` | ugyanaz, mint a `c64` | ugyanaz, mint a `c64`, a `vice_xplus4` core-ral és Plus/4 módú yapeSDL-lel |
| `dos` | `SOURCE/build.sh`, a tesztek, és a `metadata.json` `dos` szakasza (lásd: [DOS](https://wiki.teletypegames.org/hu/platforms/dos)) | a `dos-builder` image parancsai ([dos-tools](https://wiki.teletypegames.org/hu/platforms/dos)): minden eseményre a build és a tesztek; tagre a telepíthető mappa zipje (és a Win32-esé) és a forge-kiadás; az FTP-bolt a DOS-polc, a katalógusból épül |
| `tic80` | `<name>.inc`, amely felsorolja az `inc/` alatti darabokat | összefűzés, luacheck, minifikálás, LDoc, HTML + kazetta + natív binárisok |

A TIC-80-nak nem kell `metadata.json` — a metaadat a Lua-fejléc:

```lua
-- title: My TIC Game
-- name: mygame
-- author: You
-- desc: A TIC-80 game
-- category: puzzle
-- version: 1.0.0
function TIC() end
```

## A jelölő beállításai

| Kulcs | Kötelező | Leírás |
|---|---|---|
| `platform` | igen | `godot`, `tic80`, `bevy`, `love`, `ebitengine`, `phaser`, `c64`, `plus4`, `dos`, vagy egy a hoszt által felvett platform |
| `name` | nem | Szoftvernév; alapból a repó neve |
| `release` | nem | `tag` (az 1.9 óta alapértelmezés): a build minden eseményre fut, a push a `tag` lépéssel zárul, a kiadási lépések (package, upload, publish, release) csak tagre futnak. `push`: az 1.9 előtti működés, minden push publikál, és nincs tagelés. Egy platform megkövetelheti a taget (`release_rule "tag"` a szolgáltatásában), és akkor a jelölő nem választhatja a `push`-t: a DOS ilyen platform |
| `build` | nem | `make` a `c64`-en és a `plus4`-en: `make <name>.prg` az ACME-s `main.asm` helyett, C-ben írt programhoz (a WARP64U így épül) |

Minden más kulcs egy olyan opció, amit a platform sablonja olvashat: csak betű, szám és `_ . -`, a többi kimarad. A repó többi `.woodpecker/*.yaml` fájlja változatlanul visszamegy, így a repó a generált mellett megtarthatja a saját workflow-ját (tesztek, deploy).

## Pipeline előnézete

```sh
curl "https://your-host/build/config?platform=godot"
```

## Lapozás és szótár (0.13)

A `GET /api/software` borítékban válaszol — `api` (a JSON-alak saját verziója, 2), `page`, `perPage`, `total`, `totalPages`, `nextPage` és `softwares`. **`page` nélkül a teljes katalógus jön vissza** ebben a borítékban, amit minden 2-es API előtti olvasó a `softwares` alatt talál meg; `page`-gel egy lap `per_page` elemmel (`catalog_page_size`, ha nincs megadva, legfeljebb `catalog_max_page_size`). A szűrők a lapozás előtt szűkítik a katalógust; a hozzáférési szabályzat mindkettő előtt dönt a láthatóságról.

A státuszok és a kategóriák **adatbázisbeli szótár** — `WarpEngine::SoftwareStatus` és `WarpEngine::SoftwareCategory`, adminban szerkeszthető —, nem kódba írt lista. Egy szoftver `status`-a ismert kulcs kell legyen: a migráció a négyet seedeli, és a 0.13.1-től a modell maga teszi vissza őket egy üres táblába az első használatnál — egy `db:schema:load`-dal, nem migrációval épült adatbázisban, ami minden teszt-adatbázis, különben nem lenne egy sor sem. Egy publish által hozott, még el nem nevezett kategória a kulcsával mint címkével és `needs_review` jelzéssel kerül a szótárba. A `GET /api/software/vocabulary` mindkettőt publikálja, az oldal katalógusa és a WarpEngine Client ebből veszi a címkéket.

## Titkok és verziók

Minden generált pipeline egyetlen Woodpecker-secretet vár, az `application_token` értéket, amelyet `X-Update-Secret` fejlécként küld a `/build/upload` és a `/build/publish` végpontokra.

A `tag` lépés (1.9) a `gitea_token` secrettel ír tageket: ez egy Forgejo-token, írásjoggal a repóhoz (GitLabon egy `TAG_TOKEN` CI/CD-változó, `api` jogú access token). A teletypegames.org-on ez globális Woodpecker-secret, így egy új repónak ehhez nem kell semmi.

A verziózás mindenhol ugyanaz, mert az 1.7 óta a `version` lépést maga az engine adja hozzá (lásd lent).

## Lépések, amelyek minden pipeline-ban közösek

Az 1.7 óta ezek a lépések egyik sablonban sincsenek benne; az engine minden renderelt pipeline-hoz hozzáadja őket:

- **`version`**, elsőként. A verzió a `metadata.json` fájlból jön (TIC-80 esetén a Lua-fejléc `-- version:` sorából). Tagnél egy verziótagnek (`v1.2.3` vagy `1.2.3`) egyeznie kell vele, és ha a forrás nem nevez meg verziót, a tag a verzió; ha nem egyezik, a pipeline elbukik. A repó alapágán kívül `dev-<version>-<branch>` lesz belőle, pl. `dev-1.0.0-my-feature`, és ugyanígy egy `dev-<ág>` tagen; verzió nélkül a build `dev-<commit>`, ami sosem a legfrissebb. Az 1.9 óta az alapág a repó saját alapága (`CI_REPO_DEFAULT_BRANCH`), így egy `develop` alapágú repó a `develop`-ról ad ki; korábban ez a `main` vagy a `master` volt.
- **`tag`** (1.9), a push utáni build végén, hacsak a jelölő nem mondja, hogy `release: push`: új verziót tagel, vagy áthelyezi az ág dev tagjét. Lásd: *Verziók és kiadások*.
- **`release`**, utolsóként, verziótagre (`dev-` tagre nem készül), ha a hoszt a platform bejegyzésében `forge_release: true`-t állít (bármelyik platform kaphatja): a tag Gitea/Forgejo-kiadása (GitLabon GitLab-kiadás), a cím és a tag nevével, a `RELEASE_NOTES.md` szövegével, a `gitea_token` secrettel. Azokat a fájlokat csatolja, amelyeket a build a `.release-assets`-ben felsorolt, különben a feltöltési lépés által feltöltötteket. A teletypegames.org-on csak a `dos`-nál van bekapcsolva.

Az 1.9.1 óta egy generált Woodpecker-pipeline a `release` kivételével minden eseményre elindul (egy workflow-szintű `when`, amely elmarad, ha a sablonnak van sajátja): egy dev tag áthelyezésekor a Forgejo a tag mellett `release` eseményt is küld, és ez ugyanazt a commitot még egyszer lebuildelte.

Minden sablon ugyanúgy nevezi a lépéseit: amelyik a fájlokat a `/build/upload`-ra küldi, az az `upload` (a c64, a plus4, az ebitengine és a dos az 1.7 előtt `artifact`-nak hívta), amelyik a `/build/publish`-t hívja, az a `publish`. A `c64`-en és a `plus4`-en csak a ténylegesen elkészült lejátszók töltődnek fel; a LÖVE már nem tölt fel `.love.zip`-et.

## Verziók és kiadások

Az 1.9 óta senki nem tagel és senki nem publikál kézzel: mindenről a forrásban lévő verzió dönt.

1. **A verzió a forrásban van** — a `metadata.json` `version` mezője, TIC-80-on a `-- version:` sor. Az emelése maga a kiadás.
2. **Egy új verziójú push az alapágra** buildel, és a `tag` lépés `v<verzió>` taget tesz a pusholt commitra (a forge API-ján). Ez a tag saját pipeline-t indít, amely feltölt és publikál; ahol a platformon `forge_release` van, forge-kiadás is készül.
3. **Egy már tagelt verziójú push** csak buildel. Verzióemelés nélküli commit nem ad ki semmit.
4. **Kézzel készített tag** a korábbiak szerint működik: egyeznie kell a forrás verziójával, különben a `version` lépés elbuktatja a pipeline-t, a `tag` lépés pedig a már létező taget nem bántja.
5. **A verzió csak nőhet.** A `/build/publish` `409`-et ad a már kiadott legújabbnál régebbi verzióra (`Gem::Version` szerint): az lenne a legfrissebb kiadás, és visszahozná a régi metaadatait. Ugyanaz a verzió újra, egy újabb, vagy bármilyen `dev-` verzió átmegy.
6. **A dev buildek mindig követik az ágat.** Az alapágon kívül a verzió `dev-<verzió>-<ág>`; ha a forrásban nincs verzió, `dev-<commit>`. Egy ilyen buildnél a `tag` lépés **áthelyezi** az ág `dev-<ág>` tagjét a pusholt commitra (először létrehozza), és a tag pipeline-ja publikálja a dev buildet, felülírva az előző azonos nevűt. Egy dev verzió sosem egy cím legfrissebb kiadása, és nem készül belőle forge-kiadás.
7. **Kilépés:** a jelölőben a `release: push` minden pushra, tag nélkül publikál, ahogy az 1.9 előtt. A DOS nem léphet ki.

Minden engine-nel épülő teletypegames.org-os repó korábbi verziói 2026-10-27-én egy menetben megkapták a tagjeiket (`script/backfill_version_tags.rb` a monorepóban: minden verzió tagje azon az első alapági commiton, amelyben megjelent), így a tagek egy repó teljes történetét mutatják.

## Amit egy sablon a hoszttól kap

Az 1.5 óta a sablonokban nincs egyetlen hoszt címe sem. Ami hosztonként más, azt a CI adapter `platforms:` beállításában a platform bejegyzése hordozza:

- `web_shell:` — az oldal, amelybe egy bevy-, ebitengine- vagy phaser-féle böngészős build kerül. URL-lel a pipeline letölti; anélkül a repó saját `web/index.html`-jét veszi.
- `bundle_id_prefix:` — a LÖVE macOS-alkalmazás bundle-azonosítója `<prefix>.<name>` (alapból `org.warpengine`).
- `exporter:` — a `c64`-en és a `plus4`-en az az image, amelyben a `commodore-pack <c64|plus4> <name> <version>` van; ez a PRG mellé megírja a böngészős és a három natív lejátszót.

A mi hosztjaink a Teletype Games értékeit állítják be, így a pipeline-jaik ugyanúgy renderelődnek, mint korábban.

## Saját platformok

A hoszt az initializerben vehet fel platformot — egy szolgáltatásosztállyal, a CI által épített kindokkal és CI-adapterenként egy pipeline-sablonnal:

```ruby
c.register_platform "amiga",
                    service:     "AmigaPlatform",
                    built_kinds: %w[cartridge],
                    templates:   { woodpecker: Rails.root.join("ci/amiga.woodpecker.yaml.erb") }
```

A szolgáltatás a `WarpEngine::Platforms::Updatable`-t és a kindjai build-concernjeit húzza be, és megnevezi magát (`platform "amiga"`, `label "Amiga"`, `required_kinds "cartridge"`). Innentől ugyanolyan platform, mint a többi: szoftver lehet rajta, a publikálás az ő szolgáltatását futtatja, az admin listázza, a configuration extension az ő sablonját rendereli. Ami korábban a rögzített listát olvasta, az most a `WarpEngine::Platform.names`-t.

## Polcok

A polc (1.2 óta) egy mappa, amelyet az engine szinkronban tart a katalógussal: egy platformhoz minden nyilvános cím legfrissebb kiadása rögzített, verzió nélküli néven, plusz egy `INDEX.TXT` (`file|version|size|crc32|title|author|description`, ASCII, soronként legfeljebb 255 bájt). Azoknak a klienseknek szól, amelyek nem tudják HTTPS-en olvasni a JSON API-t. A `c.shelves` adja meg a platformot, a kindot, az útvonalat, a kiterjesztést és a névhosszt; a `:local` storage adapter kell hozzá. A fájlt a szoftver `shelf_name`-je nevezi meg (a `metadata.json`-ból, az adminból vagy az Admin API-ból). A polc minden publikáláskor és a címei minden változásakor újraépül, az index cserélődik utoljára; a `rake warp_engine:shelves:check` és a `warp_engine:shelves:sync` kézzel ellenőrzi és újraépíti.

Az 1.7 óta egy polc `layout: "folders"` is lehet: címenként egy mappa, abban az elrendezésben, amelyet a [DOSINST](https://wiki.teletypegames.org/hu/dos/dosinst) olvas. Az engine kicsomagolja a kiadási zip legfelső mappáját, kihagyja belőle a metaadat `dos` szakaszának `settings` fájljait (így egy frissítés megtartja a telepített program beállításait), és két fájlt ír minden mappába: `INFO.INI` (`[program]` a `name`, `description`, `version` és `platform` — `DOS` vagy `WIN32` — kulcsokkal, CRLF) és `FILES.LST` (`crc32 size path`, CRLF). A `kind` lista is lehet: a DOS-polcon a `cartridge` és a `win_x86` van, így például a DOSINST-nek egy `DOSINST` és egy `DOSINSTW` mappája van. Csak azokat a mappákat törli valaha, amelyeket a polc `INDEX.TXT`-je felsorolt, így egy kézzel odatett mappa (például egy tesztprogram) megmarad.

A polcnak `urls` mezője is van, hogy a hoszt hol szolgálja ki, és a `GET /api/service` a `shelves` alatt listázza a polcokat (platform, kindok, elrendezés, index, címek). A kiszolgálás továbbra is a hoszt dolga: az engine megírja a mappát, saját FTP- vagy HTTP-szervert nem futtat.

A teletypegames.org három polcot tart, ezeket az [FTP-szerver](https://wiki.teletypegames.org/hu/infrastructure/ftp-server) szolgálja ki FTP-n és sima HTTP-n:

| Polc | Mappa | Elrendezés | Cím |
|---|---|---|---|
| C64 | `shelves/C64` | lapos | `ftp://ftp.teletypegames.org/C64`, `http://archive.teletypegames.org/C64` |
| Plus/4 | `shelves/PLUS4` | lapos | `ftp://ftp.teletypegames.org/PLUS4`, `http://archive.teletypegames.org/PLUS4` |
| DOS | `shelves/APPS` | mappás | `ftp://ftp.teletypegames.org/APPS`, `http://archive.teletypegames.org/APPS` |

# CI-kezelés (Woodpecker API)

Csak ott aktív, ahol az adapternek van `url` és `api_token` értéke (`WarpEngine.ci.configured?`). Addig az adminoldalak elrejtik magukat.

- **Repószinkron** — *Admin → Pipelines → Sync from Woodpecker* (a gomb az adapter nevét viseli). Tükrözi a repólistát `Pipeline` rekordokba, név alapján párosítja az egyes repókat egy `Software` rekorddal, és örökli annak platformját. A Woodpeckerből eltűnt repók deaktiválódnak. A linkek kézzel szerkeszthetők maradnak.
- **Pipeline-ok** — minden bejegyzés felsorolja a legutóbbi futásait (státusz, ág, üzenet, idő) egy kézi **Trigger** gombbal. A legfrissebb futás frissíti a listaoldalon gyorsítótárazott státuszt. Egy szoftver adminoldala hivatkozik a pipeline-jaira.
- **Secret-kiosztás** — egy token létrehozása kiosztja az `application_token` secretet a tulajdonos repóira (korlátozás nélküli tokenek esetén minden aktív repóra); a törlés eltávolítja; a **Rotate** létrehoz egy cserét, mindenhova kiosztja, és egy lépésben visszavonja a régit.

A PAT-nak Woodpecker-**példányadminhoz** kell tartoznia — a repók listázása csak admin által hívható végpont, minden más `403 User not authorized` választ ad. Vedd fel a felhasználót a `WOODPECKER_ADMIN` listába, majd **lépj ki és vissza**: az adminjelző a bejelentkezéskor íródik, a szerver újraindítása nem elég.

# Buildhitelesítés

Az `X-Update-Secret` kétféle hitelesítő adat egyikét viszi, amit az `application_token_source` választ ki. A módok kizárják egymást.

| Mód | Hitelesítő adat |
|---|---|
| `:env` (alapérték) | a közös titok az `update_secret` értékben; ha nincs beállítva, minden kérés elutasításra kerül |
| `:database` | `WarpEngine::ApplicationToken` rekordok: tulajdonos (pl. `AdminUser`), jogosultságok (`update` a publikáláshoz, `upload` a feltöltéshez), opcionális lejárat. Az adminban jönnek létre; a nyílt token egyszer jelenik meg, csak a SHA256-lenyomata tárolódik. A `last_used_at` követi a használatot. |

> **Cutover warning:** a `:database` módra váltás azonnal érvényteleníti a shared secret-et. Előbb hozd létre a token-eket.

# Telepítés egy hosztba

```ruby
# Gemfile — gitből:
gem "warp_engine", git: "https://git.teletypegames.org/engines/warp_engine.git"

# ...vagy a Forgejo rubygems-regiszteréből (taggelt kiadások):
source "https://git.teletypegames.org/api/packages/engines/rubygems" do
  gem "warp_engine"
end
```

```sh
rails g warp_engine:install   # initializer, képtár, migrációk
rails db:migrate
```

```ruby
# config/routes.rb
namespace :api do
  get "image/:id", to: "images#show"   # a képtár a tiéd (lásd lentebb)
end

# a mount maradjon az utolsó, hogy a hoszt saját útvonalai nyerjenek
mount WarpEngine::Engine => "/"
```

A generátor kiírja a `config/initializers/warp_engine.rb` fájlt. Az 1.8 óta ez egy paraméter nélküli blokk, beállításonként egy sorral:

```ruby
Rails.application.config.to_prepare do
  WarpEngine.configure do
    file_container_path ENV.fetch("FILE_CONTAINER_PATH", "/softwares")

    # The image library is the host's; "Image" is the adapter's default.
    # image_class "Media::Picture"
    # image_adapter MyImageLibrary.new

    update_secret ENV["UPDATE_SECRET"]

    # application_tokens from: :database, owner: "AdminUser"

    # One adapter carries the whole CI side: server, recipes, signing key.
    ci :woodpecker do
      url            ENV["WOODPECKER_URL"]
      api_token      ENV["WOODPECKER_API_TOKEN"]
      repo_owner     ENV["WOODPECKER_REPO_OWNER"]
      public_key_url "https://ci.example.org/api/signature/public-key"
      update_server  "https://catalog.example.org"
      tools          "registry.example/ci-tools:1.0"   # every platform's, unless it names its own (1.1)

      platform :godot,      builder: "registry.example/godot-builder:4.6"
      platform :tic80,      builder: "registry.example/tic80-builder:1.0"
      platform :bevy,       builder: "registry.example/bevy-builder:1.0"
      platform :love,       builder: "registry.example/love-builder:1.0"
      platform :ebitengine, builder: "registry.example/ebitengine-builder:1.0"
      platform :phaser,     builder: "registry.example/phaser-builder:1.0"
      platform :c64,        builder: "registry.example/c64-builder:1.0", exporter: "registry.example/commodore-player:1.0"
      platform :plus4,      builder: "registry.example/plus4-builder:1.0", exporter: "registry.example/commodore-player:1.0"
    end

    # max_upload_size 500 * 1024 * 1024
    # enforce_software_ownership true
  end
end
```

A blokk a `shelf …`, az `admin_api do … end` és az `additional_resource … do … end` sort is ismeri (engedélyezett attribútumok, az adminűrlap, és egy `string`/`integer`/… sorokból épülő, camelCase `api_schema`). Ismeretlen beállítás vagy kulcs megállítja az indulást. A beállítások neve az alábbi táblázaté, kivéve az `image_class` (`image_class_name`), az `application_tokens from:, owner:` (a két `application_token_*` kulcs) és a `ci` (`ci_adapter`) sort. Az 1.8 előtti `WarpEngine.configure do |c| c.site_url = … end` forma is működik; mindkettő ugyanazt a konfigurációt írja.

| Kulcs | Alapérték | Leírás |
|---|---|---|
| `file_container_path` | `ENV["FILE_CONTAINER_PATH"]` vagy `/softwares` | Hol tárolódnak a buildartifactok |
| `update_secret` | `ENV["UPDATE_SECRET"]` | Közös titok a `/build/*` végpontokhoz |
| `application_token_source` | `:env` | `:env` vagy `:database`, kizárólagosan |
| `application_token_owner_class` | `nil` | Tulajdonosmodell a `:database` módhoz |
| `max_upload_size` | 500 MB | Korlát a `/build/upload` végponthoz |
| `enforce_software_ownership` | `false` | Adatbázistoken-tulajdonos szerinti elszigetelés |
| `ci_adapter` | `:woodpecker` | Melyik CI-szerver építi a játékokat; a `:none` kikapcsolja a CI-t, vagy egy hosztobjektum |
| `image_class_name` | `"Image"` | A képtár mögötti hosztmodell |
| `image_adapter` | `nil` | Lecseréli az alapértelmezett hosztmodell-adaptert |
| `access_policy` | `:open` | Hozzáférési szabályzat objektum |
| `access_token_owner_class` | `nil` | Felhasználómodell az eszközbelépéshez; nil = nincs belépés |
| `admin_api.*` | `enabled = false` | Az [Admin API](https://wiki.teletypegames.org/hu/engines/warp-engine/admin-api) és az MCP-szervere (1.0) |

Egy platform-spec három image-et kaphat. A `builder` futtatja a build lépéseket. Az `exporter` egy második image, amelyben egy sablon lépést futtathat — a c64 és a plus4 pipeline itt csomagolja a `.prg`-t a böngészős és a natív lejátszókba. A `tools` (1.1, opcionális) a segédlépéseké: version, lint, minify, docs, upload, publish. Nélküle ezek `alpine`-ban futnak, és minden futáskor `apk add`-dal felrakják a git, jq, curl, zip és lua5.4 csomagot, a tic80-nál `luarocks install`-lal a luacheck-et és az ldoc-ot is; vele semmit nem telepítenek, tehát az image-ben ezeknek már benne kell lenniük, a Lua 5.4-gyel `lua` néven. A DSL-ben a `ci` blokk `tools` sora minden platform alapértéke. Az upload és a publish lépés megkapja az `application_token` secretet, ezért a tools image-re digest szerint hivatkozz. A teletypegames.org így adja minden platformnak a `build/ci-tools` image-ét.

# Adminintegráció

Az ActiveAdmin-példány a hoszté (hitelesítés, téma, útvonalak); a WarpEngine hozzáfűzi az erőforrásait az `ActiveAdmin.application.load_paths` listához. Egy új hosztnak két dolog kell:

- a fájlválasztó JS a kiadási assetek útvonalmezőihez (iframe → `/admin/files?picker=1&field=<dom_id>`) az `active_admin.js` fájljában;
- apipie-dokumentáció esetén a `"#{WarpEngine::Engine.root}/app/controllers/**/*.rb"` bejegyzés az `api_controllers_matcher` beállításban.

Az 1.0 óta ugyanezek az erőforrások JSON API-ként is elérhetők — lásd [WarpEngine Admin API](https://wiki.teletypegames.org/hu/engines/warp-engine/admin-api). Az adminoldalak és az API ugyanazokat a service-eket hívják (`ApplicationTokenService`, `DownloadStatsService`, `FileManagerService`), így a kettő nem tud elcsúszni egymástól.

# Viselkedési megjegyzések

- Minden modell soft delete-tel dolgozik: `deleted_at` oszlop, és `kept` / `discarded` scope. A 0.10 óta nincs `default_scope`, egy lekérdezés tehát magától nem szűr — a hoszt `WarpEngine::Software.kept`-et kér, és aki 0.9-ről vagy régebbiről frissít, `.kept`-et tesz oda, ahol a default scope-ra épített.
- A JSON shape stabil, és bug-compatible a korábbi Go backenddel: Go-féle nulla timestamp-ek, camelCase kulcsok, legacy flat path field-ek.
- Model extension point-ok: `ActiveSupport.on_load(:warp_engine_<model>)`.

# Tesztelés

```sh
bundle install
bundle exec rake app:db:prepare RAILS_ENV=test
bundle exec rspec
```

'; var prevLevel = 2; for (var i = 0; i < headings.length; i++) { var h = headings\[i\]; var text = h.textContent.trim(); var slug = text.toLowerCase() .replace(/\[áàâä\]/g,'a').replace(/\[éèêë\]/g,'e').replace(/\[íìîï\]/g,'i') .replace(/\[óòôöő\]/g,'o').replace(/\[úùûüű\]/g,'u') .replace(/\[^a-z0-9\]+/g, '-').replace(/^-|-$/g, ''); if (!slug) slug = 'heading-' + i; if (used\[slug\]) { slug += '-' + i; } used\[slug\] = true; h.id = slug; var level = parseInt(h.tagName.charAt(1)); var cls = level > 2 ? ' class="toc-child"' : ''; html += '- [' + text + '](#'%20+%20slug%20+%20')
'; } html += '
'; toc.innerHTML = html; // Scroll-spy: highlight current heading var links = toc.querySelectorAll('a'); var ids = \[\]; for (var j = 0; j < links.length; j++) ids.push(links\[j\].getAttribute('href').slice(1)); function updateActive() { var current = ''; for (var k = 0; k < ids.length; k++) { var el = document.getElementById(ids\[k\]); if (el && el.getBoundingClientRect().top <= 80) current = ids\[k\]; } for (var m = 0; m < links.length; m++) { links\[m\].parentNode.classList.toggle('active', links\[m\].getAttribute('href') === '#' + current); } } window.addEventListener('scroll', updateActive, { passive: true }); updateActive(); })();

---

## Navigation

- Parent: [Motorok](https://wiki.teletypegames.org/hu/engines.md)
- Previous: [RuBBS](https://wiki.teletypegames.org/hu/engines/rubbs.md)
- Next: [warpstore](https://wiki.teletypegames.org/hu/engines/warpstore.md)
- Children:
  - [WarpEngine Admin API](https://wiki.teletypegames.org/hu/engines/warp-engine/admin-api.md)
  - [WECP](https://wiki.teletypegames.org/hu/engines/warp-engine/wecp.md)
