Target Frameworks
Note
Status: implemented. The shipped libraries multi-target net8.0 and net10.0; the CLI and
the diagnostics-only projects target net10.0.
Table of contents
- Goal
- The problem: two clocks, one deadline
- The shape
- Key design decisions
- Boundary cases
- Testability
- When Godot moves
Goal
Keep DialogueDown on a supported .NET runtime without dictating when a game using it must move.
The problem: two clocks, one deadline
.NET 8 and .NET 9 both reach end of support on November 10, 2026. .NET 10 is the LTS release,
supported to November 2028. So staying on net8.0 means shipping on an unsupported runtime, and
.NET 9 is no answer at all: it expires the same day as the version it would replace.
The complication is that DialogueDown does not control the runtime its primary consumer uses. The Godot documentation states it plainly:
Godot bundles the parts of .NET needed to run already-compiled games.
An exported Godot game runs on the runtime Godot ships, not the one the developer built with. Godot 4.4+ baselines on .NET 8, and its move to .NET 10 is still in progress. So the two clocks run independently: ours is a support deadline, Godot's is a release schedule, and neither waits for the other.
Picking a single target framework forces a choice between them:
| Single target | Cost |
|---|---|
net8.0 |
Unsupported after November 10, 2026. |
net10.0 |
Every Godot project must move the day we do — and it cannot until Godot does. |
The shape
Neither, then. The libraries a game references multi-target, and everything else takes LTS:
| Project | Framework | Why |
|---|---|---|
DialogueDown |
net8.0;net10.0 |
Referenced by a game; must load on Godot's bundled runtime. |
DialogueDown.ConfigurationLoader |
net8.0;net10.0 |
Same — a game reads its own dialogue.toml. |
DialogueDown.Playbook |
net8.0;net10.0 |
Same — a game embeds the playbook types to read a compiled script. |
DialogueDown.Runtime |
net8.0;net10.0 |
Same — a game runs the playbook with it. |
DialogueDown.Cli |
net10.0 |
A developer tool. It never enters a game export, so nothing about Godot constrains it. |
DialogueDown.Visualization, .Live |
net10.0 |
Diagnostics only, not shipped in the core package. |
The published package carries both builds — lib/net8.0/ and lib/net10.0/ — and NuGet picks per
consumer. A Godot project on net8.0 resolves the net8.0 assembly and never learns the other
exists; a project on net10.0 gets the LTS build. The choice moves to the consumer, which is the
only place that knows which runtime it will run on.
Key design decisions
D1 — Multi-target the consumer surface, not everything
Multi-targeting is not free: every target is another build and another set of test runs, and any
API a target lacks has to be #if-conditional. So it is spent only where it buys something — the
libraries a game actually references. Applying it to the CLI or the visualizer would double their
build for a compatibility nobody consumes.
D2 — The CLI takes LTS immediately
DialogueDown.Cli is the artifact the release publishes, and it is the one with genuine EOL
exposure. It is also a developer tool: it compiles and visualizes scripts on a developer's machine
and never ships inside a game, so moving it to net10.0 cannot break an export. Its package
therefore carries tools/net10.0 alone.
D3 — The SDK floor is not the consumer floor
global.json pins SDK 10.0.0 because building net10.0 requires it. That is a contributor
requirement and says nothing about consumers: the SDK builds every target the solution declares,
including net8.0. Conflating the two is the mistake this note exists to prevent — which is why
global.json now says so in its own comment.
Boundary cases
| Case | Behavior |
|---|---|
| A Godot project on Godot's bundled .NET 8 | Resolves lib/net8.0/; unaffected by anything here. |
| A consumer already on .NET 10 | Resolves lib/net10.0/ and gets the LTS runtime. |
| Running the test suite | The multi-targeted test projects run twice, once per framework, so a regression on either is caught. |
| CI | Installs the .NET 8 and .NET 10 runtimes: the SDK builds both targets, but executing the net8.0 test run needs the .NET 8 runtime. |
| A contributor with only the .NET 8 SDK | Restore fails with a clear SDK-version error rather than a confusing target-framework one. |
Testability
Dropping net8.0 from a shipped library is the dangerous edit, because nothing else would
notice: the solution still builds, every test still passes, and only a consumer's Godot export
fails — somewhere else, for someone else. A guardrail test therefore reads the shipped project
files directly and fails if one stops offering net8.0 or net10.0, naming the consequence in
its message. It covers DialogueDown, DialogueDown.ConfigurationLoader, and
DialogueDown.Playbook; DialogueDown.Runtime is missing from its list. It lives beside the
other repository-manifest guards in
dev-dotnet-tasks.test.mjs.
When Godot moves
Once Godot's bundled runtime reaches .NET 10, net8.0 can be dropped from these libraries and this
note revised to record it. That is a one-line change per project plus the guardrail — the work of
carrying two targets was done here precisely so the eventual move is trivial. Nothing else waits on
it: the net8.0 lane already carries no out-of-band package, since the enum wire names moved to
hand-written converters (Enum wire names).