Docs

Lumen

Install and make Lumen yours, the puzzle game in SwiftUI + Metal. Version 1.0.0.

A light-routing puzzle in soft matte ceramic, in SwiftUI and Metal, with 120 solver-checked experiments, a daily commission and a full StoreKit 2 store. This guide takes you from download to a running game under your own name. It covers version 1.0.0. See the screens on its game page.

Requirements

  • Xcode 26 and an iOS 17.0 or later deployment target
  • Tuist 4 (brew install tuist)
  • An Apple Developer team for devices, Game Center, iCloud and the widget. The simulator runs without one.

Install

Download

Included with SwiftPieces Games. Not signed in? The button takes you to sign in or create an account, then starts the download when you're back. Unzip the archive; it contains one folder, Lumen/.

Make it yours

cd Lumen
scripts/setup.sh --name YourGame --bundle-id com.you.yourgame --preset kiln

The script renames the target, scheme and bundle ids, rewrites the StoreKit product ids and the Game Center prefix, sets the starting preset and runs tuist generate. To keep the name Lumen for now, run tuist generate instead.

Run it

Open YourGame.xcworkspace, choose an iPhone simulator and run the YourGame scheme. The scheme uses Configuration/Products.storekit, so the shop works before App Store Connect is set up.

What's in the project

  • Content: 120 levels
  • Game Center: 3 leaderboards, 16 achievements, 1 challenge
  • Shop: Hint packs (10, 30, 100), Patron (unlimited hints)
  • Built on: GameCore, Feel, Shaders, DesignSystem, GameUI, Economy, GameServices, AppCore, RefractKit
  • Presets: ceramic, kiln, celadon
  • Swift: 10,615 lines in 97 files

Rebrand options

scripts/setup.sh changes everything that names the game, in one command:

OptionWhat it sets
--nameTarget, scheme and product name
--bundle-idBundle id. The widget, modules, App Group (group.<bundle-id>) and tests derive theirs from it
--display-nameThe name under the icon. Copy reads it from Info.plist at runtime
--teamYour team id. Required on devices for Game Center, iCloud and the App Group
--presetThe starting glaze: ceramic (warm bisque in daylight), kiln (warm charcoal, glowing light) or celadon (pale green-grey). Players can switch in Settings
--product-prefixApp Store Connect product ids, <prefix>.hints.10 and so on. Defaults to the bundle id
--gc-prefixGame Center ids, <prefix>.seals.total and so on. Defaults to the last part of the bundle id

The values live in Tuist/ProjectDescriptionHelpers/AppConfig.swift if you prefer to edit them by hand. Privacy, terms and support links are Info.plist keys in Template.sharedInfo (LumenPrivacyURL, LumenTermsURL, LumenSupportEmail).

Structure

App/            entry point, asset catalog, Levels.json, strings
Features/       every screen, in one framework: Menu (the cover), Map (chapter pages), Play, Shop,
                Commission, Settings, Profile (the index), Onboarding
Modules/
  GameCore      fixed-step clock, seeded RNG, grid maths, easing, event ring        (no game types)
  Feel          AVAudioEngine voice pool, synthesized sounds and music, Core Haptics (no game types)
  Shaders       MTKView stage, HDR scene target, bloom, grain, tiers                (no game types)
  DesignSystem  GameTheme tokens, the three Lumen presets, fonts, accessibility variants
  GameUI        paper-and-ink component kit: buttons, mounts, paper cards, tape, swatches, the seal,
                toggles, toasts, modals, pencil notes and callouts, the page turn
  Economy       hint wallet, catalog, daily commission, StoreKit 2                  (no game types)
  GameServices  versioned saves with iCloud merge, Game Center, reminders           (no game types)
  RefractKit    the rules: light, pieces, the tracer, the solver, the generator and chapter recipes
  LumenUI       the Metal bench renderer and scene, sounds, haptics and the juice director
  AppCore       player store, campaign, router, settings
