Run the gate, the editor suite and the three games on the Gitea runner
game-maker / suite (push) Successful in 7m31s

The job image (tools/ci/Dockerfile, label game-maker-ci) carries chromium with the Vulkan
drivers, php-cli and the host's fonts; the render device itself comes from the runner's
container.options, so a job container gets the Intel GPU instead of llvmpipe.

tools/ci/serve.sh puts the checkout behind /game-maker/ with php -S and writes the project
shells the seeders need; every tool now takes its server from GAME_MAKER_BASE_URL rather than
from a project id repeated in shell scripts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
rootandClaude Opus 5 committed 2026-09-12 20:01:45 +02:00
1 parent c47fd5e4dc
commit 4f0908df77
8 files changed
+181 -4

No files matched your search

+38
View File
@@ -0,0 +1,38 @@
# Runs the whole proof on the Gitea runner: gate, the editor suite and the three example games.
#
# `runs-on: game-maker-ci` picks the image built from tools/ci/Dockerfile. The render device and
# /dev/shm come from the runner's own container.options (/_stacks/git/runner/config.yaml), not
# from here – without them chromium falls back to software WebGL.
name: game-maker
on:
push:
workflow_dispatch:
jobs:
suite:
runs-on: game-maker-ci
timeout-minutes: 45
steps:
- uses: actions/checkout@v4
- name: Versions
run: |
node --version
chromium --version
php --version | head -1
- name: Install
run: npm ci --no-audit --no-fund
- name: Gate
run: ./check.sh
- name: Build
run: npm run build
- name: Editor suite
run: tools/ci/run-e2e.sh
- name: Example games
run: tools/ci/run-smokes.sh
+19 -1
View File
@@ -788,7 +788,7 @@ npm test # unit tests (core, editor, ui, viewport)
npm run typecheck
npm run lint # ESLint (type-aware for src and tests); lint:fix for the fixable half
npm run format # Prettier over the repo, minus what .prettierignore lists
npm run check # typecheck + lint + format:check + tests, what CI would run
npm run check # typecheck + lint + format:check + tests, the gate CI runs too
./check.sh # the same, ending in `=== CHECK EXIT n ===`: piped into `tail` npm's exit
# code is lost (tail's is reported), so a red run reads as green
npm run build # writes dist/ and regenerates index.html
@@ -914,6 +914,24 @@ he walks on the spot – a clock that ticks without the picture following would
The workspace folder itself is what nginx serves, so `npm run build` is the deployment.
### Continuous integration
Every push runs the whole proof on the Gitea runner: `.gitea/workflows/ci.yml` installs, runs
`./check.sh`, builds, then `tools/ci/run-e2e.sh` (the editor suite) and `tools/ci/run-smokes.sh`
(the three games). Both scripts source `tools/ci/serve.sh`, which puts the checkout behind
`http://127.0.0.1:8080/game-maker/` with `php -S` – the build wires its assets absolutely under
`/game-maker/dist/`, so the path segment is not optional – and writes the empty project files the
seeders need (`PUT` does not create a project, `POST` hands out a random id). Every tool takes its
server from `GAME_MAKER_BASE_URL`.
The job runs in the image from `tools/ci/Dockerfile` under the label `game-maker-ci`: chromium
with the Vulkan drivers, `php-cli`, and the host's font packages, because the checks measure text
widths to find click points. The **render device is not in this repo** – `/dev/dri/renderD128`
and a 1 GB `/dev/shm` come from `container.options` in the runner's own `config.yaml`, which
applies to every job on that runner. Without them chromium falls back to llvmpipe, which gets
WebGL 1.0 only. Measured in the job container: `ANGLE (Intel, Vulkan 1.3.230, HD Graphics 630)`,
WebGL 2.0, and the same 671 checks as here.
### Ideas, gaps and bugs
They live in the issue tracker of the Gitea repo: <https://git.nockal.com/meleki/game-maker/issues>.
+21
View File
@@ -0,0 +1,21 @@
# The image behind the Gitea Actions label `game-maker-ci`. It is built on the host and kept
# local (the runner has `force_pull: false`), so a change here needs a rebuild:
#
# DOCKER_BUILDKIT=0 docker build --network git_default -t game-maker-ci:latest tools/ci
#
# Both flags are needed on stein2: DNS is dead in Docker's default bridge network, and Buildkit
# cannot join a named network.
FROM node:22-bookworm
# chromium + the Vulkan drivers: the e2e harness starts chromium with --use-angle=vulkan and has
# no software fallback. The render device itself comes from the runner's container.options;
# without it chromium falls back to llvmpipe and gets WebGL 1.0 only.
# php-cli serves api/index.php, which every tool loads and saves its project through.
# The fonts are the host's set on purpose: the checks measure text widths to find click points,
# so a different set moves them.
RUN apt-get update && apt-get install -y --no-install-recommends \
chromium mesa-vulkan-drivers libvulkan1 \
php-cli php-mbstring \
fonts-dejavu-core fonts-droid-fallback fonts-noto-color-emoji fonts-noto-mono \
fonts-texgyre fonts-urw-base35 \
&& rm -rf /var/lib/apt/lists/*
+29
View File
@@ -0,0 +1,29 @@
#!/usr/bin/env node
/**
* Creates the empty project files the seeders write into. `PUT ?r=projects/{id}` does not create
* a project and `POST` hands out a random id, so a fresh CI checkout has to put the shells with
* the known ids in place itself. The playground is left out – no tool may touch it.
*
* Usage: node tools/ci/project-shells.mjs [dataDir]
*/
import { mkdir, writeFile } from 'node:fs/promises';
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { PROJECTS } from '../lib/projects.mjs';
const root = resolve(dirname(fileURLToPath(import.meta.url)), '../..');
const dir = process.argv[2] ?? resolve(root, 'data/projects');
await mkdir(dir, { recursive: true });
for (const [name, id] of Object.entries(PROJECTS)) {
if (name === 'playground') continue;
const doc = {
id,
name,
revision: 0,
updatedAt: '1970-01-01T00:00:00Z',
project: {},
};
await writeFile(resolve(dir, `${id}.json`), `${JSON.stringify(doc)}\n`);
console.log(`shell ${name} ${id}`);
}
+14
View File
@@ -0,0 +1,14 @@
#!/bin/bash
# The editor suite in CI: serve the checkout, seed the fixture, run every step.
# Ends in a line that survives a pipe, like check.sh does.
set -euo pipefail
cd "$(dirname "$(readlink -f "$0")")/../.."
source tools/ci/serve.sh
game_maker_serve
set +e
node tools/seed-e2e.mjs && node tools/e2e.mjs
code=$?
echo "=== E2E EXIT $code ==="
exit $code
+17
View File
@@ -0,0 +1,17 @@
#!/bin/bash
# The three example games in CI: seed each one, then play it in a headless browser.
# Ends in a line that survives a pipe, like check.sh does.
set -euo pipefail
cd "$(dirname "$(readlink -f "$0")")/../.."
source tools/ci/serve.sh
game_maker_serve
set +e
code=0
for game in jumprun tetris actor; do
echo "--- $game"
node "tools/seed-$game.mjs" && node "tools/smoke-$game.mjs" || code=1
done
echo "=== SMOKES EXIT $code ==="
exit $code
+38
View File
@@ -0,0 +1,38 @@
#!/bin/bash
# Sourced, not run: it leaves the PHP server in the caller's background and exports
# GAME_MAKER_BASE_URL, which every tool reads.
#
# The build wires its assets absolutely under /game-maker/dist/, so the checkout has to be
# served under that path segment – hence the symlink into /srv rather than `-t .`.
export GAME_MAKER_BASE_URL=http://127.0.0.1:8080/game-maker/
game_maker_serve() {
local root
root=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)
mkdir -p "$root/data/projects" "$root/data/textures"
node "$root/tools/ci/project-shells.mjs"
# A server from an earlier step of the same job is fine to keep.
if curl -sf "${GAME_MAKER_BASE_URL}api/index.php?r=projects" >/dev/null 2>&1; then
echo "server already up at $GAME_MAKER_BASE_URL"
return 0
fi
mkdir -p /srv && ln -sfn "$root" /srv/game-maker
# Without workers the single-threaded dev server stalls: the editor fetches project and
# textures in parallel.
PHP_CLI_SERVER_WORKERS=8 php -S 0.0.0.0:8080 -t /srv >/tmp/php-server.log 2>&1 &
for _ in $(seq 60); do
curl -sf "${GAME_MAKER_BASE_URL}api/index.php?r=projects" >/dev/null 2>&1 && break
sleep 0.5
done
if ! curl -sf "${GAME_MAKER_BASE_URL}api/index.php?r=projects" >/dev/null 2>&1; then
echo "the server never came up" >&2
tail -20 /tmp/php-server.log >&2
return 1
fi
echo "serving $root at $GAME_MAKER_BASE_URL"
}
+5 -3
View File
@@ -24,12 +24,14 @@ export const PROJECTS = {
export const DEFAULT_BASE_URL = 'https://nockal.com/game-maker/';
/**
* Reads `[projectId] [baseUrl]` from argv, falling back to `defaultId`. Targeting the playground
* aborts: no tool may overwrite or mutate it.
* Reads `[projectId] [baseUrl]` from argv, falling back to `defaultId` and to
* `GAME_MAKER_BASE_URL` – which is how CI points every tool at its own server without
* repeating the project ids in shell scripts. Targeting the playground aborts: no tool may
* overwrite or mutate it.
*/
export function cliArgs(script, defaultId) {
const projectId = process.argv[2] ?? defaultId;
const baseUrl = process.argv[3] ?? DEFAULT_BASE_URL;
const baseUrl = process.argv[3] ?? process.env.GAME_MAKER_BASE_URL ?? DEFAULT_BASE_URL;
if (projectId === PROJECTS.playground) {
console.error(`refusing to run ${script} against the playground project (${PROJECTS.playground}).`);
process.exit(2);