Skip to main content

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:

  1. Rust fork vs C# fork
  2. C# IL/CoreCLR vs C# NativeAOT
  3. 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:

  • view is 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.md in the historical SERS workspace
  • docs/host-integration.md in the historical SERS workspace
  • sdk/dotnet/Sers.Sdk/SersTypedDispatch.cs in 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.0 and netstandard2.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

DimensionRust Fork (current direction)C# Fork (Roslyn-based)Winner
Core differentiatorNative compiled scripts with Rust semanticsNative C#-syntax modding with AST-level mergeDepends on product
Existing investmentAlready built and integratedMostly design-stageRust fork
Need for compiler forkAlready required and already paidOnly truly required for replace / inject AST mergeRust fork for today
Modder accessibilityHigher barrier; Rust syntaxLower barrier; C# is more familiarC# fork
Runtime performance ceilingHighestGood, but generally below Rust on heavy compute/allocation workloadsRust fork
GC-free gameplay logicYesNot by default unless you constrain design hardRust fork
Dynamic plugin ecosystemWeakerMuch stronger in mainstream C# ecosystemC# fork
Unity familiarityNative plugin + SDK patternFits Unity developer mental model betterC# fork
Cross-host neutralityStrong; host can be any language via C ABITends to bias toward .NET hostsRust fork
Tooling rebase costOngoing rustc/analyzer rebase costOngoing Roslyn/analyzer rebase cost if forkedNeither is cheap
Shipping on platforms where JIT is constrainedStrong through native codePossible through NativeAOT, but with major dynamic limitationsRust 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 template member replacement
  • compile-time inject member 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 10 as the current LTS line
  • .NET 9 as the current STS line
  • .NET 8 as 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.1 or .NET Framework 4.8
  • managed plug-ins targeting .NET Core are not supported
  • managed plug-ins targeting .NET Standard are supported
  • managed plug-ins targeting .NET Framework have limited or supported status depending on project compatibility level

Sources:

Matrix

DimensionC# IL on CoreCLRC# NativeAOTWinner
Latest .NET supportYesYesTie
Dynamic assembly/plugin loadingYes, via AssemblyLoadContextNoIL/CoreCLR
Mod hot reload / layered script DLL loadingGoodPoorIL/CoreCLR
Startup timeGoodBetterNativeAOT
Memory footprintHigherLowerNativeAOT
Shipping to non-.NET host as native libraryIndirectDirectNativeAOT
Native shared library consumptionNot the primary shapeFirst-class documented shapeNativeAOT
Reflection-heavy frameworksGoodRisky / restrictedIL/CoreCLR
Runtime code generationYesNoIL/CoreCLR
Unity managed plug-in friendlinessBetter if targeting netstandard2.1 contractsNative payload is fine, managed .NET Core assembly is notSplit model
Console / restricted-JIT environmentsWorseBetterNativeAOT
Iteration speed for scripting workflowsBetterWorseIL/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 .NET managed assemblies as a direct Unity plug-in target. Use netstandard2.1 for 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.md in the historical SERS workspace
  • docs/unity-integration.md in the historical SERS workspace

Target artifact model

Use three layers of artifacts:

  1. Game.Contracts.dll
  2. Game.Scripting.*
  3. 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.dll for IL/CoreCLR development and dynamic plugin loading
  • game_scripts_native.dll/.so/.dylib for 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/Managed managed assemblies intended for Unity consumption
  • Runtime/Plugins native payloads
  • asmdef files 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:

Matrix

Packaging choiceBenefitCostRecommendation
Unity package defines contractsFastest short-term for Unity onlyLocks architecture to Unity and duplicates truthAvoid
Workspace defines contracts, Unity consumes themClean host-agnostic architectureMore packaging discipline neededRecommended
Contracts only in source form inside Unity repoEasy for one Unity productHard to reuse for non-Unity hosts/toolingAvoid
Publish contracts as versioned package/artifactStrong versioning and support storyRequires release disciplineRecommended

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
  • ref and 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 bool in 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:

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 / inject becomes productized

This keeps the monetizable boundary around integration value, tooling, and support quality, not just around language novelty.

  1. Keep Rust-first architecture as the source of truth.
  2. Build the shared contract model as a first-class workspace artifact.
  3. Generate netstandard2.1 contract assemblies for Unity and other .NET hosts.
  4. Add a standard-C# prototype path before any Roslyn fork.
  5. Benchmark three things:
    • IL/CoreCLR dynamic script path
    • NativeAOT shipping path
    • current Rust-native path
  6. Fork Roslyn only if member-level mod merge is confirmed as the differentiator worth selling and maintaining.

Concrete recommendation by problem

ProblemRecommended 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:

Primary local references used:

  • README.md in the historical SERS workspace
  • docs/host-integration.md in the historical SERS workspace
  • docs/unity-integration.md in the historical SERS workspace
  • docs/archive/comparison-sers-vs-csharp.md in the historical SERS workspace
  • claude-csharp-fork-design.md
  • claude-csharp-fork-features.md