Shared/         the widget snapshot, compiled into the app and the widget
Widget/         today's commission (small, medium, lock screen)
Tests/          unit tests (rules, feel, economy, saves, meta, theme) and a UI test
Tools/LumenLab  a Swift package that runs the tests and the level tools on any Mac or Linux box
Design/         StyleSpec.md (the art direction) and BALANCE-levels.md (the generated level table)

The rules never import SwiftUI. Bench takes a tap and returns a TurnOutcome plus a fresh Trace of where every channel of light went. The play screen hands both to LightScene, which pours the changed light outward from the turned piece at a steady speed and raises BenchEvents as it reaches each card; JuiceDirector turns those into sound, haptics, sparks and HUD reactions. SwiftUI only sees low-frequency state through PlaySession, and the HUD changes when the light lands, never before.

One MTKView sits behind every screen for the life of the app. Screens change what it shows: a blank page, a figure (the cover's prism, a chapter's ray path), or the bench. Touches, notes and the bench rect share one coordinate space (.page), so a pencil note and the card it describes always agree.

Rules at a glance

  • Light has three channels, red, green and blue, and mixes additively into seven colours. Beams travel in four directions and pass through each other.
  • Mirrors reflect 90°. Splitters pass the beam and reflect a copy. Prisms send red ahead, green to one side and blue to the other. Filters pass only their colour. Dichroic panes reflect their colour and pass the rest. Posts block light. Pinned pieces can't turn.
  • A card is lit only by its exact colour. Missing channels leave it dim; extra channels show hatching.
  • Every experiment has a par: the fewest turns from the start, found by the solver. Finishing at or under par stamps the seal.
  • Undo and reset are free and unlimited. There are no timers, lives or fail states.

Levels

App/Resources/Levels/Levels.json holds all 120 experiments: bench size, lamps, cards, pieces with their start positions and pins, par, the stored solution, seed and tutorial key. Edit it by hand or regenerate:

cd Tools/LumenLab
swift run -c release lumenlab generate --out /tmp/lumen            # all 120: Levels.json + BALANCE-levels.md
swift run -c release lumenlab generate --only 37-48 --out /tmp/iv  # one chapter, to try a recipe change
swift run lumenlab check ../../App/Resources/Levels/Levels.json   # re-solves every experiment
swift run lumenlab show 27                                         # draws one experiment as text

Copy a full run's Levels.json into App/Resources/Levels/ and its BALANCE-levels.md into Design/. A --only run writes only those experiments, so don't copy it over the full file.

The generator builds each experiment from its chapter's recipe in Recipe.swift, solution first: it lays pieces on the lit path, places cards where beams end or cross, adds posts on dark cells and pins, then scrambles the pieces. The solver rejects anything already solved, with par out of the recipe's range, or with too many solutions. BALANCE.md explains the curve and the economy around it.

Theming

Every colour, font, radius, stroke, spring, sound and haptic comes from the GameTheme in Modules/DesignSystem/Sources/Theme/Themes/Lumen.swift, which is written from Design/StyleSpec.md. Views never use raw colours, and colour belongs to light only: buttons, cards and panels are raised and pressed glaze. The renderer reads the same palette as linear-space uniforms, and the three light channels come from the palette's piece colours, so the colour-vision variants change the light too. To add a preset, add a makeCeramic(...) call next to Celadon and list it in lumenPresets.

Economy and the shop

Hints are the only currency. A player starts with five and earns one for each first seal, three for each chapter completed, two for each daily commission and five more on every seventh day of a commission streak. Values live in Modules/Economy/Sources/Catalog.swift; BALANCE.md has the reasoning.

The shop sells three hint packs and Patron, a one-time unlock for unlimited hints and a Patron mark on the profile. Purchases go through StoreKit 2 with verification, Transaction.updates, unfinished transactions, refunds and Restore. Grants are idempotent by transaction id, so a purchase is never paid twice even across devices. To check receipts on your server, implement PurchaseValidator.

Game Center

Configuration/GameCenter.json lists everything to create in App Store Connect: three leaderboards (total seals, the daily commission ranked on fewest turns, total cards lit), 16 achievements worth 900 points, a challenge on the commission leaderboard and an experiment activity. Scores and achievements queue offline and flush on sign-in. The sign-in sheet waits for the cover and never interrupts the first experiment. On iOS 26 the Games app can open an experiment through the activity.

