15 KiB
5. Proposed system architecture
5.0 Repository and implementation policy
- Rust is the confirmed implementation language for project-owned backend, CLI, and provider-adapter software.
- Development begins in one Git repository organized as a Rust workspace and clearly separated system directories.
- The backend, system integration, distribution tooling, and each provider adapter are distinct internal components with explicit boundaries, even while they share one repository.
- Provider adapters are separate crates so they can be tested independently and later extracted for reuse when their contracts and release cadence are stable.
- Do not split components into separate Git repositories during early development. Avoid submodules and multi-repository coordination while interfaces are still changing.
- A component may later move to its own repository with history preserved when it has a stable public contract, independent tests, clear external value, and a genuinely independent release lifecycle.
- The repository and task documentation must allow one Codex CLI session started at the repository root to work across the complete project without manual context handoffs.
- Bootstrap the repository before feature work with a pinned Rust toolchain, committed dependency lockfile, formatting and lint policy, deny-by-default local quality gates, license/security auditing, fixture conventions, changelog/version policy, and an architecture-decision-record directory. The first vertical slice must not invent these piecemeal.
5.1 Layer model
| Interface | kiln CLI, Pegasus adapter, future TUI/desktop/controller frontends |
|---|---|
| Unified core | Normalized library, identity, capabilities, profiles, optional index, launch plans |
| Provider adapters | Steam, Lutris, Epic, GOG, Amazon, native, and custom manifests |
| Execution services | Session supervisor, Wine/Proton/UMU runtimes, profile engine, lifecycle tracking, logging, diagnostics |
| Platform | Arch, systemd, graphics drivers, PipeWire, BlueZ, input/udev, filesystems |
5.2 Core components
The component and executable names below form the temporary Kiln development family, not approved install names. They may be used only in prose, tests, and repository-local development scaffolding. They must not become global executables, published packages/crates, configuration directories, D-Bus names, socket names, or stable compatibility surfaces. The final distribution name and its related technical namespace will be selected together, collision-checked, and applied through the documented rename procedure before the first public or installable artifact.
kiln-core: Proposed reusable core/library that normalizes games and provider capabilities.
kiln: The single human-facing, administrative, and machine-readable CLI. Subcommands provide fuzzy search and safe defaults as well as JSON output, automation, and diagnostics; no separate convenience executable is required.
Name resolution never guesses through ambiguity. An exact stable ID wins; a unique normalized name or alias may launch directly; multiple matches produce an interactive numbered/TUI choice only on an attached terminal and a structured ambiguous-game error otherwise. The error includes stable IDs, provider/store, and installation labels so scripts can retry deterministically.
Provider adapters: Separate Rust crates that discover and launch games through each provider. They remain in the shared repository initially but must not leak provider-specific parsing or policy into the core.
Session shell/supervisor: Starts and monitors the persistent minimal compositor, fullscreen terminal, selected initial frontend, and configured resident applications.
Profile engine: Combines global, hardware, provider, and per-game settings into a launch plan.
Runtime backends: Execute native, Wine, Proton, or UMU launch plans. UMU does not own or discover a game library; a game retains its store or custom-manifest identity while selecting UMU as its runtime when appropriate.
Lifecycle tracker: Observes game start and exit using the strongest method available for each provider: an owned process or systemd scope, provider-reported state, or conservative process correlation. It reports tracked, provider-reported, probable, or unknown rather than pretending every launch is equally observable.
Input subsystem: Controller discovery, pairing helpers, permissions, testing, and health checks.
Diagnostics: Collects provider, runtime, graphics, audio, controller, and session evidence.
Frontend adapters: Export or translate the normalized catalogue into formats accepted by third-party frontends while routing launches back through kiln.
5.3 Process model
The normalized backend begins as a reusable Rust library consumed directly by a one-shot CLI. Initial discovery is on demand and uses no persistent database. A disposable cache or local SQLite database may be introduced only when measured scan latency or a proven live-state requirement justifies it; provider files remain authoritative and kiln scan --no-cache must remain available. The graphical session itself requires a small user-level supervisor, but this must not force provider discovery and launch logic into a permanently resident monolithic daemon. A later local service may expose D-Bus or a Unix socket when live frontend state, events, background refresh, or download status justify it.
The one-shot CLI, future local service, and graphical clients must share one core implementation and versioned data contracts. The CLI must not become a parallel backend. If a resident service is introduced, the preferred first transport is a local Unix-domain socket with structured, versioned messages; a network HTTP API is deferred until a real remote-control use case exists.
Performance constraint: Lean starts heavyweight provider GUIs lazily. Ready may keep user-selected launchers resident for instant access. The project must measure idle CPU, GPU, memory, I/O, and game frametimes rather than treating process count or allocated RAM as performance proof.
6. Unified launcher backend specification
6.1 Identity model
Every game must have a stable store/source-qualified identifier. Display names are searchable labels, not database keys. Game identity, installation instance, configuration owner, execution backend, and compatibility runtime are separate concepts.
steam:1716740
lutris:star-citizen
epic:Salt
gog:1207658693
amazon:amzn1.adg.product.example
native:openmw
custom:star-citizen
Each installed copy receives a persistent installation UUID. An adapter-native stable installation identifier may seed it; otherwise a small schema-versioned installation registry in project-owned XDG state records the UUID, provider fingerprint, current location, and prior aliases. This registry is durable identity state, not a discovery cache or provider database. Writes are atomic and locked; rebuilding it must not change IDs for installations that can still be matched unambiguously. Store identity is independent of the management client: a game managed by Heroic is still epic:, gog:, or amazon:, while configuration_owner may be heroic. Adapters preserve aliases for changed local identifiers where possible and report duplicate, moved, and temporarily unavailable installations instead of silently merging them. Steam App IDs may be canonical directly; Lutris numeric database IDs are local implementation details, while Lutris slugs, desktop entries, and custom manifests require adapter-specific stability and migration rules.
In machine-readable contracts, store is the canonical identity namespace, adapter identifies the discovery/launch implementation, configuration_owner identifies who owns mutable settings, and execution_backend identifies the invoked client/runtime path. Human-facing text may use "provider" generically, but schemas must not overload that word across these roles.
6.2 Initial commands
kiln list [--json]
kiln search <query> [--json]
kiln info <id-or-name> [--json]
kiln scan [--provider <name>] [--no-cache]
kiln launch <id-or-name> [--dry-run] [--safe] [--x11]
kiln launch --last | --favorites | --list | --logs
kiln providers
kiln provider open <name> [--mode desktop|controller]
kiln provider start <name> [--hidden]
kiln provider show <name>
kiln session profile lean|ready|frontend|custom
kiln session shell terminal|steam|custom
kiln session status
kiln doctor
Frontend-adapter commands, including Pegasus configure/sync/start/autostart operations, are post-v1 commands added with phase 12. Pre-v1 builds must return a structured unsupported-capability error if asked for an unavailable frontend rather than exposing inert command stubs.
6.3 Provider capability contract
| Capability | Version 1 | Meaning |
|---|---|---|
| discover | Required | Enumerate installed games and core metadata. |
| launch | Required | Produce or execute a normalized launch plan. |
| info | Required | Return provider ID, install state, path, and runtime information. |
| manage | Recommended | Open the provider GUI in a maintenance session. |
| ui-optional | Informational | Games can launch without the provider's graphical interface. |
| client-required | Informational | A provider client must run for normal launch or platform services. |
| lifecycle-state | Informational | Adapter can report game-running state; confidence and method are returned explicitly. |
| client-exit | Optional | Adapter can safely request provider-client exit when no managed work is active; never assumed or automatic. |
| install | Deferred | Install a title through the provider. |
| update | Deferred | Update a title or report update state. |
| verify | Deferred | Request provider integrity verification. |
| authenticate | Deferred | Perform or initiate provider login. |
| uninstall | Deferred | Remove a title through the provider. |
6.4 Normalized game record
schema_version: 1
id: steam:1716740
name: Starfield
store: steam
adapter: steam
installation_id: 7aa62d69-8f8e-4cab-8514-275ce1f87748
configuration_owner: steam
execution_backend: steam
installed: true
install_path: /games/steam/steamapps/common/Starfield
compatibility:
type: proton
version: GE-Proton10-8
capabilities: [discover, launch, client-required]
client_required: true
runtime: steam-proton
The YAML above is illustrative; JSON is the required v1 machine-output encoding. Machine-readable records, launch plans, capabilities, and errors are schema-versioned from phase 1. Breaking changes are allowed before the public 1.0 contract, but consumers must reject unsupported versions clearly rather than guessing.
6.5 Launch plan
The backend should resolve and display a launch plan before execution. A dry run must expose provider, installation/configuration owner, runtime, session backend, resolution, refresh rate, controller state, environment, wrappers, lifecycle-tracking strategy, and final command without starting the game.
Provider launch commands may hand execution to an already-running client and then exit. The backend must not equate the invoking process with the game. Lifecycle tracking exists to time sleep inhibition, optional background-work suppression, logging, state reporting, and restoration of temporary settings. GameMode uses a real client/wrapper request bound to game execution and must not be toggled solely from heuristic lifecycle confidence. Failure to obtain exact lifecycle state must degrade to an explicit lower-confidence state and safe cleanup behavior. Automatic shutdown of Steam, Heroic, Lutris, or another provider client after play is not required.
6.6 External client contract
The backend should expose a documented, versioned contract that is useful without Pegasus or any first-party GUI. Initial machine-readable operations should map to the CLI's JSON output; a later local service may expose equivalent methods such as:
games.list
games.get
games.launch
providers.list
providers.open
sessions.status
events.subscribe
Compatibility policy, JSON schemas, stable provider-qualified IDs, structured errors, authentication/authorization rules for the local transport, and at least one small example client are required before describing this interface as a public API. Operations such as force-stop are absent unless separately designed and capability-gated. Frontends must not parse Steam manifests, Heroic configuration, Lutris data, Wine prefixes, or emulator collections when the corresponding adapter already owns that work.
6.7 Configuration ownership and precedence
Launch configuration resolves from lowest to highest priority: built-in safe defaults, hardware profile, global user profile, provider/runtime defaults, per-game profile, and explicit one-launch CLI overrides. Dry runs retain the source of every resolved value and whether it replaced, merged with, appended to, or removed an earlier value.
User-editable project configuration is versioned TOML. The CLI can generate fully commented defaults, validate without mutation, explain resolved values, and perform common edits without requiring manual file editing. JSON is reserved for machine-readable records, schemas, errors, and IPC contracts rather than being a second user-configuration syntax.
Environment deletion, wrapper ordering, list merging, invalid values, schema migration, and concurrent external edits require explicit behavior and contract tests. Provider-owned credentials, prefixes, and settings remain provider-owned unless the user explicitly imports them; read-only discovery must not silently write to Heroic, Lutris, or Steam configuration.
6.8 Trust boundaries and filesystem safety
- Use XDG base directories for project configuration, state, cache, runtime sockets, and logs; document every exception.
- Never bake the initial account name, UID, home path, or a system-wide mutable game catalogue into schemas or package defaults. V1 runs one gaming user at a time, but state and credentials remain per-user so a later multi-user session selector can isolate libraries, saves, prefixes, runtime choices, and authentication.
- Open provider and custom-manifest files defensively: bound file sizes and recursion, reject invalid encodings where required, detect symlink/path traversal, and never follow a discovered path into privileged or unrelated user data implicitly.
- Construct child processes with typed executable/argument arrays and a minimal documented environment. Shell evaluation is forbidden in provider metadata, custom manifests, artwork paths, and frontend descriptors.
- Redact credentials, authorization headers, tokens, machine identifiers, and personally identifying paths from default diagnostics. A user-requested support bundle must preview its contents before export.
- Mutating operations use atomic writes, restrictive permissions, locking or compare-and-swap behavior, and recoverable backups where the project owns the file. Read-only adapters never repair provider state silently.