Distribution via NuGet — how games consume the engine
Games reference the engine as the MainframeEngine NuGet package (nuget.org), with no checkout of this repo
and no editor needed; MainframeEngine.Templates (same version) provides dotnet new mfgame. Both are owned by the
Mainframe-Games organization on nuget.org. The editor is a separate, optional download whose New Project creates package-based games
at its own version. Project references into a checkout remain for working on the engine itself
(dotnet new mfgame --engine-source project). Decision: ADR 0188.
dotnet add package MainframeEngine # any .NET 10 projectdotnet new install MainframeEngine.Templates # or the template: a whole game projectdotnet new mfgame -n MyGame # references MainframeEngine of the template's versionThe package
Section titled “The package”The engine is more than a DLL: it has per-platform natives, engine content (compiled shaders, UI, fonts), a Roslyn source generator that game code must run, and build steps. The package carries all of it:
| Path | Contents | How a game gets it |
|---|---|---|
lib/net10.0/ | MainframeEngine.dll + .xml docs; spine-csharp.dll (the vendored Spine runtime, no package of its own) | compile + run references |
runtimes/<rid>/native/ | mfrmlui, mfsvg, enet for win-x64, linux-x64, osx-arm64, osx-x64 (the same universal files: NuGet does not pick osx-x64 assets for arm64) | NuGet runtime assets: listed in the app’s deps.json for RID-less dotnet run/test, copied for dotnet publish -r <rid> |
analyzers/dotnet/cs/ | MainframeEngine.Generators.dll | runs on every project that references the package: [Export], [Signal], [Replicated], [Rpc] |
mainframe/Content/** | exactly what the engine project copies to <output>/Content in a checkout build: .spv shaders (not the .slang sources), UI widgets, fonts, trees, brand | buildTransitive/MainframeEngine.targets copies it to the app’s Content/ (build and publish) |
tools/mf-l10n/ | mf-l10n (managed files + runtimeconfig; no deps.json, so the host loads the folder’s assemblies and none of the natives it never calls) | Localization.targets compiles Content/locale/**/*.po to .mo with it |
buildTransitive/ | MainframeEngine.props (imports MainframeGame.props), MainframeEngine.targets, Localization.targets, macos/ | imported by every project that references the package, directly or through a game library |
README.md, icon.png | the nuget.org page (MainframeEngine/PACKAGE.md) and the logo | — |
Dependencies (Silk.NET, SoundFlow, Jitter2, Box2D.NET, GetText.NET, NVorbis, Steamworks.NET, StbImageSharp, ENet-CSharp
without its natives and build files) are ordinary package dependencies with the versions from
Directory.Packages.props; exact pins ([1.4.1]) stay exact.
Content and natives: the same layout as a checkout
Section titled “Content and natives: the same layout as a checkout”Engine content lands in the same Content/ folder as the game’s, exactly where a checkout build puts it, so
ContentPaths and every engine path work unchanged. The package’s targets only copy into app projects
(Exe/WinExe, IsTestProject, or MainframeEngineCopyContent=true; false turns it off). A game library needs no
engine content, and the desktop project that references it would otherwise receive it twice.
The mainframe/Content list is not maintained by hand. The engine’s pack target (_MainframePackContent) takes this
project’s copy-to-output items whose target path is under Content/, the same items a project reference copies, minus
.pot sources. A new engine content file therefore ships in the package automatically.
Natives need no targets. A checkout build copies them flat because project references cannot express RID-specific
assets; for a package, NuGet’s runtimes/ handling does the job (SilkNativeResolver and ModelImporter probe
runtimes/<rid>/native as well).
Build integration
Section titled “Build integration”The checkout’s game-facing build files are packed unchanged, so package and checkout games build the same way:
MainframeGame.props, which checkout games import from theirDirectory.Build.props, provides theDEMOdefine andAssemblyMetadatafrom"isDemo", the app name and icon fromproject.mfproj, the macOS development.appbundle, andCopyLocalLockFileAssemblies. All of it is inert without aproject.mfprojabove the project.Localization.targetsseesMfL10nPathalready set (totools/mf-l10n), so it skips themf-l10nproject reference it uses in a checkout. SameLocaleContentRoot, same fallback to committed.mofiles.
Packing
Section titled “Packing”build/pack-nuget.sh [version] [out] (just pack) packs both packages at one version in three steps:
- It builds
mf-l10n. That project references the engine, so the engine’s own pack cannot build it: the reference would wait on the project instance that is packing. - It runs
dotnet pack MainframeEngine -c Release -p:Version=…. - It runs
dotnet pack MainframeEngine.Templatesat the same version. The packedtemplate.jsondefaultsEngineVersionto that version (_MainframeStampTemplateVersion; the source keeps0.0.0-dev). A template installed fromMainframeEngine.TemplatesX.Y.Z therefore creates games onMainframeEngineX.Y.Z with no options.
In the engine csproj:
IsPackableis false inDirectory.Build.props; only the engine andMainframeEngine.Templatesopt in.- The spine-csharp project reference is
PrivateAssets="all"only while packing (_IsPacking). Its DLL goes intolib/, and checkout projects still compile against Spine types (SpineNode.State). GenerateDocumentationFileis on. CS1591 (undocumented member), CS1573 (unlisted parameter) and CS0419 (a cref to an overload group) are suppressed; every other doc-comment check is an error.- Symbols ship as a
.snupkgwith SourceLink (PublishRepositoryUrl,EmbedUntrackedSources). - Packing is always Release, so the Debug-only
MainframeContentSourceattribute (UI hot reload from the checkout) never reaches a package.
Versioning and release
Section titled “Versioning and release”One version for the editor, the engine and the templates: the release tag vX.Y.Z (Release & versioning).
publish.yml works as follows:
- Its
packagejob packsMainframeEngine.X.Y.Z.nupkg+.snupkgandMainframeEngine.Templates.X.Y.Z.nupkg. - The
releasejob attaches the.nupkgfiles to the GitHub Release. - After the tag and the release exist, the same job pushes both packages to nuget.org. nuget.org packages can only be unlisted, so a failed release must never leave one behind.
Trusted Publishing, no stored API key. The release job has id-token: write. NuGet/login sends GitHub’s OIDC
token (repository, workflow, environment) to nuget.org, which checks it against a trusted publishing policy and returns
an API key valid for one hour. The policy on nuget.org (username → Trusted Publishing) is set as follows:
- Policy owner:
Mainframe-Games, the organization. - Repository owner:
Mainframe-Games. - Repository:
mainframe-engine. - Workflow file:
publish.yml. - Environment:
release. - Scopes: new packages and new versions, glob
MainframeEngine*.
There are no secrets. NUGET_USER in publish.yml (brogan) is the nuget.org username of the member who created the
policy. A policy stays active while that member belongs to the organization; if someone else re-creates it, change
NUGET_USER to their username.
Ownership. On nuget.org a package belongs to the owner that first pushes its id, here the policy’s owner. The
Authors metadata (“Mainframe Games”) is only display text. Both ids therefore belong to the organization, and its
members manage them. Reserving the MainframeEngine id prefix (a request to nuget.org) is optional: it adds the
verified check mark and stops others from using MainframeEngine.*.
Template and editor
Section titled “Template and editor”- Template.
dotnet new install MainframeEngine.Templates(nuget.org), from a checkout, or the copy the editor ships.dotnet new mfgamedefaults to--engine-source package, which writes<PackageReference Include="MainframeEngine" Version="$(MainframeEngineVersion)" />. The version comes from--engine-version, which defaults to the template package’s own version. From a checkout’s source template the default is0.0.0-dev, so pass a release, or use--engine-source project --engine-path ….- The game’s
Directory.Build.propssetsImportDirectoryPackagesProps=false, so a game inside a repository that uses central package management still builds. - Details: Projects & GameHost → Template.
- The game’s
- Editor.
- The published editor ships the template (
Templates/mfgame). - New Project’s Engine row chooses between two sources:
- package: MainframeEngine at the editor’s own version. It is the default in a release editor and needs no checkout.
- engine checkout: forced in development builds (
0.0.0-devhas no package).
- Details: Editor → Projects.
- The published editor ships the template (
build/template-smoke.shruns in two modes.ENGINE_SOURCE=projectisjust template-smoke.ENGINE_SOURCE=package(just template-smoke-package, CI jobtemplateon Linux/lavapipe) works like this:- It packs this checkout as
0.0.0-smoke.<timestamp>into a local feed (build/pack-nuget.sh: both packages). - It installs the template from the packed
MainframeEngine.Templatesand creates the game with no options. It checks that the game referencesMainframeEngineof that version (the stamped default) and adds anuget.configfor the feed. - It builds the game, runs 30 frames (no
[ERROR], a screenshot) and publishes it for the host RID. - It checks the engine content, the compiled
.moandmfrmluiin both outputs. - It removes the version from the package cache.
- It packs this checkout as
- Editor tests cover
NewProjectValidation.IsPackageVersionand the per-source validation anddotnet newarguments.
Not yet
Section titled “Not yet”- An “upgrade project” action. Moving a package game to another engine version means editing
MainframeEngineVersionin itsDirectory.Build.props; sceneformatmigrations already handle the data. - The Demo/Forest example downloads still reference a checkout (
EnginePathRewriter). Shipping them package-based is a follow-up. MainframeEngine.Sdk(<Project Sdk="MainframeEngine.Sdk/X.Y.Z">), which would hold the content rules, localization and RID defaults so that game projects become one-liners.- Pre-release packages from
main(-preview.N).
Related docs
Section titled “Related docs”Release & versioning · Projects & GameHost · Editor · Native libraries · Localization