165 lines
7.9 KiB
YAML
165 lines
7.9 KiB
YAML
# Magic Civilization — CI gate on push-to-main.
|
|
#
|
|
# Implements objective p2-10 (`.project/objectives/p2-10-regression-ci-gate.md`):
|
|
# every push to `main` must pass the full regression suite on the self-hosted
|
|
# apricot runner before the commit is considered "green" on the Forgejo commit page.
|
|
#
|
|
# Runner registration instructions: `.forgejo/RUNNER_SETUP.md`.
|
|
# Forge: http://10.0.0.11:3000/magicciv/magicciv (Forgejo Actions, drone-compatible).
|
|
#
|
|
# Budget: median completion <= 15 min. If a stage consistently blows this,
|
|
# demote it to a nightly workflow (see RUNNER_SETUP.md § "Budget overruns").
|
|
|
|
name: ci
|
|
|
|
on:
|
|
push:
|
|
branches:
|
|
- main
|
|
# Allow a user to kick the gate manually from the Forgejo Actions UI or
|
|
# via `POST /api/v1/repos/magicciv/magicciv/actions/workflows/ci.yml/dispatches`.
|
|
# Useful for a first-run smoke on a freshly-registered runner without
|
|
# needing a sacrificial commit.
|
|
workflow_dispatch:
|
|
|
|
# One CI run per commit on main; a later push supersedes an in-flight run.
|
|
concurrency:
|
|
group: ci-main
|
|
cancel-in-progress: true
|
|
|
|
env:
|
|
# Keeps cargo/rustup/pip caches inside the repo workspace so the runner
|
|
# can reuse them across runs without polluting $HOME on apricot.
|
|
CARGO_TERM_COLOR: always
|
|
RUST_BACKTRACE: 1
|
|
|
|
jobs:
|
|
regression:
|
|
name: regression gate
|
|
# Self-hosted apricot runner — see .forgejo/RUNNER_SETUP.md for registration.
|
|
# Labels must match forgejo-runner config on apricot exactly.
|
|
runs-on: [self-hosted, linux, apricot]
|
|
timeout-minutes: 25
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 1
|
|
|
|
# ── Stage 1: Rust workspace tests ────────────────────────────────
|
|
# Covers all mc-* crates + api-wasm + api-gdext surface tests.
|
|
# GPU-feature tests are attempted but don't fail the build if the
|
|
# gpu feature fails to compile on this runner (CUDA/Vulkan env may
|
|
# not be present); a clean "skipped" message is preferable to a
|
|
# misleading red pipeline on an environment gap.
|
|
- name: cargo test --workspace
|
|
working-directory: src/simulator
|
|
run: cargo test --workspace --locked
|
|
|
|
- name: cargo test --workspace --features gpu (best-effort)
|
|
working-directory: src/simulator
|
|
# `cargo check` gates the attempt — if gpu deps won't even compile
|
|
# on this runner, skip the full test run loudly but don't fail.
|
|
run: |
|
|
set -uo pipefail
|
|
if cargo check --workspace --features gpu 2>/dev/null; then
|
|
echo "gpu feature compiles; running gpu tests"
|
|
cargo test --workspace --features gpu --locked
|
|
else
|
|
echo "::warning::gpu feature failed to compile on this runner — skipping gpu tests"
|
|
fi
|
|
|
|
# ── Stage 2: GDScript lint (advisory) ────────────────────────────
|
|
# gdtoolkit must be installed on the runner; see RUNNER_SETUP.md.
|
|
#
|
|
# Hard-gated 2026-04-25 (p2-10a). Backlog cleared via file-splitting:
|
|
# unit_renderer_draw.gd, city_rust_bridge.gd, game_state_serialization_helpers.gd,
|
|
# ai_turn_bridge_dispatch.gd, ai_turn_bridge_state.gd, turn_processor_helpers.gd,
|
|
# turn_processor_city_helpers.gd extracted; stale src/generation/auto_play.gd
|
|
# deleted (live autoload at scenes/tests/auto_play.gd was unaffected);
|
|
# StatsTracker.CATEGORY_LABELS renamed to category_labels per
|
|
# class-variable-name rule. `gdlint src/game/engine/src/` exits 0 on main HEAD.
|
|
- name: gdlint
|
|
run: gdlint src/game/engine/src/
|
|
|
|
# ── Stage 3: Game data JSON validator ────────────────────────────
|
|
- name: validate game data
|
|
run: python3 tools/validate-game-data.py
|
|
|
|
# ── Stage 4: Objectives dashboard sync check ─────────────────────
|
|
# Fails if the dashboard in .project/objectives/ drifts from the
|
|
# frontmatter in individual objective files.
|
|
- name: objectives report --check
|
|
run: python3 tools/objectives-report.py --check
|
|
|
|
# ── Stage 4.5: Build GDExtension ─────────────────────────────────
|
|
# Godot's GDScript compile depends on `GdClimatePhysics`, `GdGridState`,
|
|
# `GdTechWeb` etc. which come from the Rust GDExtension. The .so is
|
|
# gitignored (built per-host), so a fresh `actions/checkout@v4`
|
|
# workspace has no binary — Godot's parser errors before any test runs.
|
|
# We build release-profile `magic-civ-physics-gdext` into
|
|
# `src/game/engine/addons/magic_civ_physics/libmagic_civ_physics.x86_64.so`
|
|
# which is what `magic_civ_physics.gdextension` points at.
|
|
- name: build GDExtension
|
|
working-directory: src/simulator
|
|
run: bash build-gdext.sh
|
|
|
|
# ── Stage 4.75: Godot import — build class cache ─────────────────
|
|
# Fresh checkouts have no `.godot/global_script_class_cache.cfg`;
|
|
# without it Godot's parser cannot resolve GDExtension type names
|
|
# (e.g. `GdClimatePhysics`), so even well-formed GDScript fails to
|
|
# compile. `--headless --import` walks the project once to populate
|
|
# the cache.
|
|
- name: Godot import (build class cache)
|
|
run: |
|
|
flatpak run --filesystem=home org.godotengine.Godot \
|
|
--path src/game --headless --import
|
|
|
|
# ── Stage 5: Headless GUT ────────────────────────────────────────
|
|
# Runs via Flatpak Godot on apricot. `tools/gut-headless.sh` wraps
|
|
# the Flatpak invocation and parses JUnit XML exit code (Flatpak
|
|
# swallows Godot's quit() exit code).
|
|
#
|
|
# Hard-gated 2026-04-26 (p2-10b). Original backlog: 40 failures out
|
|
# of 439 tests. All 40 triaged: 32 fixed (stale assertions, wrong
|
|
# API names, type mismatches, missing production_cost fixture field,
|
|
# is_flying() keyword check, diplomacy null-partner guard) and 8
|
|
# spun out as pending with objective refs (p2-10c thru p2-10j).
|
|
# `gut-headless.sh` exits 0 only when JUnit XML reports 0 failures.
|
|
- name: headless GUT
|
|
run: bash tools/gut-headless.sh
|
|
|
|
# ── Stage 6: 1-seed T100 smoke batch (advisory) ──────────────────
|
|
# Minimum-viable determinism + no-stall check. Confirms the commit
|
|
# doesn't deadlock or crash the autoplay loop. PARALLEL=1 to keep
|
|
# runtime predictable within the 15-minute budget.
|
|
#
|
|
# Currently advisory — autoplay-batch + flatpak sandbox plumbing
|
|
# has remaining rough edges on fresh CI checkouts (meta.json /
|
|
# turn_stats.jsonl not always landing even when the game completes).
|
|
# The `cargo test` stage already covers the deeper simulation
|
|
# determinism; this stage is the end-to-end dual-language smoke.
|
|
# Flip to hard-fail when the last sandbox path bug is fixed.
|
|
- name: autoplay smoke (seed 1, 100 turns) (advisory)
|
|
continue-on-error: true
|
|
env:
|
|
PARALLEL: "1"
|
|
run: |
|
|
set -uo pipefail
|
|
# Absolute path keeps flatpak's sandbox happy across checkout
|
|
# layouts — relative paths break when the runner workdir is not
|
|
# the CWD the sandbox resolves against.
|
|
out_dir="$GITHUB_WORKSPACE/.local/iter/ci_smoke_${GITHUB_SHA:-unknown}"
|
|
mkdir -p "$out_dir"
|
|
bash tools/autoplay-batch.sh 1 100 "$out_dir"
|
|
|
|
# Surface the smoke batch's per-turn stats + events on failure so a
|
|
# red pipeline is debuggable without having to re-run locally.
|
|
- name: Upload smoke artifacts
|
|
if: failure()
|
|
uses: actions/upload-artifact@v3
|
|
with:
|
|
name: ci-smoke-${{ github.sha }}
|
|
path: .local/iter/ci_smoke_*/**
|
|
retention-days: 7
|