Contributing - rebaserHEAD/NexusForever GitHub Wiki
How a change gets into upstream NexusForever, and what makes one look like it belongs there. Upstream writes down only part of its standard: the rest lives in an editor settings file and in the habits of the code, so this page explains where each part comes from and which wins when they disagree. The rules themselves are on three reference pages: Contributing code style, Contributing code patterns and Contributing pull requests.
How upstream works
One maintainer, decisions on Discord
One person merges everything. Design questions are settled on the project Discord, and pull request threads report the outcome in a line or two ("after discussion"). So if a change needs a decision, it needs one there before it needs a pull request.
The maintainer finishes pull requests
More than half of the pull requests merged since 2024 carry commits from the maintainer on the contributor's own branch: style fixes, a missing migration, removed tests, renames to line up with the expedition branch. Small style misses get fixed for you. What holds a pull request up is behaviour: something it breaks, logic the client wouldn't use, code sitting in the wrong layer.
That's also why a pull request's Allow edits by maintainers box should stay ticked.
A codebase mid-move
game_rework is being reworked in three directions at once:
- Static singletons are moving to dependency injection.
- The world server is splitting into services behind a message broker, each with its own database (Server architecture).
- Gameplay is being rebuilt on the expedition branch, to be merged back (Upstream branches).
So most patterns exist in two generations side by side, and copying the nearest example can copy the old one. The reference pages say which is which.
flowchart TD
D[Discord: the decision] --> P[Pull request]
P --> R[Review: behaviour, fidelity, layering]
R --> M[Maintainer fixes style on the branch]
M --> S[Squash into game_rework]
Where the rules come from
Four sources
| Source | What it covers | Weight |
|---|---|---|
| Upstream's contributing pages | Line endings, British English, braces, naming, summaries, enums, attributes, database naming and placement, bug reports | Written rule. Wins over any drift in the code |
Source/NexusForever.sln.DotSettings |
this., var, field prefixes, member order, attribute placement |
Applied as you type in ReSharper and Rider; other editors ignore it |
| The code itself | Everything else: namespaces, usings, logging, message models, registration, save masks, the dependency injection region | Habit. Strong where it's near universal, weak where it's split |
| Review history | Naming against the client, layering, scope, which branch | What reviewers actually push back on |
There's no .editorconfig, no CONTRIBUTING file and no CI in the repository, so nothing checks any of this for you.
When the sources disagree
- A written rule beats the code's drift. Plenty of public methods have no summary; yours still need one.
- When the code is split between an old and a new pattern, follow the new one. An injected
ILogger<T>over NLog's static logger, a manager registered for injection over aSingleton<T>. - When the split is by project, match the project you're in. The world-side projects name private fields in plain lowerCamel; the broker services and their database projects prefix them with
_. No file mixes the two. - When nothing settles it, match the file you're editing.
What the fork does on top
Same surface, newer language
Code we add follows upstream's formatting closely enough that a reader can't tell where ours starts. Inside that, we use current C# where it makes the code clearer or safer: collection expressions, property patterns and is null, a record struct in place of a repeated tuple, frozen and immutable collections for indexes built once at start-up, and Stopwatch.GetElapsedTime for timings. Upstream already uses some of these, such as collection expressions, target-typed new and switch expressions; others it hasn't picked up yet.
We don't restyle code we only touch. We also leave out anything that would put work on other contributors: nullable reference types stay off, because switching them on means thousands of new warnings and annotations that everyone else has to keep.
Checked in game
A change reaches stable only after it's been seen working in game, against a prediction from the game tables (Verifying a feature). Upstream has no tests and no CI, so this is the safety net we hold ourselves to.
Branch rules
Upstream doesn't mind rebases or force-pushes on an open pull request, since it squashes most of them anyway. We do neither once a pull request is open, and put review fixes on as new commits (Taking a change upstream).
Where the code lives
| Piece | Path |
|---|---|
| Editor style settings | Source/NexusForever.sln.DotSettings |
| Line-ending rule | .gitattributes |
| Package versions | Source/Directory.Packages.props |
| Dependency injection helpers | Source/NexusForever.Shared/ServiceCollectionExtensions.cs |
| Upstream's written rules | Contributing, code style, database style, bug submission |