Files
James Dansereau 4b710728bf v0.5.0 playtest fold-in: fog and near-plane clip, crash fixes, polish (D540-D564)
Deck/Windows playtest fixes: fog rework (D540) and RSP-style CPU near-plane
clip with per-vertex N64 fog (D543), with fog recomputed on near-plane-clipped vertices as the RSP does,
so the haze around the player stays steady (D553); colour-combiner clamp for banded walls
(D548); rocket and thrown-item stack overflows (D545, D549); menu
double-trigger mitigation (D541); Dam ending first-shot camera swivel
restored by removing an old port look-at workaround (D552); settings standardisation (D546), live
All unlocked (D547), F10 mouse (D544); static window title and the project
mark (the in-game crosshair shape, site gold) as window/taskbar icon and
favicon (D550). Reference frames re-based
on Windows, Linux and Steam Deck (A/B-confirmed); capture_p7.sh runs on
Windows. Opt-in update check, off by default (D551: one HTTPS request to
GitHub per launch when enabled; winhttp.dll loaded only then; the tag CI
job checks the tag against GE007_VERSION). Release notes, ROADMAP,
internals and security docs updated; tree-hygiene pass.

Refs: D540 D541 D543 D544 D545 D546 D547 D548 D549 D550 D551 D552 D553 D554 D555 D556 D557 D558 D559 D560 D561 D562 D563 D564
2026-10-06 18:35:44 -04:00

201 lines
8.8 KiB
YAML