Saves

SaveGame is versioned JSON with a .bak copy. A save from a newer version opens read-only instead of being overwritten. iCloud key-value sync merges conflicts field by field: progress takes the best, the wallet and streaks take the newest, purchases are unioned.

Launch arguments

ArgumentEffect
-uitestA fresh save in a temporary folder, no iCloud or Game Center, the intro skipped, and a hidden "solve" button for UI tests
-SPNoAudioNever starts the audio engine. For screenshot runs on a Mac whose audio daemon is stuck

Tests

tuist test                               # on a simulator, from Xcode 26
cd Tools/LumenLab && swift test          # the rules, economy, saves and meta tests, on macOS or Linux

Unit tests cover light and the tracer, every piece, the solver and par, the generator for every chapter, the bench with undo, reset and hints, the wallet, the catalog, the commission and its streak, save migration and merge, achievements, the player store (every shipped experiment solves at par), the feel layer and the theme audit. FirstLevelUITests solves experiment 1 from the cover to the result card and attaches screenshots (TEST_RUNNER_SP_SCREENSHOT_DIR=/path also writes them to disk).

Tools/LumenLab/Sources links to the module folders. Edit the files under Modules/.

Performance

Light pours at 120 Hz on ProMotion iPhones. Presentation steps at a fixed rate; beams are instanced quads accumulated in one light buffer, and the page composes it in a single full-screen pass. Nothing allocates per frame. QualityTier follows thermal state and Low Power Mode, dropping frame rate first, then bloom rings. The view drops to an ambient rate when nothing moves and pauses when the app leaves the foreground.

App icon

The icon (light, dark and tinted) is drawn by Tools/Icon/make_icon_svg.py and rendered with node Tools/Icon/render.js (Playwright). Replace the PNGs in App/Resources/Assets.xcassets/AppIcon.appiconset with your own, or open them in Icon Composer.

Localization

Strings are in the String Catalog (App/Resources/Localizable.xcstrings), which Xcode fills on build. Copy is written to survive translation: no strings built from fragments, and plurals through the catalog.

Licenses

DM Sans is under the SIL Open Font License (Licenses/). Everything else is original to SwiftPieces Pro; see ASSET_MANIFEST.md.

Changelog

1.0.0

2026-10-30

  • First release: a light-routing puzzle on the pages of a lab notebook, in SwiftUI and Metal, as a complete Tuist project with one-command setup (name, bundle id, team, page preset, product and Game Center ids).
  • RefractKit: additive light in three channels, mirrors, splitters, prisms, filters, dichroic panes, posts and pins, a per-channel beam tracer, an exhaustive solver for par and hints, and a seeded generator driven by chapter recipes.
  • 120 experiments in ten chapters (Mirrors to Large set-ups), each re-solved at par, with a chapter finale on a larger bench.
  • A Metal notebook renderer: graph paper with a margin rule, beams accumulated in a light buffer that tints the paper (or glows on the dark bench), SDF lens mounts, prisms, filters, dichroic panes, lamps and target cards with daylight shadows, light that pours along the page as you turn a piece, and a flood of colour on the solve.
  • Sound and haptics synthesized at launch: glass ticks for the mounts, a chord for light (red root, green fifth, blue third), pencil, tape and paper.
  • Screens: the cover, chapter pages with a lit ray path and index tabs, the bench with pencil notes, hints and a taped result card with filter swatches and the par seal, the daily commission, supplies, settings and the index, all on a paper-and-ink component kit.
  • Hints as the only currency, Patron for unlimited hints, and a daily commission with a forgiving streak.
  • StoreKit 2 shop with four products, verification, refunds and restore.
  • Game Center: three leaderboards, 16 achievements, a commission challenge and an experiment activity, with an offline queue.
  • Versioned saves with a backup copy and iCloud conflict merge; a commission reminder; a commission widget for the Home and Lock Screen.
  • Three page presets (Notebook, Bench, Vellum), Reduce Motion and colour-vision variants, VoiceOver play and Dynamic Type.