00

Mobile · 2026

Load Away

Year
2026
Scope
Mobile · Academic
Role
iOS Developer, gaze tracking and watchOS heart rate
Team
Team of 5
Stack
Swift, SwiftUI, Swift 6 Concurrency, Xcode
Status
Completed

A game where the loading bar hates being watched.

A satirical iOS game that inverts the logic of a loading screen: the progress bar only advances while you are not looking at it. Look back too late and it collapses while mocking you out loud.

Load Away, built under the Xcode project name Wofoking, is a standoff with a camera. The progress bar fills only while you genuinely are not watching; head turned past your calibrated neutral and eyes off the screen, or both eyes shut. Guess when it hits 100% and look back inside a roughly two-second window to win; overshoot and the bar collapses.

Read moreShow less

The pitch came from internet culture: absurdity is what gets shared, so the team built the dumbest possible version of it. The app baits you into thinking it is an adventure game, or that loading is simply broken, until you glance away and it creeps forward. After early feedback the team leaned in and dressed the whole thing as a horror game: haunted forest, a red door, a warning splash. It is a prank wearing a horror mask.

Level 2, "Unstable Loading", is the shipped mode: the bar jitters, randomly drops, saves checkpoints, and near the top fakes completion at 99% before betraying you. A fake push notification chimes to bait your gaze back at the worst moment. With a paired Apple Watch, a spiking heart rate feeds the bar's volatility; the game runs on your panic.

There is no game engine. Load Away is a native SwiftUI app whose gameplay is a @MainActor GameEngine on a roughly 30 Hz timer, reading gaze from ARKit, heart rate from HealthKit over WatchConnectivity, haptics from CoreHaptics, and taunt audio from pre-baked clips. The Apple frameworks are the engine.

Load Away horror-game cover with a glowing red door in a dead forest and a progress bar frozen at 99 percent
Load Away player flow from home and face scan through the gaze-driven loop, progress check, and final jumpscare
The Threshold home screen concept showing Load Away inside a landscape iPhone with a glowing red door
Load Away key art with a glowing red door in a dead forest and a loading bar frozen at 99 percent

01 / 04

A gaze-driven horror game: the bar moves only when the player looks away.

Brief

Problem

A loading screen is dead time nobody engages with. The design question was whether the wait itself could become the game rather than something to sit through.

Solution

Gate the loading bar on gaze and make cheating impossible: turn away too little and nothing happens, slide your eyes back and you are taxed, shut your eyes and you fill it blind but cannot see when to stop.

My role

  1. 01

    Built the ARKit gaze pipeline: calibrated face→camera classification, the eye-gaze peek guard, the eyes-closed path, the frozen-mesh guard, and the continuous player identity lock

  2. 02

    Built the watchOS companion and the heart rate path: HKWorkoutSession and HKLiveWorkoutBuilder streaming BPM over WatchConnectivity, with a stale-sample watchdog and a resting-low baseline

  3. 03

    Diagnosed the watch pairing failure down to build configuration, embedding the watch app as a companion with WKApplication and WKCompanionAppBundleIdentifier

  4. 04

    Worked as one of three engineers in a five-person team alongside two designers

Features

  1. 01

    Gaze-gated loading bar driven by ARKit face tracking

  2. 02

    Calibrated, face→camera relative gaze classification instead of absolute head angle

  3. 03

    Eye-gaze peek guard with a peek tax that reverses progress

  4. 04

    Eyes-closed counted as looking away, gated by distance and pitch

  5. 05

    Continuous player identity lock preventing a face swap mid-round

  6. 06

    Level 2 "Unstable Loading": jitter, random drops, checkpoints, and a fake completion at 99%

  7. 07

    Roughly two-second window to look back at 100% before the bar collapses

  8. 08

    Apple Watch heart rate feeding bar volatility, shipped off by default

  9. 09

    Spoken taunts from a static phrase bank with pre-baked voice clips, optionally refined by on-device Foundation Models

  10. 10

    Fake push notification trap timed to bait a glance back

  11. 11

    English and Indonesian localization across UI, taunts, and voice

  12. 12

    Manual "Hold to Look Away" fallback for devices without TrueDepth

