A .NET 10 / C# 14 game engine and application framework: the same stack that ships a game also ships Photoshop- or Blender-class desktop tooling. The editor is written in the engine, in the engine's own UI framework, and is the primary proof that the framework is general-purpose.
./build.sh Compile # or build.cmd on Windows
./build.sh TestThe SDK version is pinned in global.json; nothing else is required to build the
solution. ./build.sh is the entry point CI and developers share. There are thirty-nine targets:
Clean Restore RestoreNativeDeps Compile CompileRelease Test Coverage Pack CheckPackages CheckTemplates GoldenImages Benchmark CheckBenchmarks CheckArchitecture CheckApi CheckFormat CheckWhitespace CheckAttribution CheckStrings CheckShaders CheckDocComments CheckDocs CheckDocsCoverage Docs CheckAot CheckAotIos CompileMobile CompileWeb PublishWeb BrowserSmoke PublishEditor Release ContentBytes RemeshBytes SampleFrame AffectedProjects AffectedTests TestOrder PruneWorktrees — with
docs/plan/12 saying what each does and which are gates.
--workers <n> bounds how many projects compile and how many test assemblies run at once. It
defaults to 4 locally and to unbounded in CI, which has the machine to itself; --workers 0 asks for
unbounded anywhere. It is what keeps the run from taking the machine away from everything else on
it, and it costs less than it reads: measured from the 178 TRX of the 2026-09-05 sweep, a capped
Test reached a 3.93× speed-up on four lanes — 98% of the ideal, nine seconds off the optimal
packing of the same assemblies. ⚠ Raising the cap cannot buy more than two and a half minutes and
buys nothing at all above six, because the floor is one 412-second assembly and not the fan-out.
The second multiplier is xunit.runner.json's maxParallelThreads, which sizes each assembly's own
collection pool and which --workers cannot reach. It is 0.5x — five threads on a ten-core box —
and that number is now measured by the same interval arithmetic over the same 178 TRX. ⚠ No
assembly in that sweep ever ran more than five collections at once, which is the instrument check
rather than a curiosity: a run where the file failed to be copied beside the assembly would look
identical in every other respect and would report ten. Doubling the pool to 1.0x is worth 36
seconds spread over six assemblies and nothing at all to the other 172, because every large one is
floored by its longest collection rather than by the pool. ⚠ And the long pole is not on that list
whatever the arithmetic says: Vixen.Editor.App.Tests disables collection parallelism outright over
a real data race on the process-wide Strings signal, so neither multiplier can touch the 382
seconds it contributes.
⚠ A multiplier truncates, so 0.5x is the wrong value for a small machine and CI runs on small
machines. xunit computes it as (int)(m × ProcessorCount), a cast that rounds toward zero: ten
cores give the five that was measured, four give two, and three give one — not a halved pool but
collection parallelism switched off. macos-14 is a three-core M1 runner. So the file is chosen
rather than fixed: xunit.runner.ci.json (1.0x, which is what CI ran at for its whole history
before the cap existed) under GITHUB_ACTIONS, and the measured xunit.runner.json everywhere else.
Tools/Vixen.ApiCheck.Tests/TestParallelismTests.cs holds both to that arithmetic, and checks the
copy beside its own assembly — which is the only evidence anywhere that the link works at all.
Agent worktrees under .claude/worktrees are never cleaned up by anything, and each carries its own
bin/obj — about 25 GB apiece once the solution has been built in both configurations.
./build.sh PruneWorktrees lists which of them are merged into master, clean, unlocked and unwritten
for --idle-minutes (30 by default), and --remove-merged removes those and only those; anything
failing one of the four conditions, and any directory in there that is not a registered worktree at
all, is reported and left alone. ⚠ The fourth condition is there because the lock is the runner's
habit and not its promise — two of thirteen live agent worktrees carried none on 2026-09-05, and
merged-and-clean is exactly what a live agent looks like between its branch being merged and its
process ending. It costs the merge-then-prune sweep nothing but a delay: --idle-minutes 0 reclaims
the disk immediately for an operator who knows what else is running.
Some backends need a native binary that no package ships. ./build.sh RestoreNativeDeps fetches each
one pinned and SHA-256-verified from build/native-dependencies.json,
and commits nothing.
Core/ |
The engine: math, memory, jobs, VFS, serialization, ECS, the RHI, rendering, assets, audio, physics, animation, navigation, networking, video, XR, and the Vixen.Ui framework |
Platform/ |
Per-target implementations of Core/'s contracts — Windows, Linux, macOS, Android, iOS, Web — and the graphics backends: Vulkan, OpenGL/GLES/WebGL2, WebGPU, Null |
Editor/ |
The editor, built on Vixen.Ui and nothing else |
Raven/ |
The shader language: a hand-written parser over a Roslyn-shaped syntax tree, a semantic phase, an IR, and GLSL + SPIR-V emitters |
Tools/ |
The vixen CLI, the MSBuild SDK, the asset and shader compilers, the content server, the templates |
Samples/ |
Eleven runnable samples, each the proof for one phase of the plan |
Benchmarks/ |
The performance gates |
docs/ |
The design record, the state of it, and the manual |
Every project keeps its tests as a sibling (Vixen.Ecs / Vixen.Ecs.Tests) rather than in a mirror
tree, and its own README.md — which is where the reasoning behind that subsystem lives, including
what it deliberately does not do. Those READMEs are the best entry point into any one area.
Three kinds, kept apart on purpose, because three places recording the same thing is how they come to disagree.
docs/plan/ |
The design record — what Vixen is meant to be and why each decision was taken, in 25 documents plus the ADR register. It does not say what is built |
docs/overview.md |
The state — every feature and library with a status, a dependency tree over what is left, and one table of what is owed. Reconciled against the code, and it wins where a design document disagrees |
docs/manual/ |
Reader-facing — building a game and a server, the diagnostic-code and log-event registers, and the third-party attribution manifest |
Start with docs/plan/README.md for the index, or
docs/overview.md if the question is "what works today".
Stated because each one shapes code you will read, and each is enforced by the build rather than by
convention. The reasoning is in docs/plan/00 and the ADR
register in docs/plan/01.
- All metaprogramming is Roslyn source generators. No IL weaving, no
Mono.Cecil, no post-processing — so NativeAOT and full trimming work, and generated code is ordinary steppable C#.CheckArchitecturefails the build if an IL rewriter appears in the restore graph (ADR-002). - iOS is NativeAOT-only, and that is gated.
CheckAotIospublishes the 21 assemblies a phone links, rooted, and fails on any trim or AOT warning. Reflection debt is caught before it is expensive. ⚠ The gate now also asserts, before publishing, that the probe still roots every assembly it references and still declaresPublishAot— but it does not yet read the output, so a publish that quietly stopped being ahead-of-time for a reason outside the project file would still pass (#634), and no workflow runs it (#327). Vixen.Uinever referencesVixen.Engine. The moment it does, the application-framework claim is dead. Checked from Phase 0.internalby default.publicneeds a reason and aPublicAPI.Unshipped.txtentry, andCheckApifails on an unapproved addition and on a silent removal.- Warnings are errors, at
AnalysisLevel=latest-recommended. A rule that conflicts with a deliberate decision is disabled by name with a written reason in.editorconfig; the level itself is never lowered, because an exclusion is reviewable and a lowered level is not. - Correctness is judged by something other than us where possible — the Yoga conformance suite,
the Unicode Consortium's test data,
spirv-val, golden images, Arch's benchmarks. And gates are themselves checked by sabotage: break the thing on purpose and confirm the suite goes red.
The engine boots on three desktops, the iOS Simulator and the Android emulator; renders a forward+ PBR
pipeline with shadows and post FX; runs a UI framework that passes the full Yoga and UAX conformance
suites; hosts an editor that opens a project, imports assets, builds content, edits a scene and runs
the game; and carries a server-authoritative networking stack that meets all five of its exit criteria.
What is unfinished, and what blocks it, is in docs/overview.md — including a
dependency tree so independent work can be scheduled in parallel.
Apache-2.0 — see LICENSE and NOTICE. Apache's express patent grant is the
reason over MIT: a studio shipping a commercial title on a third-party engine cares about patent peace,
and legal review is materially easier (ADR-015). The design-time audit is in
docs/plan/01 § ADR-015; what is actually depended on is
docs/manual/third-party.md, where every licence names the artefact it
was read from and a build gate keeps the inventory honest.
⚠ That page corrects a claim this section used to make — that every dependency is permissive and
"no shipped game links anything that is not". Silk.NET.OpenAL.Soft.Native declares
LGPL-2.0-or-later and ships libopenal in every build with sound. Dynamically linked against a
separately-shipped library, which is what it is, that is dischargeable — but it is copyleft, its
obligations are not discharged today, and four further packages ship no licence statement at all.
Read the page before making a binary distribution.