Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Platform Portability

The engine must not create obstacles for any platform. Desktop is the primary dev target, but every architectural choice must be portable to browser (WASM), mobile (Android/iOS), and consoles without rework.

Player Data Directory (D061)

All player data lives under a single, self-contained directory. The structure is stable and documented — a manual copy of this directory is a valid (if crude) backup. The ic backup CLI provides a safer alternative using SQLite VACUUM INTO for consistent database copies. See decisions/09e/D061-data-backup.md for full rationale, backup categories, and cloud sync design.

<data_dir>/
├── config.toml              # Settings (D033 toggles, keybinds, render quality)
├── profile.db               # Identity, friends, blocks, privacy (D053)
├── achievements.db          # Achievement collection (D036)
├── gameplay.db              # Event log, replay catalog, save index, map catalog (D034)
├── telemetry.db             # Unified telemetry events (D031) — pruned at 100 MB
├── keys/
│   └── identity.key         # Ed25519 private key (D052) — recoverable via mnemonic seed phrase (D061)
├── communities/             # Per-community credential stores (D052)
│   ├── official-ic.db
│   └── clan-wolfpack.db
├── saves/                   # Save game files (.icsave)
├── replays/                 # Replay files (.icrep)
├── screenshots/             # PNG with IC metadata in tEXt chunks
├── workshop/                # Downloaded Workshop content (D030)
├── mods/                    # Locally installed mods
├── maps/                    # Locally installed maps
├── logs/                    # Engine log files (rotated)
└── backups/                 # Created by `ic backup create`

Platform-specific <data_dir> resolution:

PlatformDefault Location
Windows%APPDATA%\IronCurtain\
macOS~/Library/Application Support/IronCurtain/
Linux$XDG_DATA_HOME/iron-curtain/ (default: ~/.local/share/iron-curtain/)
Browser (WASM)OPFS virtual filesystem (see 05-FORMATS.md § Browser Storage)
MobileApp sandbox (platform-managed)
Portable mode<exe_dir>/data/ (activated by IC_PORTABLE=1, --portable, or portable.marker next to exe)

Override with IC_DATA_DIR environment variable or --data-dir CLI flag. All path resolution is centralized in ic-paths (see § Crate Design Notes). All asset loading goes through Bevy’s asset system (rule 5 below) — the data directory is for player-generated content, not game assets.

Data & Backup UI (D061)

The in-game Settings → Data & Backup panel exposes backup, restore, cloud sync, and profile export — the GUI equivalent of the ic backup CLI. A Data Health summary shows identity key status, sync recency, backup age, and data folder size. Critical data is automatically protected by rotating daily snapshots (auto-critical-N.zip, 3-day retention) and optional platform cloud sync (Steam Cloud / GOG Galaxy).

First-launch flow integrates with D032’s experience profile selection:

  1. New player: identity created automatically → 24-word recovery phrase displayed → cloud sync offer → backup reminder prompt
  2. Returning player on new machine: cloud data detected → restore offer showing identity, rating, match count; or mnemonic seed recovery (enter 24 words); or manual restore from backup ZIP / data folder copy

Post-milestone toasts (same system as D030’s Workshop cleanup prompts) nudge players without cloud sync to back up after ranked matches, campaign completion, or tier promotions. See decisions/09e/D061-data-backup.md “Player Experience” for full UX mockups and scenario walkthroughs.

Portability Design Rules

  1. Input is abstracted behind a trait. InputSource produces PlayerOrders — it knows nothing about mice, keyboards, touchscreens, or gamepads. The game loop consumes orders, not raw input events. Each platform provides its own InputSource implementation.

  2. UI layout is responsive. No hardcoded pixel positions. The sidebar, minimap, and build queue use constraint-based layout that adapts to screen size and aspect ratio. Mobile/tablet may use a completely different layout (bottom bar instead of sidebar). ic-ui provides layout profiles, not a single fixed layout.

  3. Click-to-world is abstracted behind a trait. Isometric screen→world (desktop), touch→world (mobile), and raycast→world (3D mod) all implement the same ScreenToWorld trait, producing a WorldPos. Grid-based game modules convert to CellPos as needed. No isometric math or grid assumption hardcoded in the game loop.

  4. Render quality is configurable per device. FPS cap, particle density, post-FX toggles, resolution scaling, shadow quality — all runtime-configurable. Mobile caps at 30fps; desktop targets 60-240fps. The renderer reads a RenderSettings resource, not compile-time constants. Four render quality tiers (Baseline → Standard → Enhanced → Ultra) are auto-detected from wgpu::Adapter capabilities at startup. Tier 0 (Baseline) targets GL 3.3 / WebGL2 hardware — no compute shaders, no post-FX, CPU particle fallback, palette tinting for weather. Advanced Bevy rendering features (3D render modes, heavy post-FX, dynamic lighting) are optional layers, not baseline requirements; the classic 2D game must remain fully playable on no-dedicated-GPU systems that meet the downlevel hardware floor. See 10-PERFORMANCE.md § “GPU & Hardware Compatibility” for tier definitions and hardware floor analysis.

  5. No raw filesystem I/O. All asset loading goes through Bevy’s asset system, never std::fs directly. Mobile and browser have sandboxed filesystems; WASM targets use browser storage APIs (OPFS primary, IndexedDB fallback, localStorage for settings only — see 05-FORMATS.md § Browser Asset Storage). Save games use platform-appropriate storage (OPFS/IndexedDB on web, app sandbox on mobile).

  6. App lifecycle is handled. Mobile and consoles require suspend/resume/save-on-background. The snapshottable sim makes this trivial for single-player and local scenariossnapshot() on suspend, restore() on resume. For live multiplayer, the local snapshot preserves state for crash recovery, but full recovery follows the reconnection protocol (relay-coordinated donor snapshot, verification, catch-up — see desync-recovery.md § Reconnection). The NetworkModel handles reconnection; the engine’s lifecycle hook handles the local snapshot. These are complementary, not interchangeable.

  7. Audio backend is abstracted. Bevy handles this, but no code should assume a specific audio API. Platform-specific audio routing (e.g., phone speaker vs headphones, console audio mixing policies) is Bevy’s concern.