# Self-hosted runtime (ROM-gated)
#
# Runs the build + the 21-level headless sweep (tools_pc/verify.sh) on the
# self-hosted runner, the one machine that has a legal ROM.
# This is the job class ci.yml explicitly does NOT do: CI is build-only, no
# ROM, so runtime checks live here. Setup + rollout plan (gitignored, local):
# docs/dev/notes/SELFHOSTED-RUNNER-PLAN.md
#
# Persistence model — everything lives under the runner's own paths, managed
# by the pipeline; no hand-maintained directories:
# - ccache -> actions/cache (stored by the runner under _work/_temp)
# - ROM -> copied every run from a seed dir on the runner:
# {runner_home}/_rom-seed/, or the repo variable
# GE_ROM_SEED_DIR. Bootstrap: put ge007.ntsc-final.z64,
# pcmodels-ntsc-final/ and pccg-ntsc-final/ there once.
# Integrity is re-verified every run against the committed
# ge007.u.sha1 (repo-tracked US ROM hash).
# The ROM is deliberately NOT stored in actions/cache (it
# used to be, key ge-rom-data-ntsc-v1). Cache entries live on
# GitHub's side, and pull_request workflows, including ci.yml
# runs from fork PRs, can restore caches created on the base
# branch, so a PR could read the ROM out of the cache. (The
# old save never actually persisted on this box, so no entry
# was ever created; checked 2026-09-30.)
# - MSYS2 -> machine-level install at C:\msys64 (verify.sh hardcodes the
# prefix), installed and its packages maintained BY HAND on
# the box. msys2/setup-msys2 still runs every job (it's what
# registers the `msys2 {0}` shell wrapper), but with
# `release: false` and update/install both omitted — verified
# against the action's source to do no pacman -Syu / pacman -S
# / network round-trip, just a fast reuse of the existing
# C:\msys64. Package presence is checked (not installed) by
# tools_pc/check-selfhosted-deps.sh, which fails fast with
# exact install instructions if something's missing.
#
# Trigger gating (non-negotiable):
# - workflow_dispatch ONLY for now. Flip on `push: branches: [main]` once
# the first manual runs are reliably green.
# - NEVER add `pull_request`: unreviewed fork code must not be routable to
# a runner that can see the ROM.
#
# Leak rules (docs/dev/notes/AGENT-VM-FORGEJO-PLAN.md checklist):
# - The ROM + sidecars live only in the runner's local seed dir (never in
# actions/cache); nothing in this workflow uploads from data/.
# - Artifacts here are console logs + a JSON verdict only. Rendered frames
# (ppm/) are ROM-derived content and are NOT uploaded.
name: Self-hosted runtime
on:
workflow_dispatch:
inputs:
levels:
description: "Sweep subset, SWEEP_LEVELS format (blank = all 21)"
required: false
default: ""
type: string
permissions:
contents: read
# One ROM job at a time per ref; never cancel an in-flight sweep out from
# under itself (the sweep is serialized by its own lockdir + watchdog). Group
# + cancel-in-progress:false is what actually serializes runs — GitHub
# Actions' workflow-level `concurrency` has no `max-parallel` key (that only
# exists under a job's `strategy:` for matrix jobs); a stray one here is a
# hard parse error that blocks every dispatch.
concurrency:
group: selfhosted-${{ github.ref }}
cancel-in-progress: false
env:
GE_ROM_SEED_DIR: ${{ vars.GE_ROM_SEED_DIR }}
jobs:
build-and-sweep:
runs-on: [ self-hosted, windows ]
defaults:
run:
shell: msys2 {0}
env:
CCACHE_DIR: ${{ github.workspace }}/.ccache
CCACHE_MAXSIZE: 500M
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
# `release: false` reuses the box's own C:\msys64 as-is instead of
# downloading/installing MSYS2 fresh (verified against the action's
# source: with this set, and update/install both omitted, it does
# exactly one `uname -a` + a pacman.conf sed tweak — no pacman -Syu,
# no pacman -S, no network round-trip). This step still has to run
# every time — it's what registers the `msys2 {0}` shell wrapper
# above — but does no install/update work.
- uses: msys2/setup-msys2@ec48f7c5447b3140e2b088413ae3a55687bccb6e # v2.33.0
with:
msystem: MINGW64
release: false
# Packages themselves are hand-maintained on the box (never installed
# by this workflow) — fails fast with exact install instructions if
# one's missing, instead of a confusing failure deeper in the build.
- name: Check self-hosted dependencies
run: tools_pc/check-selfhosted-deps.sh
# --- ROM + sidecars: runner-local seed dir (never actions/cache) ------
# {runner_home} = two levels up from RUNNER_WORKSPACE ({home}/_work/{repo}).
# The seed dir is the only copy the workflow uses; it is never deleted.
- name: Stage ROM + sidecars (runner seed dir)
run: |
seed="${GE_ROM_SEED_DIR:-$(dirname "$(dirname "$RUNNER_WORKSPACE")")/_rom-seed}"
for f in ge007.ntsc-final.z64 pcmodels-ntsc-final pccg-ntsc-final; do
[ -e "$seed/$f" ] || {
echo "missing $seed/$f" >&2
echo "One-time bootstrap: copy ge007.ntsc-final.z64, pcmodels-ntsc-final/" >&2
echo "and pccg-ntsc-final/ into $seed (or set the repo variable GE_ROM_SEED_DIR)." >&2
exit 2
}
done
mkdir -p data
# -r: pcmodels-*/pccg-* are directories, not single files (found by
# testing — a plain cp failed with "omitting directory").
cp -r "$seed/ge007.ntsc-final.z64" "$seed/pcmodels-ntsc-final" \
"$seed/pccg-ntsc-final" data/
echo "staged ROM store from $seed"
- name: Verify ROM integrity (committed US sha1)
run: |
cd data
want="$(cut -d' ' -f1 ../ge007.u.sha1)"
got="$(sha1sum ge007.ntsc-final.z64 | cut -d' ' -f1)"
[ "$want" = "$got" ] || { echo "ROM hash mismatch: want $want got $got" >&2; exit 2; }
echo "ROM sha1 ok: $got"
# --- build -------------------------------------------------------------
# Same opt-in ccache CMakeLists.txt wires up (speed-ups plan Step 5);
# persisted via actions/cache like ci.yml's windows-build.
- name: Restore ccache
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ${{ env.CCACHE_DIR }}
key: ccache-selfhosted-${{ hashFiles('CMakeLists.txt') }}-${{ github.sha }}
restore-keys: |
ccache-selfhosted-${{ hashFiles('CMakeLists.txt') }}-
ccache-selfhosted-
- name: Regenerate ROM-asset symbol file (US)
run: python scripts/gen_romassets.py u
- name: Build (ntsc-final)
run: ./build-pc.sh ntsc-final
# verify.sh: build-if-stale -> headless GE_PCDUMP per level -> crash
# symbolication -> pixcount -> framediff vs tools_pc/golden/. Serialized
# + watchdog + one retry by design (D117: not frame-deterministic).
- name: Level sweep (all 21, or dispatch subset)
env:
INPUT_LEVELS: ${{ inputs.levels }}
run: |
if [ -n "$INPUT_LEVELS" ]; then
if ! printf '%s' "$INPUT_LEVELS" | grep -Eq '^[A-Za-z0-9]+:[0-9]+( [A-Za-z0-9]+:[0-9]+)*$'; then
echo "levels input must be space-separated Name:NN entries, e.g. 'Dam:33 Facility:34'" >&2
exit 1
fi
export SWEEP_LEVELS="$INPUT_LEVELS"
fi
tools_pc/verify.sh sweep --json > sweep.log 2>&1
rc=$?
cat sweep.log
exit $rc
# Pull the JSON verdict array out of the mixed stdout for easy review.
- name: Extract JSON verdict
if: always()
run: |
python -c "
import json, sys
s = open('sweep.log', encoding='utf-8', errors='replace').read()
i = s.rfind('[{')
out = '[]'
if i >= 0:
d, _ = json.JSONDecoder().raw_decode(s[i:])
out = json.dumps(d, indent=1)
open('sweep-verdict.json', 'w').write(out + '\n')
print(out)
"
# Console logs + verdict only — no frames, no ROM bytes (leak rules).
- name: Upload sweep report
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: selfhosted-sweep-${{ github.run_number }}
path: |
sweep.log
sweep-verdict.json
ge007.crash.log
if-no-files-found: ignore
retention-days: 14