C# Fork Decision Matrix
Decision document for evaluating a forked C# path alongside the existing Rust-first SERS product.
Date context:
- Current workspace date: 2026-03-31
- Microsoft support policy page last updated: 2026-03-12
- Unity API compatibility page build date: 2026-03-31
Purpose
This document answers three concrete questions:
- Rust fork vs C# fork
- C# IL/CoreCLR vs C# NativeAOT
- Unity-first contract packaging and strongly typed host/template contracts
It also adds the missing commercial view:
- what product benefits a C# path creates
- what new support and maintenance burden it creates
- what shape of commercial offering is coherent
Current SERS Baseline
Today SERS is Rust-first:
viewis the shared memory layout between host and scripts- templates are auto-exported through native vtables
- the C# SDK loads typed unmanaged vtables and dispatches through unmanaged function pointers
- Unity is a consumer/package boundary, not the canonical source of interop contracts
Repo evidence:
README.mdin the historical SERS workspacedocs/host-integration.mdin the historical SERS workspacesdk/dotnet/Sers.Sdk/SersTypedDispatch.csin the historical SERS workspace
Important current properties:
- The host boundary is already memory-first and ABI-first, not object-graph-first.
- The SDK already targets both
net8.0andnetstandard2.1. - Unity packaging already sits downstream of this workspace and consumes built artifacts rather than defining the authoritative shared contract.
1. Rust Fork vs C# Fork
Decision Summary
Fork Roslyn only if the product goal is SERS-style member-level mod merging for C#.
If the goal is only:
- strongly typed contracts
- better C# ergonomics
- Unity package consumption
- shipping native payloads
then a full C# compiler fork is not yet justified.
Matrix
| Dimension | Rust Fork (current direction) | C# Fork (Roslyn-based) | Winner |
|---|---|---|---|
| Core differentiator | Native compiled scripts with Rust semantics | Native C#-syntax modding with AST-level merge | Depends on product |
| Existing investment | Already built and integrated | Mostly design-stage | Rust fork |
| Need for compiler fork | Already required and already paid | Only truly required for replace / inject AST merge | Rust fork for today |
| Modder accessibility | Higher barrier; Rust syntax | Lower barrier; C# is more familiar | C# fork |
| Runtime performance ceiling | Highest | Good, but generally below Rust on heavy compute/allocation workloads | Rust fork |
| GC-free gameplay logic | Yes | Not by default unless you constrain design hard | Rust fork |
| Dynamic plugin ecosystem | Weaker | Much stronger in mainstream C# ecosystem | C# fork |
| Unity familiarity | Native plugin + SDK pattern | Fits Unity developer mental model better | C# fork |
| Cross-host neutrality | Strong; host can be any language via C ABI | Tends to bias toward .NET hosts | Rust fork |
| Tooling rebase cost | Ongoing rustc/analyzer rebase cost | Ongoing Roslyn/analyzer rebase cost if forked | Neither is cheap |
| Shipping on platforms where JIT is constrained | Strong through native code | Possible through NativeAOT, but with major dynamic limitations | Rust fork |
| Distinct market story | "Full language fork with native performance and safe mod merging" | "C#-native modding product with strong typing and better adoption" | Depends on audience |
What a C# fork actually buys
The local C# design notes are consistent on this point: the one feature that truly requires a fork is AST-level mod merging.
Without a fork, standard C# can already provide:
- strongly typed views
- strongly typed schemas/interfaces
- generated wrappers and metadata
- source generators
- analyzers
- NativeAOT shipping
- managed or native host-side SDKs
What standard C# cannot provide cleanly is:
- compile-time
replace templatemember replacement - compile-time
injectmember addition across mod layers - merged type-checked compilation of layered content as one logical template
Local references:
Recommendation
Recommendation:
- Keep Rust as the engine-first flagship product.
- Treat C# as a second product line only if you want to sell "SERS semantics for Unity/C# developers", not merely "a nicer SDK".
- Do not fork Roslyn to get strong typing alone. That is too expensive for too little gain.
- Fork Roslyn only when you are ready to productize member-level mod merging as the commercial differentiator.
2. C# IL/CoreCLR vs C# NativeAOT
External platform facts
As of 2026-03-31, Microsoft's current supported lines relevant to this decision are:
.NET 10as the current LTS line.NET 9as the current STS line.NET 8as an active LTS line still present in current NativeAOT guidance
Microsoft also documents that NativeAOT:
- produces self-contained native code
- improves startup time and memory footprint
- targets a specific runtime environment
- does not support dynamic loading such as
Assembly.LoadFile - does not support runtime code generation such as
System.Reflection.Emit
Microsoft also documents that NativeAOT native libraries:
- can be consumed from non-.NET languages
- are self-contained
- only support shared libraries
- do not support unloading via
dlclose/FreeLibrary
Unity currently documents:
- project code compiles against
.NET Standard 2.1or.NET Framework 4.8 - managed plug-ins targeting
.NET Coreare not supported - managed plug-ins targeting
.NET Standardare supported - managed plug-ins targeting
.NET Frameworkhave limited or supported status depending on project compatibility level
Sources:
- https://dotnet.microsoft.com/en-us/platform/support/policy/dotnet-core
- https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/
- https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/libraries
- https://learn.microsoft.com/en-us/dotnet/core/tutorials/creating-app-with-plugin-support
- https://docs.unity3d.com/Manual/dotnet-profile-support.html
Matrix
| Dimension | C# IL on CoreCLR | C# NativeAOT | Winner |
|---|---|---|---|
| Latest .NET support | Yes | Yes | Tie |
| Dynamic assembly/plugin loading | Yes, via AssemblyLoadContext | No | IL/CoreCLR |
| Mod hot reload / layered script DLL loading | Good | Poor | IL/CoreCLR |
| Startup time | Good | Better | NativeAOT |
| Memory footprint | Higher | Lower | NativeAOT |
| Shipping to non-.NET host as native library | Indirect | Direct | NativeAOT |
| Native shared library consumption | Not the primary shape | First-class documented shape | NativeAOT |
| Reflection-heavy frameworks | Good | Risky / restricted | IL/CoreCLR |
| Runtime code generation | Yes | No | IL/CoreCLR |
| Unity managed plug-in friendliness | Better if targeting netstandard2.1 contracts | Native payload is fine, managed .NET Core assembly is not | Split model |
| Console / restricted-JIT environments | Worse | Better | NativeAOT |
| Iteration speed for scripting workflows | Better | Worse | IL/CoreCLR |
Implication
These two shapes solve different problems:
- IL/CoreCLR is the right development-time and dynamic-mod-loading shape.
- NativeAOT is the right shipping/native-host shape.
Trying to make one artifact do both is where the design goes wrong.
Recommendation
Recommendation:
- Use IL/CoreCLR for dev-time script compilation, dynamic loading, tooling, and fast iteration.
- Use NativeAOT only for shipping payloads or non-.NET/native host scenarios.
- Do not use NativeAOT as the primary mod-loading architecture if you want runtime-loaded script DLLs.
- Do not rely on latest
.NETmanaged assemblies as a direct Unity plug-in target. Usenetstandard2.1for Unity-facing contracts and SDK surface.
3. Unity-First Contract Packaging
Core principle
Unity can be the customer-facing package boundary. Unity should not be the canonical type-contract authoring boundary.
That matches the existing workspace structure already documented in the repo:
- shared interop and runtime ABI live here
- Unity package content lives in
unity/package
Local references:
README.mdin the historical SERS workspacedocs/unity-integration.mdin the historical SERS workspace
Target artifact model
Use three layers of artifacts:
Game.Contracts.dllGame.Scripting.*- Unity package consumer artifacts
1. Game.Contracts.dll
Authoritative shared contract assembly.
Contains:
- views
- schema interfaces/contracts
- generated IDs and metadata
- blittable view structs
- generated typed vtable layouts
- helper APIs intentionally exposed to scripts/modders
Target:
netstandard2.1
Why:
- Unity supports it
- non-Unity .NET hosts can still consume it
- contract types stay stable and host-agnostic
2. Game.Scripting.*
Per-runtime implementation output.
Possible shapes:
Game.Scripts.dllfor IL/CoreCLR development and dynamic plugin loadinggame_scripts_native.dll/.so/.dylibfor NativeAOT/native-host shipping- existing Rust-native bundle outputs on the Rust side
These artifacts reference or are generated from the same contract model.
3. Unity package consumer
The Unity package should contain:
Runtime/Managedmanaged assemblies intended for Unity consumptionRuntime/Pluginsnative payloadsasmdeffiles wiring Unity assemblies together- samples, editor tooling, bootstrap, docs
Unity documentation also reinforces:
- code should be organized via assembly definitions
- package manifest and versioning are part of the package boundary
- assembly define constraints and version defines can gate platform-specific content
Sources:
- https://docs.unity3d.com/Manual/CustomPackages.html
- https://docs.unity3d.com/Manual/class-AssemblyDefinitionImporter.html
Matrix
| Packaging choice | Benefit | Cost | Recommendation |
|---|---|---|---|
| Unity package defines contracts | Fastest short-term for Unity only | Locks architecture to Unity and duplicates truth | Avoid |
| Workspace defines contracts, Unity consumes them | Clean host-agnostic architecture | More packaging discipline needed | Recommended |
| Contracts only in source form inside Unity repo | Easy for one Unity product | Hard to reuse for non-Unity hosts/tooling | Avoid |
| Publish contracts as versioned package/artifact | Strong versioning and support story | Requires release discipline | Recommended |
Strongly typed templates and host-side data
Yes, you should generate strongly typed contract code from the workspace side.
Recommended generated outputs:
LocationView,CharacterView, etc.IBuilding,IEvent, etc.- typed vtables such as
BuildingVtable - const/modifier metadata wrappers
- stable IDs/enums for schemas, stats, variables, and links where appropriate
This gives you:
- strongly typed host code
- strongly typed script-facing contracts
- fewer stringly typed APIs
- safer refactoring across hosts
- cleaner docs and better AI/codegen ergonomics
Memory boundary
Yes, direct memory access remains viable and desirable.
The current SERS SDK already demonstrates the shape:
- unmanaged function pointers
- blittable structs
- sequential layout
refand pinned memory dispatch
.NET interop guidance also explicitly recommends blittable types and fixed layout for performance-sensitive native boundaries.
Implication:
- the contract assembly should prefer blittable structs
- avoid
boolin shared ABI layouts unless explicitly marshalled - keep strings and managed object references out of hot-path shared structs
- use IDs/handles for graph references, not managed object pointers
Sources:
sdk/dotnet/Sers.Sdk/SersTypedDispatch.csin the historical SERS workspace- https://learn.microsoft.com/en-us/dotnet/standard/native-interop/best-practices
Benefits You Gain From a C# Path
Product benefits
- Lower adoption barrier for Unity developers
- Easier hiring and onboarding than Rust-first gameplay scripting
- Better fit for teams that already author gameplay in C#
- Stronger editor integration story in C#-centric studios
- Easier commercial messaging for "strongly typed moddable C# content"
- Larger potential modder funnel than Rust syntax
Technical benefits
- Reuse of mainstream C# ecosystem knowledge
- Better host/script mental-model alignment for Unity customers
- Easier source generation, analyzers, and code-first tooling
- Potentially simpler debugging for customers who stay fully in managed code
- NativeAOT shipping path available when you need native deployment
Documentation and support benefits
- Cleaner "contracts assembly" story for customers
- Easier to show sample projects, package APIs, and generated wrappers
- More familiar stack traces and language surface for support tickets
Costs and Risks You Add
Technical risks
- Two language products can fragment the architecture
- Roslyn fork maintenance is not free; it is a second compiler product
- NativeAOT removes the dynamic features that make managed scripting attractive
- Unity managed plug-in constraints prevent a naive "just use latest .NET everywhere" story
- GC and allocation discipline become product constraints if gameplay logic runs managed
Product risks
- Customers may not understand the difference between:
- managed contracts
- managed scripts
- native shipping payloads
- Unity package content
- A C# path can dilute SERS branding if positioned as "the real product" and Rust as legacy
- Supporting both Rust and C# authoring doubles docs, samples, support matrices, and QA surface
Commercial risks
- Wider market does not automatically mean lower support cost
- A more accessible language often increases ticket volume
- Unity customers expect polished package UX, editor tooling, asmdef hygiene, version compatibility notes, and migration support
- If both Rust and C# are sold, pricing and positioning must avoid making one feel like the "wrong purchase"
Commercial View
Where the C# path helps commercially
It improves the offer if the target customer is:
- Unity-first studio
- C#-heavy gameplay team
- technical designer / gameplay programmer who wants strong typing but not Rust
- middleware buyer who values ease of integration over maximal runtime performance
Commercial upside:
- broader top-of-funnel
- easier demos
- shorter proof-of-concept time
- stronger enterprise conversation around "typed contracts" and "safe content customization"
- better fit for teams that reject a Rust compiler fork on organizational grounds
Where the Rust path remains commercially stronger
It remains stronger if the target customer values:
- native performance as a primary selling point
- non-.NET host neutrality
- ABI stability and cross-language embedding
- long-term engine identity built around the Rust fork itself
- eventual console/native-heavy deployment story
Best commercial positioning
Best positioning is not "replace Rust with C#".
Best positioning is:
- Rust product: engine-grade, native-first, highest-performance, host-agnostic SERS
- C# product: Unity/C#-native authoring product with the same content model and stronger accessibility
That lets you sell two different value propositions without pretending they are the same thing.
Packaging and SKU suggestion
Commercially coherent packaging:
-
Core engine/toolchain SKU
- runtime
- CLI/toolchain
- contracts generation
- host SDK
-
Unity integration SKU
- package
- bootstrap/editor tooling
- samples
- validated platform payloads
-
Optional C# authoring SKU
- contracts generator
- C# analyzers/source generators
- later, Roslyn fork only if
replace/injectbecomes productized
This keeps the monetizable boundary around integration value, tooling, and support quality, not just around language novelty.
Recommended Path
Recommended sequence
- Keep Rust-first architecture as the source of truth.
- Build the shared contract model as a first-class workspace artifact.
- Generate
netstandard2.1contract assemblies for Unity and other .NET hosts. - Add a standard-C# prototype path before any Roslyn fork.
- Benchmark three things:
- IL/CoreCLR dynamic script path
- NativeAOT shipping path
- current Rust-native path
- Fork Roslyn only if member-level mod merge is confirmed as the differentiator worth selling and maintaining.
Concrete recommendation by problem
| Problem | Recommended answer |
|---|---|
| "Can we use latest .NET?" | Yes for tooling, host apps, and NativeAOT payloads. Do not equate that with Unity managed plug-in support. |
| "Can we load it in the host?" | Yes. For dynamic managed plugins, use IL/CoreCLR. For native payloads, use NativeAOT shared libraries or the existing Rust-native route. |
| "Can boundary access be memory-based?" | Yes. Keep ABI structs blittable and fixed-layout. |
| "Can Unity package define the contract?" | Unity package can distribute the contract, but the workspace should author and version it. |
| "Should we fork C# now?" | Not for strong typing alone. Only for AST-level merge semantics and modding product differentiation. |
Source Notes
Primary external sources used:
- .NET support policy: https://dotnet.microsoft.com/en-us/platform/support/policy/dotnet-core
- Native AOT overview: https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/
- Native AOT libraries: https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/libraries
- .NET plugin loading: https://learn.microsoft.com/en-us/dotnet/core/tutorials/creating-app-with-plugin-support
- .NET interop best practices: https://learn.microsoft.com/en-us/dotnet/standard/native-interop/best-practices
- Unity API compatibility: https://docs.unity3d.com/Manual/dotnet-profile-support.html
- Unity package workflow: https://docs.unity3d.com/Manual/CustomPackages.html
- Unity assembly definitions: https://docs.unity3d.com/Manual/class-AssemblyDefinitionImporter.html
Primary local references used:
README.mdin the historical SERS workspacedocs/host-integration.mdin the historical SERS workspacedocs/unity-integration.mdin the historical SERS workspacedocs/archive/comparison-sers-vs-csharp.mdin the historical SERS workspace- claude-csharp-fork-design.md
- claude-csharp-fork-features.md