Platform Target Matrix

PlatformGraphics APIInput ModelKey ChallengePhase
Windows / macOS / LinuxVulkan / Metal / DX12Mouse + keyboardPrimary target1
Steam DeckVulkan (native Linux)Gamepad + touchpadGamepad UI controls3
Browser (WASM)WebGPU / WebGL2Mouse + keyboard + touchDownload size, no filesystem7
Android / iOSVulkan / Metal (via wgpu)Touch + on-screen controlsTouch RTS controls, battery, screen size8+
XboxDX12 (via GDK)GamepadNDA SDK, certification8+
PlayStationAGC (proprietary)Gamepadwgpu doesn’t support AGC yet, NDA SDKFuture
Nintendo SwitchNVN / VulkanGamepad + touch (handheld)NDA SDK, limited GPUFuture

Input Abstraction

#![allow(unused)]
fn main() {
/// Platform-agnostic input source. Each platform implements this.
/// Covers simulation orders (hot path, every tick) and UI-level input
/// (chat, navigation — used in lobby and post-game loops).
pub trait InputSource {
    /// Drain pending player orders from whatever input device is active.
    fn drain_orders(&mut self, buf: &mut Vec<TimestampedOrder>);
    // Caller provides the buffer (reused across ticks — zero allocation on hot path)

    /// Optional: hint about input capabilities for UI adaptation.
    fn capabilities(&self) -> InputCapabilities;

    /// Drain a pending chat message typed by the user (lobby/post-game).
    /// Returns None if no chat input is pending. Platform implementations
    /// source this from text fields, on-screen keyboards, etc.
    /// Default: no-op (platforms without chat input).
    fn drain_chat_input(&mut self) -> Option<ChatMessage> { None }

    /// Whether the user has signaled intent to leave the current screen
    /// (e.g., pressed Escape, tapped Back, clicked Leave button).
    /// Used by post-game and lobby loops. Default: false.
    fn wants_leave(&self) -> bool { false }
}

pub struct InputCapabilities {
    pub has_mouse: bool,
    pub has_keyboard: bool,
    pub has_touch: bool,
    pub has_gamepad: bool,
    pub screen_size: ScreenClass,  // Phone, Tablet, Desktop, TV
}

pub enum ScreenClass {
    Phone,    // < 7" — bottom bar UI, large touch targets
    Tablet,   // 7-13" — sidebar OK, touch targets
    Desktop,  // 13"+ — full sidebar, mouse precision
    TV,       // 40"+ — large text, gamepad radial menus
}
}

ic-ui reads InputCapabilities to choose the appropriate layout profile. The sim never sees any of this.

Platform Installer / Setup Capability Split (D069)

The first-run setup wizard (D069) needs a platform capability view that is separate from raw input capabilities. This captures what the distribution channel / platform shell already handles (binary install/update/verify, cloud availability, file browsing constraints) so IC can avoid duplicating responsibilities.

#![allow(unused)]
fn main() {
pub enum PlatformInstallChannel {
    StoreSteam,
    StoreGog,
    StoreEpic,
    StandaloneDesktop,
    Browser,
    Mobile,
    Console,
}

pub struct PlatformInstallerCapabilities {
    pub channel: PlatformInstallChannel,
    pub platform_handles_binary_install: bool,
    pub platform_handles_binary_updates: bool,
    pub platform_exposes_verify_action: bool, // Steam/GOG-style "verify files"
    pub supports_cloud_sync_offer: bool,      // via PlatformServices or platform API
    pub supports_manual_folder_browse: bool,  // browser/mobile often restricted
    pub supports_background_downloads: bool,  // policy/OS dependent
}
}

ic-game (platform integration layer) populates PlatformInstallerCapabilities and injects it into ic-ui. The D069 setup wizard and maintenance flows use it to decide:

  • whether to show platform verify guidance vs IC-side content repair only
  • whether to offer manual folder browsing as a primary or fallback path
  • whether to present a browser/mobile “setup assistant” variant instead of a desktop-style installer narrative

This preserves the platform-agnostic engine core while making setup UX platform-aware in a principled way.