smoke.yml was scaffolding to prove the self-hosted runners could execute
anything at all, back when checkout and the SDK were still unresolved. ci.yml
now builds, gates and releases on both runners, so the smoke jobs only
duplicate its environment checks. Leaving dead workflows around is exactly the
debt that confuses the next reader.
This commit also serves as the pipeline's first real push-triggered run: every
green run so far was a workflow_dispatch.
Three tests reached the CI gate needing the installed retail DATs, which no
build machine has, and failed with FileNotFoundException on client_cell_1.dat:
- Issue127FloodFlipReplayTests (both facts replay via ResolveDatDir)
- FindCellListConformanceTests.FindCellList_DoorwayThreshold_IndoorPicks_
MatchRetail, the one untagged method among already-tagged siblings
They now carry [Trait("Lane", "InstalledDat")] like every other DAT test, so
the gate filter excludes them and the local DAT lane still runs them.
Also reverts the DOTNET_SYSTEM_GLOBALIZATION_INVARIANT pin from the previous
commit. It was too blunt: it fixed the 40 decimal-comma failures but broke
ChatLogTests.FormatTimestampPrefix_UsesLiteralColons_RegardlessOfCurrentCulture,
which legitimately constructs a culture and cannot under invariant mode. The
runner's HKCU locale (LocaleName=en-SE, sDecimal=',') was corrected to en-US
instead, which is the actual defect.
The runner reports en-US interactively, but its scheduled task inherits
en-SE (English/Sweden), whose decimal separator is a comma. That broke 40
tests across App/Core/Runtime/UI.Abstractions on number formatting alone
(expected "update:0.25", actual "update:0,25"). Set-Culture does not reach
a task running without a loaded user profile, and a build gate should not
depend on a machine's regional settings regardless.
Note for follow-up: this pins CI only. The underlying culture sensitivity is
real — a Swedish-locale player would see comma-formatted numbers in these
diagnostic strings.
FakeAceTransportTests.PausedSelector_SeededDroppedServerReady_RecoversOnIdle
Sweep failed in CI after 37 s while passing 5/5 in ~350 ms in isolation on the
same machine: under full-assembly parallel load on a 6-core container its 2 s
real-time waits get starved. The test is timing-sensitive, not broken, so cap
the runner rather than edit Campaign N transport code.
The windows-gate job was marked failed while the work was still running: 20
dotnet processes were alive on the runner and a complete 8.7 MB App.Tests TRX
was on disk after Forgejo had already recorded a failure.
Cause: tools/run-release-gate.ps1 redirects every bounded child process to its
own log file, so the workflow step emits no output for minutes. Forgejo treats
a task that stops reporting as a zombie and fails it. The Linux job, which
runs dotnet test directly, streamed continuously and produced real results.
The Windows job now builds and then runs each test project directly with the
same trait filter copied from the gate script's default, so output streams the
whole time. run-release-gate.ps1 remains the canonical LOCAL gate, where its
bounded-process/blame-hang machinery is the point.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Every push to main now runs the gate on the self-hosted runners and, when
green, publishes a Gitea Release carrying the client, launcher+bake, and
manifest.
Pipeline (.gitea/workflows/ci.yml):
- windows-gate runs tools/run-release-gate.ps1, the project's own bounded
gate. A bare `dotnet test AcDream.slnx` is NOT usable as a gate: it fails
~36 tests by design, because the InstalledDat/Live/Manual/OS lanes assert
their own preconditions. The gate script's trait filter is what excludes
them.
- linux-portable runs the portable closure, where the Linux-lane tests
actually execute instead of failing on Windows.
- release depends on both, so a red gate cannot publish. It is a job in the
same workflow rather than a workflow_run trigger, whose Forgejo support is
unreliable; `needs` is guaranteed.
No actions/setup-dotnet: data.forgejo.org does not mirror it at all (404),
and both runners carry the pinned SDK band already. actions/checkout IS
mirrored and is used normally.
Release payloads become release ATTACHMENTS, outside git history, so ~120 MB
per build never enters a branch. Only the ~500-byte manifest.json is
committed, to the payload-free dist branch, because Forgejo has no
/releases/latest/download/ route (verified 404) for the launcher to poll.
publish-bin.ps1 takes -BaseUrl so the manifest points at the release tag.
Two real gate failures fixed:
- LauncherProjectBoundaryTests asserted four `**` path filters belonging to
the push triggers that 8be14d39 removed when workflows went manual-only.
The assertions about what the workflow DOES are untouched.
- MainWindowViewTests failed in Test Case Cleanup with "calling thread cannot
access this object" while passing in isolation: Avalonia's headless session
is thread-affine and xUnit ran collections in parallel. Serialized via
xunit.runner.json, the same settings AcDream.Core.Tests already uses.
Local gate: 12 projects, 14,346 tests, 0 failures.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A stock publish shipped native debug symbols to players: libSkiaSharp.pdb
(80 MB) and libHarfBuzzSharp.pdb (20 MB) from Avalonia's rendering packages
were 100 MB of a 278 MB launcher payload. MSBuild's DebugType switches only
govern our own managed symbols, not native .pdb files arriving as package
runtime assets, so the payload build drops every .pdb before zipping.
launcher-win-x64.zip 103.4 -> 77.4 MB, client 44.5 -> 43.6 MB. The launcher
payload now also fits under GitHub's 100 MB per-file limit, though the feed
stays on the Gitea-only dist branch to keep main's history clean.
Also fixes a StrictMode crash in the lock-file warning: an empty git status
result is null, not an empty array, so .Count threw at the end of a
successful publish.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The launcher reported "no client available" because its update source was
pinned to a GitHub Releases manifest in a PRIVATE repo — nothing anonymous
could ever be fetched from it. Switch the feed to the PUBLIC Gitea repo so a
friend needs no account, and add the two commands that publish it.
- ReleaseManifestClient.ProductionManifestUri now points at
git.snakedesert.se/erik/acdream raw on the `dist` branch. No update
machinery changed: the existing strict reader already accepts any HTTPS
manifest, so this is a URL swap plus a build script.
- tools/publish-bin.ps1 publishes the payloads into /bin and writes
bin/manifest.json (schema v1, SHA-256 + size per artifact):
client-win-x64.zip AcDream.App + acdream-headless
launcher-win-x64.zip acdream-launcher + co-deployed acdream-bake
Stamps InformationalVersion ONLY — never -p:Version, which also rewrites
project-reference versions inside the committed packages.<rid>.lock.json
files and churned every one of them with a throwaway build stamp.
- tools/publish-dist.ps1 pushes /bin to the Gitea-only `dist` branch from a
throwaway worktree, leaving the developer's checkout, index, and HEAD
untouched. It refuses a GitHub remote outright.
Why `dist` and not main: the launcher payload is ~103 MB because the launcher
and its co-deployed bake CLI are each self-contained single files (deliberate,
see AcDream.Launcher.csproj). GitHub hard-rejects files over 100 MB, and all
three refs currently track main, so payloads on main would break every GitHub
push. `dist` is a single-commit orphan branch that each publish REPLACES, so
superseded builds never accumulate. /bin stays gitignored repo-wide and is
force-added only on that branch.
Verified live: manifest and both payloads serve anonymously over HTTPS, and a
downloaded client payload matches its declared SHA-256 and size byte for byte.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>