Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 30 additions & 19 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ pre-release of production code. Production is built ground-up with
testability, review, and production-readiness as first-class goals. The POC
carries forward (1) proof of feasibility and (2) validated technical
constraints that are properties of the Darktide binary (in
`docs/reference/darktide-binary.md`). It does not carry forward code.
`docs/reference/darktide/darktide-binary.md`). It does not carry forward code.
Requirements, architecture, and technology choices are made fresh.

## Repository state
Expand Down Expand Up @@ -88,10 +88,14 @@ magos-modificus/ Magos Modificus — the mod manager app (.NET 10 + Avalo
launcher/ Magos.Modificus.Launcher — stub (slim profile launcher exe;
the Steam non-steam-shortcut target)
tests/
Magos.Modificus.General.Tests/ xUnit tests for the general library
Magos.Modificus.Profiles.Tests/ xUnit tests for the profiles library (incl. staging)
Magos.Modificus.SharedMods.Tests/ xUnit tests for the shared-mod store + allocation
docs/ architecture, poc (frozen), reference
Magos.Modificus.General.Tests/ xUnit tests for the general library
Magos.Modificus.Profiles.Tests/ xUnit tests for the profiles library (incl. staging)
Magos.Modificus.SharedMods.Tests/ xUnit tests for the shared-mod store + allocation
Magos.Modificus.Integrations.Tests/ xUnit tests for the GitHub Releases client
Magos.Modificus.Steam.Tests/ xUnit tests for discovery + IsGameRunning
Magos.Modificus.EnginseerClient.Tests/ xUnit tests for the launch façade (dual-purpose:
`dotnet test` = xUnit; `dotnet run` = composition smoke harness)
docs/ architecture/ + reference/ (darktide/, community-tools/, magos-modificus/)
.github/workflows/ CI: mingw-build + msvc-build (Enginseer) + magos-build (Magos Modificus)
.gitignore ignores enginseer/target, enginseer/bin, .NET bin/obj, build artifacts, _local/
```
Expand Down Expand Up @@ -150,25 +154,28 @@ dotnet run --project magos-modificus/ui --configuration Release # bare Avalo
file/dir → defaults (first-run safe).
- **Logging** is Serilog (console + file) bridged into
`Microsoft.Extensions.Logging`; honors `Logging:Level` + `Logging:LogFile`.
- Profiles (Phase 1: profile data model + lifecycle) + SharedMods (Phase 2: the
global shared mod store + version-policy model + allocation resolution) are
implemented; Profiles now stages **shared-first** — `PrepareModRoot` builds a
`staged/` symlink projection (Share → shared store, Diverge → profile's
`diverged/` copy) + writes `mods.lst` from the staged mods (Phase 1's
per-profile `mods/` dir is replaced). The other library projects are **stubs**
(interfaces + `Add<Library>()` only). Real implementations come in later
phases. See `docs/architecture/MAGOS-MODIFICUS.md`.
- The backend libraries are all implemented: **Profiles** (Phase 1: profile data
model + lifecycle; stages **shared-first** — `PrepareModRoot` builds a `staged/`
symlink projection (Share → shared store, Diverge → profile's `diverged/` copy)
+ writes `mods.lst`), **Steam** (Phase 1: Steam + Darktide + Proton discovery
+ `IsGameRunning` — `WinProcessLookup` via process comm on Windows,
`LinuxProcessLookup` via `/proc` argv[0] under Proton), **Integrations**
(Phase 1: GitHub Releases client), **Enginseer-client** (Phase 1: the launch
façade), **SharedMods** (Phase 2: shared mod store + version-policy model +
allocation resolution). The **UI is still the bare Phase-0 window** and the
**Launcher** is a stub (Phase 5). See `docs/architecture/MAGOS-MODIFICUS.md`.
- **CI** (`magos-build.yml`) is scoped to `magos-modificus/**` + the workflow
file, matrixed on Windows + Ubuntu; gates on build + tests.

## Key docs

- `docs/architecture/` — the production architecture (component model, the
Hybrid, the seam, test strategy, build, launcher flow).
- `docs/reference/darktide-binary.md` — validated game-binary constraints.
- `docs/reference/darktide-framework-analysis.md` — the existing modding
ecosystem being replaced.
- `docs/poc/` — frozen POC handoff (historical reference).
- `docs/reference/darktide/darktide-binary.md` — validated game-binary constraints.
- `docs/reference/community-tools/darktide-framework-analysis.md` — the existing
modding ecosystem being replaced.
- `docs/reference/magos-modificus/` — per-library API reference for the Magos
Modificus backend libraries.

## Conventions

Expand Down Expand Up @@ -218,8 +225,12 @@ affects repo structure, build, architecture, or ops, update:
goes in the relevant component README, and the root must link to it.
- **Component-dir `README.md`** (e.g. `enginseer/README.md`) — for build/dev
detail under that component; ensure the root links to it.
- **`docs/architecture/`** for any architecture change; `docs/reference/` for
game/ecosystem facts.
- **`docs/architecture/`** for any architecture change.
- **`docs/reference/`** — categorized: `darktide/` (game-binary facts),
`community-tools/` (existing modding ecosystem), `magos-modificus/`
(per-library API reference). When a Magos Modificus library's public surface,
key types, or DI registration changes, update its
`docs/reference/magos-modificus/<library>.md` in the same PR.

Then ensure `make build/check/test` + clippy pass. **Outdated docs in a PR are
a review blocker** — including this file.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ Magos has two components:
production seed of this repo. See
[`enginseer/README.md`](enginseer/README.md) for build + developer details.
- **Magos Modificus** — the mod manager app (UI, staging, load order, profiles,
dependency resolution). Phase 0 scaffold in place (.NET 10 + Avalonia 12
foundation, DI + logging + config + a bare UI window); the Profiles (Phase 1)
+ SharedMods (Phase 2 — shared-first mod storage) libraries are implemented,
other library behavior is still stubbed. See
dependency resolution). Phases 0–2 complete: the backend libraries (Profiles,
Steam, Integrations, Enginseer-client, SharedMods) are implemented; the UI is
still the bare Phase-0 window (no profile/mod-management UI yet) and the
Launcher is a stub (Phase 5). See
[`magos-modificus/README.md`](magos-modificus/README.md) for developer/build
details.

Expand Down
3 changes: 1 addition & 2 deletions docs/architecture/ENGINSEER.md
Original file line number Diff line number Diff line change
Expand Up @@ -286,5 +286,4 @@ gate.
re-rooting (the loader, the loader surfaces, the `Managers.mod` shape contract,
the load timing).
- `docs/architecture/README.md` — project architecture + the Enginseer↔mod-manager contract.
- `docs/reference/darktide-binary.md` — the validated game-binary constraints.
- `docs/poc/` — frozen POC handoff (the discovery methodology + DMF bootstrap approach are validated here).
- `docs/reference/darktide/darktide-binary.md` — the validated game-binary constraints.
99 changes: 80 additions & 19 deletions docs/architecture/MAGOS-MODIFICUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ Mods, Steam), and the "Launch Darktide" button that invokes the Enginseer
launcher. Enginseer does the injection + mod loading; Magos Modificus owns the
management experience around it.

> **Status: Phase 0 scaffold in place; library implementations pending.** The
> foundation is built (.NET 10 + Avalonia 12 layout, DI composition, structured
> logging, global config schema/loader, a bare UI shell). Library
> implementations come in later phases. Enginseer (the runtime it builds on) is
> built — see `docs/architecture/ENGINSEER.md`.
> **Status: Phases 0–2 complete.** The foundation (.NET 10 + Avalonia 12 layout,
> DI composition, structured logging, global config schema/loader, a bare UI
> shell) plus the backend libraries are built: Profiles, Steam, Integrations,
> Enginseer-client (Phase 1) + SharedMods (Phase 2). The **UI is still the bare
> Phase-0 window** (no profile/mod-management UI yet) and the **Launcher** is a
> stub (Phase 5). Next: Phase 3 (UI build-out). Enginseer (the runtime it builds
> on) is built — see `docs/architecture/ENGINSEER.md`.

## In scope for this document

Expand Down Expand Up @@ -45,14 +47,16 @@ its own subfolder:
```
magos-modificus/
ui/ the Avalonia app (UI only — no direct data access)
enginseer-client/ Enginseer-client library — the launch façade
profiles/ Profiles + Settings library
integrations/ GitHub Releases + Nexus Mods client
steam/ Steam discovery + shortcuts
general/ cross-cutting infra (DI, logging, config, primitives)
launcher/ (optional) slim profile launcher — launches a profile without
the UI (entry point for Steam non-steam shortcuts)
general/ cross-cutting infra (DI, logging, config, primitives)
config/ the global config schema + defaults
profiles/ Profiles library — profile data, staging, mods.lst
shared-mods/ SharedMods library — global shared mod store + version-policy model
steam/ Steam library — Steam/Darktide/Proton discovery + IsGameRunning
integrations/ Integrations library — GitHub Releases client (Nexus = Phase 4)
enginseer-client/ Enginseer-client library — the launch façade
launcher/ (optional) slim profile launcher — launches a profile without
the UI (entry point for Steam non-steam shortcuts); Phase 5
tests/ xUnit test projects per library
```

The UI **never** touches files, directories, APIs, or any data directly —
Expand All @@ -73,6 +77,56 @@ UI models.
| **Steam** | Steam operations outside Enginseer: locate Steam (`libraryfolders.vdf`), Darktide install + compatdata, Proton version; add / remove non-steam shortcuts; detect whether the game is running. Owns the Linux discovery + escape hatch (see [Launch](#launch)). |
| **General** | Cross-cutting infra: DI composition, structured logging, configuration, shared primitives. |

## Composition & startup

The composition root is `ui/MagosComposition.cs` — a static `Build()` that
constructs and returns the application `IServiceProvider`. The UI **never**
touches files, directories, or APIs directly; every data operation flows
through a registered library interface. The UI registers only its own surface
(main window + view model) — no data access.

`MagosComposition.Build()` runs this sequence, in order:

1. **Load config** — `new ConfigLoader().Load()` produces a fully-defaulted
`MagosConfig` (defaults + JSON overrides; first-run safe). Logging needs this
first.
2. **Build the logger** — `LoggingBootstrap.CreateLoggerFactory(config)`
(Serilog console + file, level-honored, truncated on startup). Both config
and the logger are constructed **outside** DI because DI itself needs them.
3. **Compose services** — `new ServiceCollection()`, then the `Add<Library>()`
extensions in their real order:
- `AddGeneral(config, loggerFactory)` — registers the config singleton, the
logger factory, `AddLogging()`, and the config loader.
- `AddSharedMods()` — the global shared store (called explicitly here and
idempotently again inside `AddProfiles()`, so the store is discoverable at
the root and `IProfileService` always resolves its staging dependency).
- `AddProfiles()` — profile service + the `SymlinkCreator` staging seam.
- `AddIntegrations()` — the typed GitHub HTTP client.
- `AddSteam()` — Steam discovery + the platform process-lookup seam.
- `AddEnginseerClient()` — the launch façade + the process-launcher seam.
- `AddLauncher()` — the slim profile launcher stub (Phase 5).
- `AddTransient<MainWindow>()` + `AddSingleton<MainViewModel>()` — the UI
surface.
4. **Build** — `BuildServiceProvider()`.

**The DI contract:** each library exposes one `Add<Library>()` extension and
accepts only interfaces or primitives (never concrete UI models). Supporting
services and injectable seams are registered with `TryAdd` — `SteamDiscoveryOptions`,
`ISteamRegistryReader`, `IProcessLookup` (Steam), `SymlinkCreator` (Profiles),
`IProcessLauncher` (Enginseer-client), `ISharedModStore` (SharedMods) — so tests
and hosts can pre-register overrides (e.g. the Steam fixture's fakes, or a
throwing `SymlinkCreator` to exercise the failure path) and have them survive the
`Add<Library>()` chain. `TryAdd` is specifically load-bearing for `AddProfiles()`,
which calls `AddSharedMods()` unconditionally: a plain `AddSingleton` there would
clobber a pre-registered mock.

**Per-profile vs global:** global, system-level settings live in `MagosConfig`
(one config file under the OS local-app-data dir); per-profile settings (mods,
load order, per-mod policies) live with the profile, not in the global config.

Per-library public surfaces — interfaces, key types, exact DI registrations —
are documented under [Reference — Magos Modificus](../reference/magos-modificus/).

## The Enginseer contract Magos consumes

Stable surface (Enginseer is built; this is the boundary Magos builds against):
Expand Down Expand Up @@ -108,7 +162,9 @@ logging, the hook-ready handshake).
Magos writes `mods.lst` into it on each launch.
- **DMF on profile creation:** the new-profile flow offers "add latest DMF?"
(default yes). If accepted, DMF is added to the profile's mod list like any
mod (fetched from GitHub Releases). DMF is a normal mod with exactly two
mod (sourced per the open DMF-sourcing decision — see
[Mod sources / integrations](#mod-sources--integrations); it is **not**
settled as GitHub Releases). DMF is a normal mod with exactly two
exceptions: (1) the creation-time prompt; (2) DMF is never auto-placed by an
Enginseer-side rule — Magos writes it first in `mods.lst` because dependency
resolution puts it there. Beyond those, DMF is fully user-controllable (a
Expand Down Expand Up @@ -162,11 +218,15 @@ again, the local copy is dropped back to a shared reference.
- **GitHub Releases** — a source for mods that publish there; no auth required
for public releases (version checks + downloads).
- **Local** — manually-installed mods (the user supplies the files).
- **DMF specifically** is fetched from **GitHub Releases** at the new-profile
prompt. Most mods depend on DMF, so this is the common case; sourcing it from
GitHub means the user needn't configure a Nexus API key just to get it. DMF
isn't mandatory — a mod can be written without it — so the prompt is an offer,
not a requirement. (See [Profiles](#profiles).)
- **DMF specifically** — the new-profile prompt offers to add it (most mods
depend on it, so this is the common case; DMF isn't mandatory, so the prompt
is an offer, not a requirement). **DMF sourcing is an OPEN decision
(Phase 4):** the original plan (fetch from GitHub Releases, keyless) is broken
— DMF's GitHub repo has no releases/tags; its canonical releases are on
NexusMods. The lean is to require a Nexus API key be configured, or have the
user download DMF manually. Bundling DMF with Magos is rejected
(modding-community norms + Nexus rules). Resolution deferred to Phase 4. (See
[Profiles](#profiles).)
- Per-mod: auto-update override (overrides the global setting); version pinning.
- **Import / Export** — profile import / export.

Expand Down Expand Up @@ -281,7 +341,8 @@ Per-profile settings live with the profile, not in the global config.
- Mod list: enable / disable / remove, update indicators, version pinning,
per-mod auto-update override, auto-sort + manual sequential reorder.
- Shared mod storage (shared-first allocation by version policy).
- Mod sources: Nexus Mods (primary) + GitHub Releases + local; DMF via GitHub.
- Mod sources: Nexus Mods (primary) + GitHub Releases + local; DMF via the
open sourcing decision (Phase 4 — see Mod sources).
- Launch Darktide (Windows trivial; Linux native + Proton-at-launch +
discovery + escape hatch).
- Steam non-steam shortcuts.
Expand Down
2 changes: 1 addition & 1 deletion docs/architecture/MOD_LOADER-DMF.md
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,6 @@ unchanged.
- `enginseer/mod_loader/{file,hook,class_patch,require_wrap}.lua` — the loader API.
- `docs/architecture/ENGINSEER.md` — the engine-context mechanism + the deferred
bootstrap bridge + the launcher/shell contracts.
- `docs/reference/darktide-framework-analysis.md` — how the existing community
- `docs/reference/community-tools/darktide-framework-analysis.md` — how the existing community
toolchain (`patch_999` → `mod_loader` → DMF → mods) works; the loader
replicates the community `mod_loader` role.
25 changes: 13 additions & 12 deletions docs/architecture/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ way for vanilla play (launch from Steam = the unmodified game).
one DLL, delivered by `CreateRemoteThread`; the C shell stages the
**mod loader** — the runtime-controlled Lua loader that loads DMF + user mods.
See `docs/architecture/ENGINSEER.md` for the full subcomponent breakdown.
- **Magos Modificus — `magos-modificus/`** (planned, not built): the user-facing
- **Magos Modificus — `magos-modificus/`** (Phases 0–2 built): the user-facing
app — staging-directory management, load order, profiles, dependency
resolution, the "Launch Modded" button. See
resolution, the "Launch Modded" button. The backend libraries are implemented;
the UI is still the bare Phase-0 window (Phase 3). See
[`MAGOS-MODIFICUS.md`](MAGOS-MODIFICUS.md) for the target architecture.
- **DMF + user mods** (Lua, not our code): the Darktide-Mod-Framework Lua
files, preserved as-is; only the harness is replaced. Loaded by the mod loader
Expand Down Expand Up @@ -78,11 +79,10 @@ addresses), via two methods + `.pdata` gap handling:
- `.pdata` gaps: CFG thunks (`E9 rel32`), leaf functions, import thunks
(`FF 25`).

The engine is **build-agnostic** — validated when the installed binary turned
out to be a newer build than the POC's pinned one: all 16 were found at
uniformly-shifted RVAs (+0xf0680 cluster). The matchers are the ongoing
maintenance surface (re-tune on a LuaJIT version change — rare; LuaJIT is
static Stingray code).
The engine is **build-agnostic** — all 16 functions are found at
uniformly-shifted RVAs across binary versions (validated across builds; e.g. a
+0xf0680 cluster shift). The matchers are the ongoing maintenance surface
(re-tune on a LuaJIT version change — rare; LuaJIT is static Stingray code).

## Test strategy

Expand Down Expand Up @@ -128,8 +128,9 @@ Windows (CI). Both gate on `cargo clippy --all-targets --features test-hooks --
consumes, profiles, the Windows/Linux launch paths, v1 scope.
- `docs/architecture/MOD_LOADER-DMF.md` — the mod_loader↔DMF integration: the
loader, the IO re-rooting, the load timing, the two-path split.
- `docs/reference/darktide-binary.md` — the validated game-binary constraints
(addresses, struct offsets, sandboxed `_G`, discovery methodology).
- `docs/reference/darktide-framework-analysis.md` — the existing modding
ecosystem being replaced.
- `docs/poc/` — the frozen POC handoff (detailed historical reference).
- `docs/reference/darktide/darktide-binary.md` — the validated game-binary
constraints (addresses, struct offsets, sandboxed `_G`, discovery methodology).
- `docs/reference/community-tools/darktide-framework-analysis.md` — the existing
modding ecosystem being replaced.
- `docs/reference/magos-modificus/` — per-library API reference for the Magos
Modificus backend libraries.
Loading
Loading