Architecture

A native SwiftUI app with no game engine. Gameplay is a @MainActor GameEngine on a roughly 30 Hz timer, structured as MVVM with a state machine, and every threshold is tunable through ConfigService rather than hardcoded. Gaze comes from ARFaceTrackingConfiguration, heart rate from a watchOS workout session over WatchConnectivity, audio from pre-baked clips with AVSpeechSynthesizer as live fallback.

Front end
Swift, SwiftUI, Swift 6 Concurrency
Tools
Xcode, Figma
Integrations
ARKit, HealthKit, WatchConnectivity, watchOS, Foundation Models, CoreHaptics, AudioToolbox, AVSpeechSynthesizer

Challenges

  1. 01

    Problem

    TrueDepth stops tracking near profile (~45°+) and reports the face as lost, so a full head turn read as "face lost" rather than "looking away".

    Solution

    Set the away-gate at 50°, well inside the reliable range, and enforced no-peeking with an eye-gaze guard instead of a wider angle.

  2. 02

    Problem

    Judging head angle in ARKit world axes drifted with phone motion and inverted the reading, turning right went undetected while turning left registered as a peek.

    Solution

    Re-derived the whole classification as face→camera relative geometry, which depends only on where the camera sits in the face's own frame and stays drift-immune in landscape.

  3. 03

    Problem

    Players cheated by turning their head away but sliding their eyes back to the screen, which a pure head-angle test cannot catch.

    Solution

    Added a pose-independent eye-gaze peek guard comparing the eye ray against the ray to the camera, plus a peek tax that knocks the bar backwards.

  4. 04

    Problem

    A perfectly still face mesh signalled a stale or replayed anchor whose frozen pose was often a turn, which falsely advanced the bar.

    Solution

    Added a frozen-mesh guard that treats a non-jittering mesh as face-lost.

  5. 05

    Problem

    ARKit silently re-fits a surviving anchor onto a different face during a player swap, so UUID alone let a substitute inherit the lock.

    Solution

    Added a continuous identity check using a canonical-mesh shape signature and an averaged pre-lock fingerprint, run at every angle the mesh stays reliable.

  6. 06

    Problem

    There is no realtime heart rate stream from the phone; live BPM requires a workout session, which lives on the watch.

    Solution

    Moved heart rate onto a watchOS companion running HKWorkoutSession and HKLiveWorkoutBuilder, streaming BPM over WatchConnectivity with a stale-sample watchdog and a resting-low baseline.

  7. 07

    Problem

    A watch app that looked installed never paired; WCSession.activationState stayed at 0 and heart rate never reached the phone.

    Solution

    The fix was build configuration, not code: embed the watch app as a companion inside the iOS app with WKApplication and WKCompanionAppBundleIdentifier set, then erase the stale simulator install shadowing the rebuild.

  8. 08

    Problem

    The eyeBlink blendShape reads high even with eyes open when the face is far away or tilted, so eyes-closed detection produced false positives.

    Solution

    Gated eyes-closed detection by a maximum distance of 0.6 m and a 20° pitch delta.

Lessons

  1. 01

    The central assumption, that looking away is just an angle, was wrong. Calibration and face-relative geometry were mandatory, not polish.

  2. 02

    The hard bugs were physical sensor behaviour and Xcode target wiring, and only surfaced on real hardware. Documentation and AI described the happy path; the device described reality.

  3. 03

    Anti-cheat signals doubled as comedy: because the camera already knew the eyes, brow, and heart rate, frustration taunts and the notification trap came nearly for free.

  4. 04

    Cutting Level 1 from play sharpened the hook even though it was built and working.