diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml index 87f7794..acf55c6 100644 --- a/.gitea/workflows/build.yml +++ b/.gitea/workflows/build.yml @@ -1,6 +1,7 @@ -# Сборка на каждый пуш: мод (.hmod, для всех ОС) и игра-форк для Linux, в одной задаче -# (без передачи артефактов между задачами). Пуш в main публикует релиз build-<номер>. -# Сборки для Windows и macOS — desktop.yml (вручную). +# На каждый пуш: только мод (.hmod, один для всех ОС) и релиз build-<номер> с ним. +# Раннер живёт на одной машине с Gitea и другими сервисами (2 ядра, 4 ГБ): сборка мода лёгкая — +# один поток компиляции, низший приоритет, без системных пакетов и без кэша target. +# Игра-форк собирается отдельно и только вручную: game.yml (Linux) и desktop.yml (macOS/Windows). name: build on: @@ -9,55 +10,28 @@ on: env: CARGO_TERM_COLOR: never - # The runner shares its machine with Gitea: two compile jobs at a time, no incremental - # files, so the build doesn't starve the server (Gitea answered 500 under a full build). - CARGO_BUILD_JOBS: "2" + CARGO_BUILD_JOBS: "1" CARGO_INCREMENTAL: "0" - # Lowest CPU priority for the long steps (ionice too where the image has it, below). - LOW: nice -n 19 jobs: - build: + mod: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Системные библиотеки - run: | - SUDO=$(command -v sudo || true) - $SUDO apt-get update -qq - $SUDO apt-get install -y -qq build-essential pkg-config libasound2-dev libudev-dev - - name: Низкий приоритет - run: command -v ionice >/dev/null && echo "LOW=nice -n 19 ionice -c 3" >> "$GITHUB_ENV" || true - name: Rust run: | curl -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --target wasm32-unknown-unknown echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" - # Only the small things are cached: the build's target folder is gigabytes, and moving it - # through the cache server timed out and loaded the machine. + # Инструмент упаковки (cargo-horizon-mod без пробного запуска) — единственное, что стоит кэшировать. - uses: actions/cache@v3 continue-on-error: true with: - path: | - ~/.cargo/registry/index - ~/.cargo/registry/cache - ~/.cargo/git/db - key: cargo-${{ hashFiles('HORIZON_COMMIT') }} - restore-keys: cargo- + path: ~/.cargo/bin/cargo-horizon-mod + key: tool-${{ hashFiles('HORIZON_COMMIT', 'horizon-patches/*') }} - name: Форк Horizon - run: $LOW python3 tools/airdef.py fork + run: nice -n 19 python3 tools/airdef.py fork - name: Мод - run: $LOW python3 tools/airdef.py mod - - name: Игра - run: $LOW python3 tools/airdef.py game - - name: Архивы - run: | - mkdir -p out - cp airdef-mod/target/horizon-mod/airdef.hmod out/airdef.hmod - D=airdef-linux-x86_64 - mkdir -p "$D" - cp horizon/target/fast/horizon-client horizon/target/fast/horizon-ctl out/airdef.hmod dist/play.sh docs/INSTALL.md "$D/" - tar czf "out/$D.tar.gz" "$D" - ls -la out + run: nice -n 19 python3 tools/airdef.py mod - name: Релиз if: github.ref == 'refs/heads/main' env: @@ -67,17 +41,14 @@ jobs: SHA: ${{ github.sha }} run: | set -e - BODY=$(printf 'Сборка %s (%s).\n\n- airdef.hmod — мод, для всех ОС;\n- airdef-linux-x86_64.tar.gz — игра-форк Horizon для Linux с модом и play.sh.\n\nСборки для Windows и macOS добавляет workflow desktop (запуск вручную с этим тегом).\nКак запускать: docs/INSTALL.md.' "$TAG" "$SHA") + mkdir -p out && cp airdef-mod/target/horizon-mod/airdef.hmod out/airdef.hmod + BODY=$(printf 'Сборка %s (%s).\n\n- airdef.hmod — мод, для всех ОС.\n\nИгра-форк Horizon (нужна для мода): собрать у себя — python3 tools/airdef.py all, или запустить вручную workflow game (Linux) / desktop (macOS, Windows) с этим тегом.\nКак запускать: docs/INSTALL.md.' "$TAG" "$SHA") DATA=$(BODY="$BODY" python3 -c 'import json,os;print(json.dumps({"tag_name":os.environ["TAG"],"target_commitish":os.environ["SHA"],"name":os.environ["TAG"],"body":os.environ["BODY"]}))') if [ -z "$TOKEN" ]; then echo "::error::нет токена: добавь секрет RELEASE_TOKEN"; exit 1; fi CODE=$(curl -sS -o release.json -w '%{http_code}' -X POST -H "Authorization: token $TOKEN" -H "Content-Type: application/json" "$API/releases" -d "$DATA") if [ "$CODE" != "201" ]; then - echo "::error::Gitea не создала релиз (HTTP $CODE): $(cat release.json). Встроенному токену может не хватать прав — добавь секрет RELEASE_TOKEN (токен с правом записи в репозиторий)." + echo "::error::Gitea не создала релиз (HTTP $CODE): $(cat release.json). Проверь секрет RELEASE_TOKEN (токен с правом записи в репозиторий)." exit 1 fi ID=$(python3 -c 'import json;print(json.load(open("release.json"))["id"])') - for f in out/*; do - name=$(basename "$f") - echo "upload $name" - curl -sf -X POST -H "Authorization: token $TOKEN" -F "attachment=@$f" "$API/releases/$ID/assets?name=$name" > /dev/null - done + curl -sf -X POST -H "Authorization: token $TOKEN" -F "attachment=@out/airdef.hmod" "$API/releases/$ID/assets?name=airdef.hmod" > /dev/null diff --git a/.gitea/workflows/game.yml b/.gitea/workflows/game.yml new file mode 100644 index 0000000..4306403 --- /dev/null +++ b/.gitea/workflows/game.yml @@ -0,0 +1,56 @@ +# Игра-форк для Linux: только вручную (Actions → game → Run workflow) с тегом готового релиза +# (build-N), дописывает в него airdef-linux-x86_64.tar.gz. Тяжёлая сборка (час на 2 ядрах, +# несколько ГБ диска и памяти), поэтому один поток, низший приоритет, и отказ, если на сервере +# мало места: забитый диск роняет Gitea. +name: game + +on: + workflow_dispatch: + inputs: + tag: + description: Тег релиза, например build-7 + required: true + +env: + CARGO_TERM_COLOR: never + CARGO_BUILD_JOBS: "1" + CARGO_INCREMENTAL: "0" + +jobs: + game-linux: + runs-on: ubuntu-latest + steps: + - name: Место на диске + run: | + FREE=$(df --output=avail -BG / | tail -1 | tr -dc 0-9) + echo "свободно: ${FREE} ГБ" + if [ "$FREE" -lt 15 ]; then echo "::error::на сервере свободно ${FREE} ГБ, нужно хотя бы 15: сборка игры забьёт диск и уронит Gitea"; exit 1; fi + - uses: actions/checkout@v4 + - name: Системные библиотеки + run: | + SUDO=$(command -v sudo || true) + $SUDO apt-get update -qq + $SUDO apt-get install -y -qq --no-install-recommends build-essential pkg-config libasound2-dev libudev-dev + - name: Rust + run: | + curl -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --target wasm32-unknown-unknown + echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" + - name: Форк Horizon + run: nice -n 19 python3 tools/airdef.py fork + - name: Мод + run: nice -n 19 python3 tools/airdef.py mod + - name: Игра + run: nice -n 19 python3 tools/airdef.py game + - name: В релиз + env: + TOKEN: ${{ secrets.RELEASE_TOKEN || gitea.token }} + API: ${{ github.server_url }}/api/v1/repos/${{ github.repository }} + TAG: ${{ inputs.tag }} + run: | + set -e + D=airdef-linux-x86_64 + mkdir -p "$D" out + cp horizon/target/fast/horizon-client horizon/target/fast/horizon-ctl airdef-mod/target/horizon-mod/airdef.hmod dist/play.sh docs/INSTALL.md "$D/" + tar czf "out/$D.tar.gz" "$D" + ID=$(curl -sf -H "Authorization: token $TOKEN" "$API/releases/tags/$TAG" | python3 -c 'import json,sys;print(json.load(sys.stdin)["id"])') + curl -sf -X POST -H "Authorization: token $TOKEN" -F "attachment=@out/$D.tar.gz" "$API/releases/$ID/assets?name=$D.tar.gz" > /dev/null diff --git a/docs/INSTALL.md b/docs/INSTALL.md index ec3f1af..958e424 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -19,13 +19,14 @@ AirDef работает на своей сборке (форке) Horizon: в н ## 2а. Готовая сборка из релиза (проще) -В репозитории → **Releases** → последний `build-N`. Там лежат: +В репозитории → **Releases** → последний `build-N`. В каждом релизе есть `airdef.hmod` — сам мод (один для всех +ОС). Игру-форк сервер сборок не собирает сам: это тяжело для него. Архивы с игрой появляются в релизе, только если +их собрали вручную: +- `airdef-linux-x86_64.tar.gz` — Actions → **game** → Run workflow, указать тег релиза; +- `airdef-windows-x86_64.zip`, `airdef-macos-arm64.tar.gz` — Actions → **desktop** (нужны раннеры с метками + `windows-latest` / `macos-latest`). -- `airdef.hmod` — сам мод; -- `airdef-windows-x86_64.zip`, `airdef-macos-arm64.tar.gz`, `airdef-linux-x86_64.tar.gz` — игра-форк + мод + - скрипт запуска. Сборка для Linux есть всегда; для Windows и macOS её добавляет workflow **desktop** - (Actions → desktop → Run workflow, указать тег релиза), если подключены раннеры с метками - `windows-latest` / `macos-latest`. Нет такой сборки — способ 2б. +Нет архива для твоей ОС — способ 2б: игра соберётся у тебя, а мод можно взять из релиза или собрать там же. **Windows:** распакуй zip куда угодно и запусти `play.bat`. Он положит мод в папку игры, включит его и запустит игру. Конкретный мир: `play.bat --world "Имя мира"` (в командной строке, из папки архива). diff --git a/horizon-patches/0001-Mod-API-AirDef-fork-world-models-client-particles-so.patch b/horizon-patches/0001-Mod-API-AirDef-fork-world-models-client-particles-so.patch index 4f91e87..1ada8de 100644 --- a/horizon-patches/0001-Mod-API-AirDef-fork-world-models-client-particles-so.patch +++ b/horizon-patches/0001-Mod-API-AirDef-fork-world-models-client-particles-so.patch @@ -1,7 +1,7 @@ From 3de7a6875140a107e28563cc44489b55f5794517 Mon Sep 17 00:00:00 2001 From: chexit Date: Wed, 28 Oct 2026 12:37:45 +0300 -Subject: [PATCH 1/9] Mod API (AirDef fork): world models, client particles, +Subject: [PATCH 01/10] Mod API (AirDef fork): world models, client particles, sounds, ground heights render.set-models draws block models with an interpolated pose per id; diff --git a/horizon-patches/0002-cargo-horizon-mod-report-particles-and-sounds.patch b/horizon-patches/0002-cargo-horizon-mod-report-particles-and-sounds.patch index 6cb0c6f..3494444 100644 --- a/horizon-patches/0002-cargo-horizon-mod-report-particles-and-sounds.patch +++ b/horizon-patches/0002-cargo-horizon-mod-report-particles-and-sounds.patch @@ -1,7 +1,7 @@ From a6d64e6415bf618539af464e521091e72b72d72f Mon Sep 17 00:00:00 2001 From: chexit Date: Wed, 28 Oct 2026 12:39:45 +0300 -Subject: [PATCH 2/9] cargo-horizon-mod: report particles and sounds +Subject: [PATCH 02/10] cargo-horizon-mod: report particles and sounds Co-Authored-By: Claude Opus 5.5 --- diff --git a/horizon-patches/0003-Mod-API-AirDef-fork-full-screen-post-pass-for-mods.patch b/horizon-patches/0003-Mod-API-AirDef-fork-full-screen-post-pass-for-mods.patch index 007ba5b..f5ad6fe 100644 --- a/horizon-patches/0003-Mod-API-AirDef-fork-full-screen-post-pass-for-mods.patch +++ b/horizon-patches/0003-Mod-API-AirDef-fork-full-screen-post-pass-for-mods.patch @@ -1,7 +1,7 @@ From ee79b3562feebe51959a01f1585a9e85bd53ccb5 Mon Sep 17 00:00:00 2001 From: chexit Date: Wed, 28 Oct 2026 13:33:10 +0300 -Subject: [PATCH 3/9] Mod API (AirDef fork): full-screen post pass for mods +Subject: [PATCH 03/10] Mod API (AirDef fork): full-screen post pass for mods render.set-post compiles a mod's WGSL after a prelude (camera matrices, time, 256 vec4 of params, the world's colour and depth) and runs it after diff --git a/horizon-patches/0004-Mod-models-draw-far-models-as-scaled-impostors-past-.patch b/horizon-patches/0004-Mod-models-draw-far-models-as-scaled-impostors-past-.patch index 370eb33..1d66f0c 100644 --- a/horizon-patches/0004-Mod-models-draw-far-models-as-scaled-impostors-past-.patch +++ b/horizon-patches/0004-Mod-models-draw-far-models-as-scaled-impostors-past-.patch @@ -1,8 +1,8 @@ From 2b137dbbedd540daeebc7f5e387847aeb56a1355 Mon Sep 17 00:00:00 2001 From: chexit Date: Wed, 28 Oct 2026 22:09:30 +0300 -Subject: [PATCH 4/9] Mod models: draw far models as scaled impostors past the - far plane, cull at 4096 +Subject: [PATCH 04/10] Mod models: draw far models as scaled impostors past + the far plane, cull at 4096 --- crates/horizon-client/src/game/mod_models.rs | 20 ++++++++++++++++---- diff --git a/horizon-patches/0005-Mod-cameras-render.set-views-draws-the-world-from-ot.patch b/horizon-patches/0005-Mod-cameras-render.set-views-draws-the-world-from-ot.patch index ce1db24..9e542d5 100644 --- a/horizon-patches/0005-Mod-cameras-render.set-views-draws-the-world-from-ot.patch +++ b/horizon-patches/0005-Mod-cameras-render.set-views-draws-the-world-from-ot.patch @@ -1,8 +1,8 @@ From c8ebca6e0af68630e65211844b34d25c5b985c03 Mon Sep 17 00:00:00 2001 From: chexit Date: Wed, 28 Oct 2026 22:31:00 +0300 -Subject: [PATCH 5/9] Mod cameras: render.set-views draws the world from other - points into pictures that HUD view shapes show (API level 9) +Subject: [PATCH 05/10] Mod cameras: render.set-views draws the world from + other points into pictures that HUD view shapes show (API level 9) Co-Authored-By: Claude Opus 5.5 --- diff --git a/horizon-patches/0006-Mods-more-fuel-per-tick-and-per-event-AirDef-plans-r.patch b/horizon-patches/0006-Mods-more-fuel-per-tick-and-per-event-AirDef-plans-r.patch index 9bc422c..3b05dbc 100644 --- a/horizon-patches/0006-Mods-more-fuel-per-tick-and-per-event-AirDef-plans-r.patch +++ b/horizon-patches/0006-Mods-more-fuel-per-tick-and-per-event-AirDef-plans-r.patch @@ -1,7 +1,7 @@ From 8d2261fc4f8072c32e454139cfea7a8c0cd9d664 Mon Sep 17 00:00:00 2001 From: chexit Date: Wed, 28 Oct 2026 23:08:21 +0300 -Subject: [PATCH 6/9] Mods: more fuel per tick and per event (AirDef plans +Subject: [PATCH 06/10] Mods: more fuel per tick and per event (AirDef plans raids) Co-Authored-By: Claude Opus 5.5 diff --git a/horizon-patches/0007-Mod-cameras-up-to-1920x1080-full-screen-replays.patch b/horizon-patches/0007-Mod-cameras-up-to-1920x1080-full-screen-replays.patch index ad81800..855b1af 100644 --- a/horizon-patches/0007-Mod-cameras-up-to-1920x1080-full-screen-replays.patch +++ b/horizon-patches/0007-Mod-cameras-up-to-1920x1080-full-screen-replays.patch @@ -1,7 +1,7 @@ From 6c548de34def8e4a624d734243418090798bd16a Mon Sep 17 00:00:00 2001 From: chexit Date: Wed, 28 Oct 2026 23:25:29 +0300 -Subject: [PATCH 7/9] Mod cameras up to 1920x1080 (full-screen replays) +Subject: [PATCH 07/10] Mod cameras up to 1920x1080 (full-screen replays) Co-Authored-By: Claude Opus 5.5 --- diff --git a/horizon-patches/0008-Mod-cameras-distant-terrain-tiles-fog-measured-from-.patch b/horizon-patches/0008-Mod-cameras-distant-terrain-tiles-fog-measured-from-.patch index 3d477eb..3eaf703 100644 --- a/horizon-patches/0008-Mod-cameras-distant-terrain-tiles-fog-measured-from-.patch +++ b/horizon-patches/0008-Mod-cameras-distant-terrain-tiles-fog-measured-from-.patch @@ -1,8 +1,8 @@ From 9b9e93269d67401cc620567a884d5ce97f68d76e Mon Sep 17 00:00:00 2001 From: chexit Date: Thu, 29 Oct 2026 11:15:11 +0300 -Subject: [PATCH 8/9] Mod cameras: distant terrain tiles, fog measured from the - camera, no impostors while one is shown +Subject: [PATCH 08/10] Mod cameras: distant terrain tiles, fog measured from + the camera, no impostors while one is shown Co-Authored-By: Claude Opus 5.5 --- diff --git a/horizon-patches/0009-Mods-game.world-id-so-mods-keep-state-per-world-API-.patch b/horizon-patches/0009-Mods-game.world-id-so-mods-keep-state-per-world-API-.patch index 1ff3672..e141d54 100644 --- a/horizon-patches/0009-Mods-game.world-id-so-mods-keep-state-per-world-API-.patch +++ b/horizon-patches/0009-Mods-game.world-id-so-mods-keep-state-per-world-API-.patch @@ -1,7 +1,7 @@ From 065d9fa02966238a5bd9ff460392a1a809db0b11 Mon Sep 17 00:00:00 2001 From: chexit Date: Thu, 29 Oct 2026 11:25:45 +0300 -Subject: [PATCH 9/9] Mods: game.world-id, so mods keep state per world (API +Subject: [PATCH 09/10] Mods: game.world-id, so mods keep state per world (API level 9) Co-Authored-By: Claude Opus 5.5 diff --git a/horizon-patches/0010-cargo-horizon-mod-trial-runs-behind-feature-full-so-.patch b/horizon-patches/0010-cargo-horizon-mod-trial-runs-behind-feature-full-so-.patch new file mode 100644 index 0000000..a966c27 --- /dev/null +++ b/horizon-patches/0010-cargo-horizon-mod-trial-runs-behind-feature-full-so-.patch @@ -0,0 +1,98 @@ +From 1bf28135fd9a31f837323dc3a8d81ed73343d816 Mon Sep 17 00:00:00 2001 +From: chexit +Date: Thu, 29 Oct 2026 12:01:08 +0300 +Subject: [PATCH 10/10] cargo-horizon-mod: trial runs behind feature full, so a + packing-only tool skips wasmtime + +Co-Authored-By: Claude Opus 5.5 +--- + crates/cargo-horizon-mod/Cargo.toml | 8 +++++++- + crates/cargo-horizon-mod/src/main.rs | 16 ++++++++++++++-- + 2 files changed, 21 insertions(+), 3 deletions(-) + +diff --git a/crates/cargo-horizon-mod/Cargo.toml b/crates/cargo-horizon-mod/Cargo.toml +index 34eefe227..21b8a9221 100644 +--- a/crates/cargo-horizon-mod/Cargo.toml ++++ b/crates/cargo-horizon-mod/Cargo.toml +@@ -6,8 +6,14 @@ license.workspace = true + rust-version = "1.86" + description = "cargo horizon-mod new/build/run/check: make Horizon mods in Rust (docs/mods.md)" + ++[features] ++default = ["full"] ++# The trial runs (`check`, `test`) and the registry's CI need wasmtime. Without this feature the ++# tool is new, build and run only, and builds in a fraction of the time (CI that only packs mods). ++full = ["horizon-mods/runtime", "horizon-mods/index"] ++ + [dependencies] +-horizon-mods = { path = "../horizon-mods", default-features = false, features = ["pack", "runtime", "index"] } ++horizon-mods = { path = "../horizon-mods", default-features = false, features = ["pack"] } + serde_json = "1.0.151" + + [lints] +diff --git a/crates/cargo-horizon-mod/src/main.rs b/crates/cargo-horizon-mod/src/main.rs +index 95d0a9eb3..ff4ab663b 100644 +--- a/crates/cargo-horizon-mod/src/main.rs ++++ b/crates/cargo-horizon-mod/src/main.rs +@@ -11,12 +11,15 @@ + //! - `check [--json]`: what the registry checks of a submission + //! (`.hmod`, `.hpack`, resource pack `.zip`), with a trial run for a mod. + //! - `registry build|review|check|list`: the registry repository's CI. ++#[cfg(feature = "full")] ++use horizon_mods::testing::{Script, TestGame}; + use horizon_mods::{ + Capability, Content, Manifest, Package, + manifest::{MANIFEST, component_imports, componentize}, +- testing::{Script, TestGame}, + }; ++#[cfg(feature = "full")] + mod check; ++#[cfg(feature = "full")] + mod registry; + + use std::{ +@@ -39,9 +42,16 @@ fn main() -> ExitCode { + println!("Built {}", p.display()); + }), + Some("run") => run(&args[1..]), ++ #[cfg(feature = "full")] + Some("test") => test(&args[1..]), ++ #[cfg(feature = "full")] + Some("check") => check(&args[1..]), ++ #[cfg(feature = "full")] + Some("registry") => registry::main(&args[1..]), ++ #[cfg(not(feature = "full"))] ++ Some("test" | "check" | "registry") => { ++ Err("this cargo-horizon-mod was built without trial runs (feature `full`)".into()) ++ } + _ => Err("usage: cargo horizon-mod new [--template code|content] | build [--debug] | run [--game ] [-- ] | test [--debug] [--hmod ] [] | check [--kind ] [--key --signature ] [--json] | registry build|review|check|list".into()), + }; + match result { +@@ -249,6 +259,7 @@ fn compile(root: &Path, release: bool) -> Result<(Vec, PathBuf), String> { + + /// `check `: the registry's checks of a submission, printed; fails + /// when one does. ++#[cfg(feature = "full")] + fn check(args: &[String]) -> Result<(), String> { + let file = args + .first() +@@ -280,6 +291,7 @@ fn check(args: &[String]) -> Result<(), String> { + + /// `test`: every `tests/*.toml` script (those whose name holds ``) + /// against the built mod, or the `.hmod` given with `--hmod`. ++#[cfg(feature = "full")] + fn test(args: &[String]) -> Result<(), String> { + let hmod = match flag(args, "--hmod") { + Some(p) => PathBuf::from(p), +@@ -360,7 +372,7 @@ fn run(args: &[String]) -> Result<(), String> { + .ok_or_else(|| format!("the game exited with {status}")) + } + +-#[cfg(test)] ++#[cfg(all(test, feature = "full"))] + mod tests { + use super::*; + +-- +2.54.0 (Apple Git-157) + diff --git a/tools/airdef.py b/tools/airdef.py index b2779d3..63bbc99 100644 --- a/tools/airdef.py +++ b/tools/airdef.py @@ -88,7 +88,8 @@ def mod(): if not Path(tool).exists(): if not (FORK / "Cargo.toml").exists(): fork() - run([cargo(), "install", "--locked", "--path", FORK / "crates" / "cargo-horizon-mod"]) + # Without trial runs (wasmtime): building a mod needs only packing, a fraction of the compile. + run([cargo(), "install", "--locked", "--no-default-features", "--path", FORK / "crates" / "cargo-horizon-mod"]) run([cargo(), "horizon-mod", "build"], cwd=ROOT / "airdef-mod")