@@ -88,8 +88,12 @@ every OS-specific input + platform seam is injected:
8888 Notable fields: ` LinuxDefaultSteamRoot ` , ` LinuxFlatpakSteamRoot ` ,
8989 ` LinuxCompatibilityToolsDir ` , ` LinuxSystemCompatibilityToolsDirs ` (the two
9090 standard Steam system directories, defaulting so ` /usr/share/steam/compatibilitytools.d `
91- is searched), ` WindowsDefaultSteamRoot ` , ` DarktideAppId ` (` 1361210 ` ),
92- ` DarktideCommonDir ` , ` GameBinaryName ` , ` GameProcessName ` .
91+ is searched), ` IsSteamDeck ` (a platform identity input: whether the host is a
92+ Steam Deck; ` CreateDefault() ` detects it from OS release metadata --
93+ ` ID=steamos ` + ` VARIANT_ID=steamdeck ` , reading ` /run/host/os-release ` before
94+ ` /etc/os-release ` ; tests can inject a fixed value), ` WindowsDefaultSteamRoot ` ,
95+ ` DarktideAppId ` (` 1361210 ` ), ` DarktideCommonDir ` , ` GameBinaryName ` ,
96+ ` GameProcessName ` .
9397- ` ISteamDiscoverer ` (internal) -- ` Discover() → DiscoveryResult ` . The
9498 platform-specific discovery strategy. Two implementations
9599 (` LinuxSteamDiscoverer ` , ` WindowsSteamDiscoverer ` ), selected once at DI time
@@ -104,15 +108,24 @@ every OS-specific input + platform seam is injected:
104108 composition, not inheritance -- each discoverer injects the core and layers its
105109 own platform steps on top.
106110- ` ProtonResolver ` (internal) -- the Linux Proton compatibility-tool resolver,
107- constructed by ` LinuxSteamDiscoverer ` . Reads Steam's ` CompatToolMapping ` from
108- ` config.vdf ` , then resolves the selected tool name to a ` proton ` binary as a
109- custom tool (a ` compatibilitytool.vdf ` manifest) or a Valve-managed tool
110- (appinfo + appmanifest). See [ Linux Proton resolution] ( #linux-proton-resolution ) .
111+ constructed by ` LinuxSteamDiscoverer ` . Reads Steam's tool selection (the
112+ app-specific ` CompatToolMapping ` entry, then the global ` "0" ` entry, then the
113+ appinfo recommended runtime when both mappings are absent), then resolves the
114+ selected name to a ` proton ` binary as a custom tool (a
115+ ` compatibilitytool.vdf ` manifest) or a Valve-managed tool (appinfo +
116+ appmanifest). See [ Linux Proton resolution] ( #linux-proton-resolution ) .
111117- ` SteamAppInfoReader ` (internal) -- parses Steam's binary ` appinfo.vdf `
112- container (versions 39-41) and extracts the first app entry carrying a
113- ` compat_tools ` collection. Used by ` ProtonResolver ` for Valve-managed tool
114- resolution. Has an internal ` ReadCompatTools(Stream) ` overload so tests feed a
118+ container (versions 39-41) in one scan that collects the first app entry
119+ carrying a ` compat_tools ` collection and the requested app's recommended
120+ runtime (either may be absent; a requested app id of 0 skips the runtime
121+ lookup so the scan stops at the first registry). Used by ` ProtonResolver ` for
122+ Valve-managed tool resolution + the no-user-mapping runtime fallback. Has an
123+ internal ` ReadSnapshot(Stream, requestedAppId) ` overload so tests feed a
115124 synthetic binary fixture.
125+ - ` SteamDeckDetector ` (internal static) -- the production Steam Deck detection
126+ behind the ` SteamDiscoveryOptions.IsSteamDeck ` platform identity input (see
127+ above). Quoted values are tolerated; IO/access failures degrade to "not a
128+ Deck".
116129- ` SteamTextVdf ` (internal static) -- the single entry point for Steam text KV1
117130 parsing (` config.vdf ` , ` compatibilitytool.vdf ` , ` appmanifest_*.acf ` ). Wraps
118131 ValveKeyValue with ` HasEscapeSequences = true ` always on (Steam files
@@ -222,32 +235,52 @@ flipping the mode.
222235
223236### Linux Proton resolution
224237
225- ` ProtonResolver ` reads the tool Steam actually selected for Darktide, rather
238+ ` ProtonResolver ` resolves the tool Steam actually selected for Darktide, rather
226239than guessing from directory names. The steps are best-effort: a missing or
227240unreadable file degrades to an unresolved Proton (warning), never a throw.
228241
2292421 . ** Selected tool name** -- ` <steamRoot>/config/config.vdf ` →
230- ` Software > Valve > Steam > CompatToolMapping ` . The app-specific mapping for
231- Darktide's app id (` 1361210 ` ) is authoritative when present; its ` name ` is
232- used as-is, and an empty or malformed name fails resolution without falling
233- through. The global ` "0" ` mapping is consulted only when the app-specific
234- mapping is absent. No mapping at all → unresolved.
243+ ` Software > Valve > Steam > CompatToolMapping ` , with this precedence:
244+ - The app-specific mapping for Darktide's app id (` 1361210 ` ) is
245+ authoritative when present; its ` name ` is used as-is.
246+ - The global ` "0" ` mapping is considered only when the app-specific mapping
247+ is absent, and is authoritative when present.
248+ - Only when both are absent, Darktide's appinfo
249+ ` common/steam_deck_compatibility/configuration/recommended_runtime ` is
250+ Steam's non-user default and supplies the name on any Linux host; host
251+ identity (Steam Deck or not) is not consulted for this decision.
252+ - A present mapping whose ` name ` is missing, non-string, empty, or
253+ whitespace is ** invalid** : resolution fails without falling through (this
254+ covers both the app-specific and the global entry, and an invalid
255+ app-specific entry blocks the global entry too). Likewise, a selected
256+ mapping whose named tool cannot be resolved stays authoritative and never
257+ falls through to the recommendation, and a ` config.vdf ` that exists but
258+ cannot be read or parsed fails unresolved rather than bypassing a
259+ possible user choice. A missing config file, or a valid config with
260+ neither key, counts as no user mapping and permits the recommended-runtime
261+ fallback.
262+ - A missing, empty, whitespace, ` native ` , or unresolvable recommendation
263+ yields unresolved with a warning; no other runtime is guessed.
2352642 . ** Custom tool** -- a ` compatibilitytool.vdf ` manifest whose ` compat_tools `
236- collection defines the selected name, searched across every
237- compatibility-tool root in order: the resolved Steam root's
238- ` compatibilitytools.d ` , the configured user root, then the system roots
239- (including ` /usr/share/steam/compatibilitytools.d ` ). Each root is checked
240- root-level first (Valve permits a manifest directly at the root with a
241- relative or absolute ` install_path ` ), then its per-tool subdirectories. The
242- resolved ` install_path ` 's ` proton ` file must exist.
265+ collection defines the selected name, searched across every
266+ compatibility-tool root in order: the resolved Steam root's
267+ ` compatibilitytools.d ` , the configured user root, then the system roots
268+ (including ` /usr/share/steam/compatibilitytools.d ` ). Each root is checked
269+ root-level first (Valve permits a manifest directly at the root with a
270+ relative or absolute ` install_path ` ), then its per-tool subdirectories. The
271+ resolved ` install_path ` 's ` proton ` file must exist. This runs first for any
272+ selected name, including the recommended runtime.
2432733 . ** Valve-managed tool** -- the ` compat_tools ` entry in
244- ` <steamRoot>/appcache/appinfo.vdf ` (binary; parsed by
245- ` SteamAppInfoReader ` ) whose key or comma-separated alias matches the selected
246- name, then ` appmanifest_<appid>.acf ` across the libraries, parsing its
247- ` installdir ` , requiring
248- ` <library>/steamapps/common/<installdir>/proton ` to exist.
274+ ` <steamRoot>/appcache/appinfo.vdf ` (binary; parsed by
275+ ` SteamAppInfoReader ` ) whose key or comma-separated alias matches the selected
276+ name, then ` appmanifest_<appid>.acf ` across the libraries, parsing its
277+ ` installdir ` , requiring
278+ ` <library>/steamapps/common/<installdir>/proton ` to exist. One ` Resolve `
279+ call parses ` appinfo.vdf ` at most once: when the recommended runtime
280+ supplies the name, the snapshot already read for the recommendation is
281+ reused here.
2492824 . Nothing resolves → ` null ` (escape hatch; UI prompts). A reason is appended to
250- ` Warnings ` .
283+ ` Warnings ` .
251284
252285` ProtonVersion ` carries the tool's ` display_name ` when present (the custom
253286manifest's, or the appinfo entry's), otherwise the internal tool name.
@@ -399,10 +432,15 @@ no-op).
399432` Modificus.Curator.Steam.Tests ` covers Linux discovery (` LinuxDiscoveryTests ` ,
400433` FlatpakDiscoveryTests ` ), Windows discovery (` WindowsDiscoveryTests ` ), Proton
401434compatibility-tool selection (` ProtonSelectionTests ` , ` ProtonResolverTests ` --
402- app-specific vs global mapping, custom vs Valve-managed tool resolution, root-
403- level vs subdirectory custom manifests, system roots), the binary
404- ` appinfo.vdf ` reader (` SteamAppInfoReaderTests ` , against a compact synthetic v41
405- fixture with a string table), the ` SteamTextVdf ` escape-semantics helper
435+ app-specific vs global mapping vs the appinfo recommended-runtime fallback
436+ (identical regardless of Deck identity) and every blocking rule around it,
437+ custom vs Valve-managed tool resolution, root-level vs subdirectory custom
438+ manifests, system roots), the binary
439+ ` appinfo.vdf ` reader (` SteamAppInfoReaderTests ` , against compact synthetic v41
440+ fixtures plus a realistic multi-entry fixture matching the live appinfo shape --
441+ Darktide's ` recommended_runtime ` + the Steam Play manifest's ` compat_tools ` in
442+ both entry orders), the Steam Deck OS-release detector (` SteamDeckDetectorTests ` ), the
443+ ` SteamTextVdf ` escape-semantics helper
406444(` SteamTextVdfTests ` , including a sanitized realistic ` config.vdf ` fixture with
407445an escaped JSON scalar), the ` libraryfolders.vdf ` parser
408446(` LibraryFoldersVdfTests ` ), game-running detection (` GameRunningTests ` ,
0 commit comments