Quest-Forge
An interactive dark fantasy narrative adventure for iOS 17+ with choice-based gameplay, character selection, branching storylines, procedural audio, and session persistence. Pure SwiftUI with zero third-party dependencies. v1.4 shipped — maintenance mode.
Overview
Quest-Forge is a choice-based narrative game where players select characters and navigate branching story scenarios in a dark fantasy setting. The game engine uses a dictionary-based scene graph ([String: StoryScene]) for O(1) lookups with human-readable snake_case scene IDs, making content authoring natural. The app shipped through phases 1–15 across milestones v1.0–v1.4, adding character-specific branching (v1.0), typewriter text animation via Combine Timer (v1.1), achievements and persistence (v1.3), and procedural audio soundscapes via AVAudioEngine (v1.4).
GitHub: irishrocker1125/Quest-Forge
Tech Stack
| Technology | Version | Purpose |
|---|---|---|
| Swift | 5.9+ | Primary language — @MainActor, modern concurrency |
| SwiftUI | iOS 17+ | Declarative UI, navigation, animations |
| Combine | System | Timer.publish for typewriter animation |
| AVFoundation | System | AVAudioEngine for procedural soundscapes and SFX |
| Foundation | System | Codable models, JSON persistence to Documents directory |
| Xcode | 15+ | Build system / IDE |
No external packages. Zero third-party dependencies.
Architecture
MVVM with GameState as central @MainActor ObservableObject. All screens use fullScreenCover for immersive chrome-free presentation. Services (HapticManager, AudioManager, PersistenceManager) use singleton pattern with lazy initialization.
Key Decisions
| Decision | Rationale | Outcome |
|---|---|---|
| SwiftUI native | Clean stack matches iOS-only scope | Good |
| Choice-based input only | Reduces complexity, maintains narrative flow | Good |
| No save states in v1 | Simplifies architecture, sessions are short by design | Good (revisiting in v1.1) |
| Dark fantasy primary tone | Sets atmospheric foundation, can expand later | Good |
| StoryScene naming | Avoid SwiftUI Scene collision | Good |
| Theme-prefixed colors | Avoid Xcode asset symbol conflicts | Good |
| fullScreenCover for game/selection | Immersive experience without nav chrome | Good |
| String IDs for scenes | Human-readable story authoring | Good |
| Combine Timer.publish | SwiftUI-compatible animations without retain cycles | Good |
| 0.25s fade timing | Quick but visible scene transitions | Good |
| SampleStory.all pattern | Enables iteration over all scenarios | Good |
| requiredCharacterIds on Choice | Character-specific content without fragmenting stories | Good |
| Optional discoveries (pendant) | Rewards exploration, affects later paths | Good |
| Named NPCs (Lyra Shadowmend) | Emotional weight to backstory reveals | Good |
| Genre-bending scenarios | Heist + cosmic horror differentiates content | Good |
Patterns Established
| Pattern | Description | Reusable |
|---|---|---|
| String scene IDs | Human-readable story graph navigation | Yes |
| Choice-based branching | Simple but effective narrative structure | Yes |
| Theme-prefixed assets | Avoid Xcode symbol conflicts | Yes |
| fullScreenCover for immersion | Modal presentation for game screens | Yes |
| Timer-based animations | Combine for SwiftUI-compatible timing | Yes |
Lessons Learned
| Lesson | Impact | Recommendation |
|---|---|---|
| String IDs are human-friendly | Story authoring is more natural | Use strings over UUIDs for narrative content |
| Prefix theme colors | Xcode assets can conflict | Always namespace asset names |
| Short sessions justify no saves | Reduced scope significantly | Scope to session length early |