Skip to content

refactor: realign to SOLID — polymorphic dispatch (S via D) - #25

Merged
ModifAmorphic merged 2 commits into
mainfrom
refactor/solid-dispatch-polymorphism
Jul 2, 2026
Merged

refactor: realign to SOLID — polymorphic dispatch (S via D)#25
ModifAmorphic merged 2 commits into
mainfrom
refactor/solid-dispatch-polymorphism

Conversation

@ModifAmorphic

Copy link
Copy Markdown
Owner

Realigns two libraries with the single-responsibility principle, leveraging the dependency-inversion principle to replace per-call if platform then X else Y dispatch with polymorphic collaborators selected once at DI composition. Behavior-preserving. This is the SOLID audit's outcome (OS-branching was the example; the smell is any class doing type/kind/platform dispatch that belongs on a polymorphic collaborator).

steam — ISteamDiscoverer split + Windows-only registry reader

  • ISteamDiscoverer + LinuxSteamDiscoverer / WindowsSteamDiscoverer; shared discovery mechanics (ResolveRoot/ReadLibraries/FindDarktide/Failed) factored into a composed SteamDiscoveryCore (composition, not inheritance).
  • SteamService.Discover() => _discoverer.Discover() — no per-call dispatch. The tuple-switch status derivation is left untouched (legitimate decision table, not dispatch).
  • Discoverer selected at DI by SteamDiscoveryOptions.Platform (not the runtime OS) — this preserves the test-injectable Platform knob so WindowsDiscoveryTests can force Windows discovery on Linux CI. (Deliberate, documented difference from IProcessLookup, which selects by runtime OS because IsGameRunning has no test-forcing requirement.)
  • SteamRegistryReader becomes honestly Windows-only: the OperatingSystem.IsWindows() guard is removed (it defeated fail-fast), the class is annotated [SupportedOSPlatform("windows")] (satisfies CA1416 at the type level), and it's registered only on Windows. On Linux a Windows-only capability is simply unavailable — resolving it fails fast instead of silently nulling. WindowsSteamDiscoverer depends on ISteamRegistryReader; LinuxSteamDiscoverer does not.

enginseer-client — IPlatformLaunchStrategy split

  • IPlatformLaunchStrategy + WindowsLaunchStrategy / LinuxLaunchStrategy. The strategy owns the spawn (via IProcessLauncher), per-platform required discovery fields, and the Z:\ translation. Windows = direct Process.Start, no translation; Linux = proton run + both STEAM_COMPAT_* env + Z:\-translated args. --log-level remains intentionally omitted.
  • EnginseerLaunchService keeps only the platform-agnostic orchestration (Discover → required-fields → PrepareModRoot → launcher-exists → strategy.Start → result mapping). The LaunchPlatform enum is folded into strategy.Name. Strategy selected at DI by runtime OS; tests inject the strategy + a fake IProcessLauncher.

Validation

  • Behavior-preserving — discovery outputs identical; launch args/env/Z:\-translation byte-identical; reviewer line-traced the logic from main into the new classes (verbatim relocation).
  • QA: PASS — all acceptance criteria; code-review: APPROVE (composition done right, DI correct, fail-fast verified, public surface unchanged).
  • dotnet build 0 warnings (CA1416 clean); dotnet test 193/193 (Steam 47 incl. forced-Windows discovery on Linux, EnginseerClient 28 incl. forced-Windows launch-arg assembly on Linux).
  • Public surface unchanged: ISteamService, DiscoveryResult, SteamDiscoveryOptions, IEnginseerLaunchService, LaunchResult, LaunchStatus.
  • Reference docs updated in-PR: steam.md + enginseer-client.md (the new splits + DI blocks).

One callout for log consumers

Relocating logic into new classes shifts the ILogger<T> source context: discovery/launch log lines now emit under LinuxSteamDiscoverer/WindowsSteamDiscoverer/SteamDiscoveryCore/WindowsLaunchStrategy/LinuxLaunchStrategy instead of SteamService/EnginseerLaunchService. Message text, levels, and semantics are unchanged; only the category moved (unavoidable when relocating logic). No test pins the category. Flagging in case anyone filters logs by category.

Out of scope (not touched, deliberately)

  • AllocationResolver type-switch on ModVersionPolicy — it's a 2-D decision table (shared-policy × profile-policy), not 1-D dispatch; the branching is its single responsibility. Left as-is.

…s-only registry reader

Realign with SOLID: SteamService.Discover dispatched on _options.Platform
(DiscoverLinux/DiscoverWindows as private methods) — one class owning two
platform strategies. Leverages DI to move the dispatch to polymorphic
collaborators selected once at composition.

- ISteamDiscoverer + LinuxSteamDiscoverer/WindowsSteamDiscoverer; the shared
  discovery mechanics (ResolveRoot/ReadLibraries/FindDarktide/Failed) move into
  a composed SteamDiscoveryCore (composition, not inheritance).
- SteamService.Discover() => _discoverer.Discover() (no per-call dispatch); the
  tuple-switch status derivation stays (legitimate decision table, not dispatch).
- Discoverer selected at DI by SteamDiscoveryOptions.Platform (not runtime OS) —
  preserves the test-injectable Platform knob so WindowsDiscoveryTests can force
  Windows discovery on Linux CI.
- SteamRegistryReader: drop the OperatingSystem.IsWindows() guard (it defeated
  fail-fast), annotate [SupportedOSPlatform("windows")], and register it ONLY on
  Windows. On Linux a Windows-only capability is simply unavailable — resolving
  it fails fast instead of silently nulling.

Behavior-preserving (discovery outputs identical). 193 tests green, 0 warnings
(CA1416 clean via the type annotation). steam.md reference updated.
…rategies

Realign with SOLID: EnginseerLaunchService dispatched _platform == Windows ?
LaunchWindows : LaunchLinux (+ platform-branched required-fields + a translate
flag) — one class owning two platform launch strategies.

- IPlatformLaunchStrategy + WindowsLaunchStrategy/LinuxLaunchStrategy. The
  strategy owns the spawn (via IProcessLauncher), per-platform required discovery
  fields, and the Z:\ path translation (Linux). Windows is direct Process.Start
  with no translation; Linux is `proton run` + both STEAM_COMPAT_* env +
  Z:\-translated args. --log-level remains intentionally omitted.
- EnginseerLaunchService keeps only the platform-agnostic orchestration (Discover
  -> required-fields -> PrepareModRoot -> launcher-exists -> strategy.Start ->
  result mapping). LaunchPlatform enum folded into strategy.Name.
- Strategy selected at DI by runtime OS; tests inject the strategy + a fake
  IProcessLauncher.

Behavior-preserving (args/env/translation byte-identical). 193 tests green.
enginseer-client.md reference updated.
@ModifAmorphic
ModifAmorphic merged commit 82dee7b into main Jul 2, 2026
2 checks passed
@ModifAmorphic
ModifAmorphic deleted the refactor/solid-dispatch-polymorphism branch July 2, 2026 20:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant