A modular .NET game engine monorepo: archetype ECS, behavior trees, stateless physics
queries and rigid-body dynamics, a WebGPU (Dawn) renderer with a Slang shader pipeline
targeting desktop and the browser, glTF/KTX2 asset loading, an asset build pipeline driven
by the paradise CLI, SDL windowing, Wwise audio, and ImGui/NoesisGUI UI integrations.
Targets net10.0, C# 14, NativeAOT/trimming compatible.
All packages are published to NuGet from a single version tag — the libraries below plus
Paradise.Cli, which ships as a dotnet tool rather than a reference.
| Package | NuGet | Description |
|---|---|---|
| Paradise.BLOB | Standalone unmanaged binary blob builder (BlobArray, BlobString, BlobPtr) | |
| Paradise.Features | Feature switches layered from configuration, environment and CLI | |
| Paradise.Features.Toml | TOML configuration reader, isolated from switch-only consumers | |
| Paradise.Hosting | — | Application lifetime, feature configuration, fixed-step loops, capture scheduling and pooled snapshot transport |
| Paradise.Hosting.Desktop | — | SDL/offscreen platform adapters for application hosting |
| Paradise.Physics | Stateless collision queries (raycasts, shape casts) and rigid-body sphere dynamics (gravity, Coulomb friction, spin) | |
| Paradise.Export | Engine-neutral export core for editor hosts: exported-data contract, DotRecast navmesh baking, Blender/KTX tool orchestration | |
| Paradise.Authoring |
[Authored] records and generated editor schemas |
| Package | NuGet | Description |
|---|---|---|
| Paradise.ECS | Archetype-based ECS core; ships its source generator for queryables/systems | |
| Paradise.ECS.Tag | Zero-size tag component support | |
| Paradise.ECS.Managed | Managed class components with explicit snapshot policies, deferred commands and generated queries/systems | |
| Paradise.ECS.Concurrent | Concurrent command buffers and thread-safe structural changes | |
| Paradise.ECS.Jobs | Parallel job scheduling over ECS chunks |
| Package | NuGet | Description |
|---|---|---|
| Paradise.BT | Behavior tree runtime (inspired by EntitiesBT); ships its source generator | |
| Paradise.BT.Builder | Authoring DSL base classes | |
| Paradise.BT.Nodes | Built-in node library (Sequence, Selector, Parallel, decorators, delay) |
| Package | NuGet | Description |
|---|---|---|
| Paradise.Rendering | Backend-agnostic rendering data contract: handles, descriptors, reflection records | |
| Paradise.Rendering.WebGPU | WebGPU (Dawn) backend via WebGPUSharp | |
| Paradise.Rendering.Browser | WebAssembly WebGPU backend with a bundled JavaScript bridge | |
| Paradise.Geometry | Wide quantized BVHs with CPU construction and reference traversal | |
| Paradise.Rendering.Pbr | PBR metallic-roughness scene renderer with embedded Slang-compiled shaders, Forward+ lights, shadow maps, and runtime probe global illumination over a compute ray tracer |
Runtime readers — what a host links against to load what the pipeline built:
| Package | NuGet | Description |
|---|---|---|
| Paradise.Assets.Gltf | AOT-clean GLB/glTF 2.0 reader scoped to the Paradise export contract | |
| Paradise.Assets.Textures | KTX2 texture transcoding (BasisLZ/UASTC) via libktx |
The build-time asset pipeline — authoring-side only; a host that merely mounts a built tree never references these:
| Package | NuGet | Description |
|---|---|---|
| Paradise.Assets.Project | Project layout, manifest, shared artifact cache and Zio mounts | |
| Paradise.Assets.Documents | Canonical TOML, sidecars and prefab documents, mirrored in the Blender addon | |
| Paradise.Assets.Pipeline | Verification, importers and asset build operations on Zio |
| Package | NuGet | Description |
|---|---|---|
| Paradise.Windowing | Backend-agnostic windowing contract: window control, render surfaces, timestamped raw device input | |
| Paradise.Windowing.Sdl | SDL3 implementation of that contract, with WebGPU-ready surface descriptors for Win32/Cocoa/Wayland/X11 | |
| Paradise.Audio.Wwise | Audiokinetic Wwise integration; managed bindings only (requires a Wwise licence and a local SDK install, from which the native shim is built) |
| Package | NuGet | Description |
|---|---|---|
| Paradise.Ui | Engine-neutral UI input contract: the UiEvent stream, the sim-thread IUiInput half, and CompositeUiInput fan-out for stacking UI systems |
|
| Paradise.Ui.ImGui | Dear ImGui debug/overlay UI on the WebGPU backend | |
| Paradise.Ui.Noesis | NoesisGUI (XAML) integration (requires a NoesisGUI license) |
| Package | NuGet | Description |
|---|---|---|
| Paradise.Cli.Host | CLI library and custom-importer entry point: BuildHost.Run(args, importers)
|
|
| Paradise.Cli | The paradise dotnet tool for project, asset and toolchain operations |
Source generators (Paradise.ECS.Generators, Paradise.BT.Generators,
Paradise.Authoring.Generators) are not published standalone — they ship inside
Paradise.ECS, Paradise.BT and Paradise.Authoring under analyzers/dotnet/cs, so
referencing those packages activates the codegen automatically.
-
src/— library, test (*.Test), generator, and benchmark projects. - ParadiseSamples — native and browser sample applications, their NativeAOT smoke test, and website deployment. Run the browser demos.
-
src/Directory.Build.props/src/Directory.Packages.props— shared build settings, shared NuGet package metadata, and centrally managed package versions. -
src/Slang.targets— Slang → WGSL shader toolchain (downloads a pinnedslangcpertools/slang/slang.manifest.json, compiles and embeds shaders at build time). -
src/Ktx.targets— libktx native-library staging for platforms not covered by Ktx2.NET. -
tools/slang/,tools/ktx/— pinned external toolchains (manifest + bootstrap) for the Slang shader compiler and thektx createCLI the texture step shells out to.paradise tools doctorreports both;paradise tools install <ktx|slang>fetches one. -
ParadiseEngine.slnx— top-level solution covering all projects. -
AGENTS.md— architecture notes, custom node patterns, and the coordinate convention. The canonical agent guide, shared across AI tools;CLAUDE.mdimports it.
Right-handed, Y-up, −Z forward, +X right (Godot / glTF standard), meters, column-major
matrices. Editor tools (ParadiseGodotEditor) export this data verbatim — no handedness
conversion anywhere in the pipeline.
dotnet build ParadiseEngine.slnx
dotnet test --solution ParadiseEngine.slnx --output normal
# Single project
dotnet build src/Paradise.BT/Paradise.BT.csproj
dotnet test src/Paradise.BT.Test/Paradise.BT.Test.csproj --output normalTests use TUnit on Microsoft.Testing.Platform. The first build of a shader-owning project downloads the pinned Slang toolchain (cached under the NuGet package root).
The paradise CLI builds an assets/ tree and its project.toml into build/.
Install globally, or use a repository tool manifest:
dotnet tool install --global Paradise.Cli
# Per repository: dotnet new tool-manifest && dotnet tool install Paradise.Cliparadise new MyGame # scaffold an asset project
paradise assets verify # validate sidecars, identities and documents
paradise assets verify --fix # repair stale reference paths
paradise assets build # assets/ -> build/; --editor uses .editor/play/
paradise assets watch # maintain sidecars and rebuild
paradise assets mv <from> <to> # move assets, sidecars and reference hints
paradise assets rm <path> # refuse referenced assets unless --force
paradise assets refs <path> # references in both directions; --transitive recurses
paradise assets extract <glb> # extract parts; --all processes a directory
paradise host play --scene assets/levels/arena.prefab
paradise host play --watch # run through dotnet watch
paradise host build # build the launcher
paradise tools doctor # tool versions and installation helpUse paradise --help for verbs and shared --project / --profile options.
[host] names the launcher relative to the project root and arguments placed before caller arguments:
[host]
project = "Game.Launcher/Game.Launcher.csproj"
arguments = ["--ui", "ui/Shell.xaml"]host play builds assets into .editor/play/, updates the launcher, runs the scene's built path
and waits for exit. Its exit code matches the game; SIGTERM or Ctrl+C stops the process tree.
--no-build reuses binaries, --no-assets skips assets, -c Release selects configuration,
and arguments after -- go to the game.
Freshness checks use the reference closure in obj/project.assets.json, including injected
ProjectReferences. Newer source/project files trigger MSBuild; restore runs only when project files
changed. Unchanged inputs skip it; the first check after an external build requires a no-op pass.
--watch uses dotnet watch run --non-interactive. Method edits hot-patch; other changes may
restart. Hot Reload does not rerun constructors or static initializers: force a restart, such as
by changing a signature, for those edits. Launchers enabling PublishAot need
StartupHookSupport=true in Debug for the watch agent.
paradise assets extract Models/crate.glb creates mesh, skeleton and clip reference documents,
material documents, external images and a prefab. The GLB remains source: build cooks .mesh /
.skinnedmesh to Paradise blobs and .skeleton / .anim to ozz archives. A skinned mesh names
its skeleton. Runtime consumers load cooked files and built materials. Clips retain keys unless
the GLB sidecar enables [glb] optimize = { tolerance = 0.001, distance = 0.1 }.
Routes are assets-relative:
[extract]
directory = "models" # common fallback
meshes = "models" # .mesh / .skinnedmesh
skeletons = "animations" # defaults to the meshes route
animations = "animations" # .anim
materials = "materials"
textures = "textures"
prefabs = "prefabs/models"
tilesets = "tilesets" # a kind declared by a game importerWithout routes, outputs stay beside the container. Kind-specific fallbacks precede directory;
a per-GLB [glb] extract folder overrides all routes. Routing affects new files only: recorded
outputs retain identity and location. Move them explicitly with assets mv.
Watchers freely mint/update tool-owned mesh, skeleton and clip documents. Materials, images and
the generated prefab become authored files. Extraction tracks container and document fingerprints:
re-exports update materials while retaining Paradise-only fields, material edits can update the
GLB's glTF fields, and image edits are reported. Changes on both sides require --take-glb or
--take-document. The prefab is created once and never synchronized. KTX2 is build output;
verify rejects authored KTX2 beneath assets/.
An authored reference is { guid = "…", path = "…" }. GUID identifies; path is a readable hint.
AssetIndex resolves identity through sidecars. Stale hints are warnings repaired by verify --fix;
a missing identity is an error even if the hinted path exists. Renames preserve references when
the sidecar travels or the watcher relinks it by content hash. The watcher holds deleted identities
for 30 seconds, then reports remaining dangling references.
Container texture URIs use [mesh] sidecar entries { slot, uri, guid, path }. The DCC follows
the URI; the pipeline follows the GUID. verify --fix and watch record missing entries and update stale URIs
when the format supports rewriting. A changed source URI is treated as a re-export and resolved
again. ReferenceGraph derives edges per run from documents and sidecars; it is never persisted.
Moves follow dependents, removal protects referenced assets, and refs lists both directions.
Sidecar creation records importer = "mesh" using Claims, with appended importers taking
precedence. All later verbs honor that name. Change the line to choose another importer for an
existing asset; changing chain order affects newly claimed assets. Unknown names are errors,
missing names are repairable warnings, and a named importer declining an asset fails its build.
Implement IAssetImporter: Claims examines the path and at most a header; Import builds it.
The global CLI can load public, parameterless importer classes from prebuilt assemblies:
[extensions]
assemblies = ["tools/assets/bin/Debug/net10.0/MyGame.Assets.dll"]For extensions with source in the project, list the project paths instead:
[extensions]
projects = ["tools/YarnExtension/YarnExtension.csproj", "tools/AudioExtension/AudioExtension.csproj"]Before loading an extension, the CLI runs an incremental Release publish and places the DLL and
its dependencies under .editor/extensions/. YarnExtension.csproj produces
.editor/extensions/YarnExtension.dll; no output path or DLL mapping is configured.
The project must retain its default assembly name (the project filename without .csproj or .proj).
It runs on every watcher start or other extension-loading command; MSBuild incrementality avoids
unnecessary recompilation. A fresh checkout needs no prepublished DLL, and restarting the watcher
picks up extension source changes. Build failures stop the command without loading an old DLL.
Each publish also retracts the files that project's previous publish produced and the new one no
longer does, so a dropped dependency does not stay resolvable beside the DLL.
Projects with colliding output names are rejected. --dry-run and assets clean skip publishing.
The assemblies list remains available for extensions supplied as prebuilt binaries.
Paths are project-root-relative. Importers append to the same chain used by every verb, including watch and host play. Dynamic loading requires compatible Paradise versions; the CLI therefore is not trimmed or NativeAOT-published. For CI, a game-owned console tool gives MSBuild control of the complete dependency graph:
return Paradise.Cli.BuildHost.Run(args, [.. AssetImporters.All, new MyBankImporter()]);Implement References to expose sites and Rewrite to repair them; source bytes may change only
when the context permits. This integrates the kind with build, verify, watch, move, remove and refs.
Extraction uses the same importer and recorded name. Declare output kinds to enable it:
public string Name => "crate";
public bool Claims(ImportCandidate candidate) => candidate.Asset.GetExtensionWithDot() == ".crate";
public IReadOnlyList<ExtractKindDeclaration> ExtractKinds { get; } =
[
new("tilesets"),
new(ExtractKind.Materials),
];HasParts, HasAuthoredParts, IsExtracted, Extract and MintReferences have defaults for
non-container importers. Verify rejects routes for undeclared kinds. Call extraction only from
extract/watch: ImportContext.FileSystem is read-only, and writing build inputs would invalidate
the incremental index.
Shared extraction APIs handle these contracts:
-
ExtractionRecordstores kind, ownership, index, name, identity and two fingerprints in the[extract]sidecar domain. -
ToolOwnedparts follow the container;TwoSideddocuments can change on either side;Blobparts are authored bytes with no write-back operation. -
ExtractionSync.Decideclassifies changes and conflicts. AfterTakeDocument, fingerprints agree only if the importer can write the edit back. - Generated prefabs are unrecorded; validate their route immediately after writing.
-
SidecarMaintainer.Ensuremints identity;AssetIndex.Resolvefinds moved outputs by GUID. Update recorded path hints when resolving them.
See GameExtractorTests for a complete public-API example with routing, conflicts and moves.
Versions are centrally managed in src/Directory.Packages.props; see that file for the
reasoning behind each pin.
| Library | License | Used for |
|---|---|---|
| Microsoft.CodeAnalysis.CSharp (Roslyn) | MIT | Source generators (ECS, BT, Authoring) and their tests |
| Microsoft.Extensions.Logging.Abstractions | MIT | The engine's logging contract; hosts choose the sink |
| System.Runtime.CompilerServices.Unsafe | MIT | Low-level unmanaged/blob code |
| System.Text.Json | MIT | JSON reading/writing in the asset pipeline |
| WebGPUSharp | MIT | Dawn/WebGPU bindings for Paradise.Rendering.WebGPU
|
| Noesis.GUI | Commercial (requires a NoesisGUI licence) | NoesisGUI (XAML) player-facing UI, Paradise.Ui.Noesis
|
| Hexa.NET.ImGui | MIT | Dear ImGui binding for debug/dev tooling, Paradise.Ui.ImGui
|
| Hexa.NET.ImGuizmo | MIT | Transform gizmos for the editor's Scene panel |
| ppy.SDL3-CS | MIT | SDL3 windowing/input, Paradise.Windowing.Sdl
|
| Ktx2.NET | Apache-2.0 (wraps libktx) | KTX2 texture transcoding, Paradise.Assets.Textures
|
| Zio | BSD-2-Clause | Filesystem abstraction every asset path goes through |
| Tomlyn | BSD-2-Clause | TOML reading for authored documents and project.toml
|
| DotRecast.Core / .Detour / .Recast | zlib | Navmesh baking in Paradise.Export
|
| ozz-animation archive format | MIT |
Paradise.Animation is a managed port reading/writing ozz's v2/v7 archives (no native dependency) |
| TUnit | MIT | Test framework (Microsoft.Testing.Platform) |
| Microsoft.Coyote.Test | MIT | Systematic concurrency testing (*.CoyoteTest projects) |
| BenchmarkDotNet | MIT | Benchmarking (e.g. Paradise.Animation.Benchmarks) |
Not on NuGet, vendored/downloaded by the build itself:
| Tool | License | Used for |
|---|---|---|
Slang (slangc) |
Apache-2.0 WITH LLVM-exception | Slang → WGSL shader compilation, via src/Slang.targets
|
libktx (ktx CLI) |
Apache-2.0 | KTX2 texture creation, via src/Ktx.targets
|
| Audiokinetic Wwise | Commercial (requires a Wwise licence) | Native audio engine behind Paradise.Audio.Wwise (requires a licensed local SDK) |
Pushing a v* tag (or manually dispatching the Publish NuGet packages workflow with a
version) packs all library projects at that version and pushes them to nuget.org via OIDC
trusted publishing:
git tag v0.2.0
git push origin v0.2.0-
src/Paradise.BLOB/README.md— blob builders and serialization format -
src/Paradise.BT/README.md— behavior tree pipeline, custom nodes, serialization -
src/Paradise.Physics/README.md— collision world and query semantics