This is a submission for the Presage SmartSpectra SDK Challenge
What I Built
Breathe as One is a little co-op game for two people and two iPhones. You win by breathing together.
You sit across from each other with your phones propped up, front cameras facing you. Each phone only watches its own player. The SmartSpectra SDK picks up the rise and fall of your chest and turns it into a live wave, and the two phones swap those waves over a private nearby connection. So both screens show the same thing: your breath in blue and your partner's in coral. As you settle into the same rhythm, the two lines drift toward each other's color until they melt into one lilac line. Getting there is the whole game.
A game takes a few minutes and has three rounds:
- Follow the Tide (45 s). A white wave rolls across the screen at 6 breaths a minute, and you both ride it.
- No Leader (60 s). The wave goes away and you have to find each other without a guide. It's harder than it sounds, and it's my favorite part.
- Heart Meet (45 s). Each of you gets a glowing heart that beats with your own pulse. Breathing slowly together tends to bring your pulse rates closer, and the hearts drift toward each other as they do.
At the end you see your overall sync score, your longest stretch in sync, how many breaths you took together, and a ribbon of both waves woven together. You can share that as an image if you like.
If you only have one phone, there's a ghost mode with a simulated partner. It's good for practice and for checking your setup.
Before I started, I read through the other entries, and all of them measure one person. I kept wondering what it would look like if the signal were something two people make together.
Demo
A note on the footage. The tune-in and Follow the Tide shots run on a real SmartSpectra session from my iPhone 17 Pro. Debug builds save the SDK's numbers during a game, so I copied one off the phone and replayed it in the simulator. The wave, the switch from "Reading breath" to "Ready" once the SDK's 30 second window fills, and the score against the tide all come from my actual breathing. The SDK doesn't save images, so the blurred face in the camera card is a stock clip run through the app's own blur.
The two-phone scenes are two iPhone simulators playing a full game with each other. They use the real pairing code and encrypted connection, with simulated breathing. Every scene is labelled on screen so you can tell which is which. I've also paired two real iPhones with the same code flow.
Code
sbis04
/
breathe-as-one
Two-player co-op iPhone game you win by breathing together, built on the Presage SmartSpectra SDK
Breathe as One
A two-player co-op game for iPhone where you win by breathing together.
Two phones. Two people. One breath
You and a friend sit across from each other, phones propped up, front cameras facing you. Each phone watches only its own player. The Presage SmartSpectra SDK picks up the rise and fall of your chest and turns it into a live wave, and the two phones swap those waves over a private nearby connection. Both screens show the same duet, and when you settle into the same rhythm the two lines slowly blend into one.
I made this for the DEV Presage SmartSpectra SDK Challenge.
Screens
Setup steps are in the README. You'll need a SmartSpectra API key (it goes in a git-ignored .env), XcodeGen, and one or two iPhones on iOS 17 or later. Everything except the camera also runs in the simulator, with simulated or replayed players. It's MIT licensed.
How It Responds to Human Signals
Here are the SmartSpectra signals the game uses (Swift SDK 3.4.0):
| Signal | How the game uses it |
|---|---|
Breathing waveform (breathing.upperTrace) |
Your wave on both screens, and the sync score |
| Breathing rate and confidence | Decides when you're "Ready", so scoring only starts once the signal is trustworthy |
| Pulse rate | How close your two hearts are in Heart Meet |
Pulse waveform (cardio.arterialPressureTrace) |
Makes each heart in Heart Meet beat with that player's actual pulse |
Talking detection (face.talking) |
Pauses scoring and says "Shh. Talking pauses your breathing wave." |
Validation status (validationStatus.code / hint) |
Setup tips when your face or chest isn't in view, it's too dark, or you're too close |
I configure SmartSpectraSDK.shared for the front camera with breathingMetrics + cardioMetrics + [.talking], turn on imageOutputEnabled for the framing preview, and set enableTelemetry = false. A small PresageSource class polls sdk.metrics 30 times a second and passes along only the samples it hasn't seen yet, converted to the app's own clock.
The SDK needs about 30 seconds of data before it reports breathing confidence, so the game opens with a tune-in screen instead of a spinner. You see a small, blurred teal version of yourself with an outline for your face and shoulders. There's an eye button if you want a clear look for 4 seconds. The SDK's validation hints show up as short tips underneath, and each player has a status row that goes from "Finding you" to "Reading breath" to "Ready".
During a round, each phone sends its chest wave, pulse rate and a signal quality flag to the other phone ten times a second. A quick ping exchange at the start lines up the two phones' clocks.
The scoring lives in SyncEngine, which is plain Swift with unit tests. It resamples both waves onto a shared 20 Hz grid, removes the slow drift, and normalizes each wave, because everyone's chest moves on a different scale. The sync score is the Pearson correlation over the last 8 seconds, mapped to 0 to 100. It also checks up to 1.5 s of lag so the game can tell you when you're a beat behind. Inhales are found with a bit of hysteresis, and if you both peak within 0.6 s of each other, that counts as a shared breath.
Most of the feedback is color and touch. As your score climbs, the lines blend toward lilac. You feel a soft heartbeat haptic when you cross into sync, and each shared breath gets a gentle swell.
Something the real data taught me. On my first iPhone build, the game kept getting stuck on "Reading breath". I pulled a saved session off the phone to find out why. It turns out the SDK only includes breathing and pulse rates in some of its updates: pulse every 1 to 3 seconds, breathing roughly every 3, and sometimes 20 seconds apart. Confidence also arrives as 0 to 100, and I'd assumed 0 to 1. My code read every update without a rate as "no signal". The fix was a tiny RateTracker that keeps the last reading for up to 25 seconds and rescales the confidence. Replaying that same session, the number of frames with a usable signal went from 40 to 4,678 (out of 5,651).
The connection between phones uses Network.framework. The phones find each other over Bonjour and peer-to-peer Wi-Fi, so you don't need a router or internet. To join, you type the 4 digit code from your partner's screen. That code becomes the TLS pre-shared key (through HMAC-SHA256), so the connection is encrypted and a wrong code simply fails to connect. I started out with MultipeerConnectivity and then found it's deprecated in the iOS 27 SDK. One phone acts as the host. It runs the round timer and sends the final tally, so both screens always show the same score.
Prize Categories
I'm entering Most Creative or Delightful Use.
The idea came from something a lot of people have noticed: when two people sit together quietly, their breathing often lines up without either of them trying. I wanted to make that visible and turn it into a game, using only the phones people already have. No chest straps, no wearables.
It's built for two, and the score belongs to the pair. You can't win it alone. In No Leader, if one person takes charge, the other ends up chasing and the waves drift apart, so you really have to pay attention to each other. The color blend is the score itself, which means you can feel how you're doing without reading a number. I also like that each phone only measures the person holding it, so nobody's camera ever points at someone else.
Consent and Data Handling
The camera doesn't turn on until each player agrees on their own phone. First there's a "Before we start" screen. It explains that the camera only watches you, what your partner will receive, that nothing is saved, that the SDK talks to Presage, and that this is a game and not a medical device. Tapping "I'm in, turn on my camera" starts it, and iOS asks for camera permission at that point.
Video never goes to your partner, and Presage's docs say the SDK computes the vitals on the device. The camera preview stays blurred unless you tap to peek.
The only things sent to the other phone are derived numbers: chest wave points, pulse rate, a signal quality flag and the round timing. They travel over a local TLS connection keyed by your pairing code. There's no server and no account.
Nothing is stored. There's no history and no analytics, and when the game ends the numbers are gone. The results card is drawn on the phone and only leaves it if you tap Share. Debug builds can save the SDK's numbers (never images) so I can replay sessions while developing, but release builds don't include that code.
While measuring, the SDK keeps an authenticated session with Presage to check the API key and count usage. I turned its optional telemetry off with enableTelemetry = false, and the app doesn't use LLM Insights or any other AI service.
It's not a medical device. The numbers are for fun and relaxation, the game makes no health claims, and it can't be used to measure anyone who hasn't agreed to it on their own phone.
Agent Session
I built this with Claude Code. Here's the session, trimmed down, with private paths and device names taken out:
can you lookm into this hackathon: https://dev.to/challenges/presage-2026-11-04
and suggest what can we build to get a winning spot?
subvmission template:
This is a submission for the Presage SmartSpectra SDK Challenge
What I Built
<!-- Provide an overview of your experience and what it lets people do. -->
Demo
<!-- Embed or link your demo video showing the live Presage signal(s) in action. Public hosting of your app is optional. -->
Code
<!-- Share a link to your public code repository. Make sure it includes setup instructions. -->
How It Responds to Human Signals
<!-- Which live Presage signal( …
{
}
{"Ok":[{"id":143,"title":"The Presage SmartSpectra …
{
"tag": "presagechallenge",
"per_page": 100
}
{"Ok":[{"id":4993301,"title":"Exhale: A Breathing …
{
"tag": "presagechallenge",
"per_page": 100,
"page": 2
}
{
"Ok": [
]
}
{
"id": 4975387
}
{"Ok":"We're excited to announce our newest challe …
Docs are read. Waiting on the submissions scan before picking an idea, so the pick is based on what's actually been entered.
[6 more tool calls omitted]
we can build anuthign btw, web, mobile, desktop, abythig that makes sense
Got it, any platform is fine. One thing from the docs: there's no browser SDK. SmartSpectra ships for iOS (Swift, iOS 17+), Android (Kotlin, min SDK 28), Node/Electron (desktop), and C++. So "web" in practice means an Electron app, or a native app that streams signals to a web or TV display. The submissions scan is still running; I'll come back with the recommendation once it finishes.
Presage challenge: what's been entered, and what I'd build
Rules that matter: submissions are due Nov 22, 11:59 PM PST, so you have 14 days. Judging is on use of the tech, usability and originality. There are 10 prizes of $300 each: 4 overall, 3 Most Creative, 3 Thoughtful Real-World. Medical claims and monitoring people without consent are banned. You only get Presage's free trial, which entrants say is 100 credits with no top-ups.
What's already entered (25 real entries out of 37 posts)
- Breathing, calm and generative art are saturated. There are about 9 of these, including 3 gardens or forests. Stress and interview coaching is crowded too: HalfBeat, Viva Mode, StagePu …
[Image #2]
The pricing page confirms there's a free trial with no credit card, but it doesn't say how big the trial is or what uses it up. The "100 credits" figure came from other entrants' posts, not from Presage, so treat it as unconfirmed until you see your own account.
To pin it down:
- Get the key at physiology.presagetech.com.
-
Read your balance. Either check the dashboard for a credit or usage count, or connect Presage's MCP server (
https://mcp.presagetech.com/mcp). Its README says it can report your plan and credits, and fetch your key and register the iOS bundle ID for you. - Find out what one credit costs. I'd run one 60- …
what are your project siuggestins?
Six ideas, ranked by how likely they are to place. Each one avoids what's already crowded (breathing calm apps, gardens, interview or stress coaches) and uses gaps nobody has touched: native mobile, more than one person, beat-by-beat pulse, and education.
1. Body Lab: a pocket physiology lab (my top pick)
iOS, for both prize categories. Guided 60–90 second experiments you run on yourself, each ending with a lab report card for students:
- Heart follows breath: watch your heart rate rise as you breathe in and fall as you breathe out.
- Dive reflex: hold your breath and watch the gaps between heartbeats lengthen.
- Blink rate: compare reading, watching and talking.
It use …
I reallty like this:
- Breathe as One: a co-op sync game
Two phones and one shared screen (your TV, or one phone), for Creative. Each player's phone measures only them. The shared screen overlays both breathing waves, and you score points by getting in sync. Later rounds add pulse matching, plus a "make your partner laugh" round using expressions. No entry has more than one person, and it demos beautifully. Risk: keeping two phones connected and their timing aligned.
let's get started with it and create a native ios app
maybe exclude this: ""make your partner laugh" round using expressions."
first let's plan before starting the implentation
{
}
Entered plan mode. You should now focus on explori …
$ {"command": "ls ~/MobileDev; ls ~/MobileDev/<other-project> 2>/dev/null | head -30; xcodeb …
[listing of unrelated local folders redacted]
$ {"command": "for p in docs/swift/option-1-api-key.md docs/swift/headless-mode.md; do echo …
=================== docs/swift/option-1-api-key.md …
$ {"command": "ls ~/MobileDev/iOS; …
[listing of unrelated local folders redacted]
Checked the Swift SDK API and your existing iOS projects. An earlier project's XcodeGen setup is a good template to copy. Now I'll confirm a few hardware and scope choices before writing the plan.
Plan approved. Starting Day 1: scaffold the project and resolve the SmartSpectra package, so I can read its real Swift interface before writing the spike.
[5 more tool calls omitted]
[Image #3]
{"file_path": "~/.claude/projects/<project>/memory/breathe-as-one-presage.md", "content": …
File created successfully at: ~/.claude/projects/< …
$ {"command": "cd ~/.claude/projects/<project>/memory && echo '- [Breathe as One (Presage)]( …
[redacted]
let me know hiow cna I provide the api key
Creating a git-ignored Secrets.xcconfig for the key now, so it never lands in the repo or this chat. Instructions follow in a moment.
$ [redacted: touches secrets]
[redacted]
{"file_path": "~/MobileDev/iOS/BreatheAsOne/project.yml", "content": "# BreatheAsOne: Xcod …
File created successfully at: ~/MobileDev/iOS/Brea …
{"replace_all": false, "file_path": "~/MobileDev/iOS/BreatheAsOne/project.yml", "old_strin …
The file ~/MobileDev/iOS/BreatheAsOne/project.yml …
$ {"command": "mkdir -p BreatheAsOne/Resources/Assets.xcassets/LaunchBackground.colorset Bre …
⚙️ Generating project...
⚙️ Writing project...
C …
i have the api key in the clipboard feel free to save it in a .env
Saving it from the clipboard without printing it. .env is the source of truth, and the git-ignored Secrets.xcconfig is generated from it.
$ [redacted: touches secrets]
[redacted]
The key is saved: 40 characters, in .env and Config/Secrets.xcconfig, both git-ignored. Now reading the SDK's real Swift interface.
$ {"command": "find build/spm -name \"*.swiftinterface\" -path \"*arm64-apple-ios.swiftinter …
build/spm/artifacts/smartspectra-swift/SmartSpectr …
$ {"command": "cd ~/MobileDev/iOS/BreatheAsOne/build/spm/artifacts/smartspectra-swift/SmartS …
./ios-arm64-simulator/SmartSpectra.framework/Modul …
$ {"command": "F=~/MobileDev/iOS/BreatheAsOne/build/spm/artifacts/smartspectra-swift/SmartSp …
5110 ~/MobileDev/iOS/BreatheAsOne/build/spm/artifa …
[4 more tool calls omitted]
also wehn the basic implementation is done, feel free to test it end to end on a ios simulator (iPhone 18 Pro), and fix any ux issues and bugs there
{"file_path": "~/MobileDev/iOS/BreatheAsOne/BreatheAsOne/Signals/PresageSource.swift", "co …
File created successfully at: ~/MobileDev/iOS/Brea …
{"file_path": "~/MobileDev/iOS/BreatheAsOne/BreatheAsOne/Signals/SimulatedSource.swift", " …
File created successfully at: ~/MobileDev/iOS/Brea …
{"file_path": "~/MobileDev/iOS/BreatheAsOne/BreatheAsOne/Signals/SampleLog.swift", "conten …
File created successfully at: ~/MobileDev/iOS/Brea …
{"file_path": "~/MobileDev/iOS/BreatheAsOne/BreatheAsOne/Signals/ReplaySource.swift", "con …
File created successfully at: ~/MobileDev/iOS/Brea …
My Clock enum clashes with Swift's built-in Clock protocol, so I'm renaming it to MonoClock. The other diagnostics are SourceKit indexing before regeneration.
Now the spike screen: credit meter, waveform view and the source picker.
Turning on file sharing so recordings can be pulled off the phone, then building for the simulator.
The build needs an app icon. I'll generate a placeholder with a small script, modelled on an earlier project's icon script.
The build succeeded. Checking the icon, then launching the spike on the booted iPhone 18 Pro simulator with the simulated source.
The icon is a fine placeholder; the glow is off-centre, and I'll polish it later. To drive the simulator I'll check how an earlier project's accessibility helper taps the simulator.
[17 more tool calls omitted]
continuye
Continuing. I'll leave the other booted simulator alone (no terminate or uninstall there) and run the steps one at a time. First, booting the second iPhone 18 Pro:
$ {"command": "SIM=<device-id>; xcrun simctl boot $SIM 2>&1; xcrun simctl bootstatus $SIM -b …
An error was encountered processing the command (d …
It's already booted. Installing and launching there:
$ {"command": "SIM=<device-id> && xcrun simctl install $SIM build/dd/Build/Products/Debug-ip …
com.<user>.breatheasone: 55045
Application 'Breath …
$ {"command": "SIM=<device-id>; idb ui tap 200 294 --udid $SIM && sleep 8 && xcrun simctl io …
ok
$ {"command": "SIM=<device-id>; idb ui tap 200 294 --udid $SIM; sleep 1; idb ui tap 314 197 …
Application 'Breathe as One' {'x': 0, 'width': 402 …
Replay works too: the recording plays back with the same timing (breathing confident at 4.0 s). Stopping it and committing this first milestone:
Fixing an isolation shortcut I took in the invitation handler: the accept callback is now main-actor-isolated instead of reaching across with assumeIsolated.
The model builds. Now the screens: welcome, consent, pairing, play (tune-in, duet ribbons, Heart Meet) and results.
Two small hooks are still missing: mySourceForPreview on the model, and a close button on the Signal Lab. Adding them, then building.
The full app builds. Now the end-to-end simulator run, starting with the ghost mode flow on the iPhone 18 Pro.
[26 more tool calls omitted]
comyinuer
$ {"command": "SIM=<device-id> && S=<scratchpad> && xcrun simctl terminate $SIM com.<user>.b …
ok
The screenshot is just the launch screen, so it was probably taken too early. Retaking:
$ {"command": "SIM=<device-id>; S=<scratchpad>; sleep 3; xcrun simctl io $SIM screenshot $S/ …
Application 'Breathe as One' {'width': 402, 'x': 0 …
The welcome screen looks good. Walking the ghost flow now: consent, then tune-in.
$ {"command": "SIM=<device-id>; S=<scratchpad>; cat > $S/ui.sh <<'EOF'\n#!/bin/sh\n# ui.sh < …
tapped Practice with a ghost at 201 740
<scratchpa …
$ {"command": "SIM=<device-id>; S=<scratchpad>; $S/ui.sh $SIM tap \"I'm in, turn on my camer …
tapped I'm in, turn on my camera at 201 750
<scrat …
[1 more tool calls omitted]
can we also make syre there's nice haptics in the app as well
{"file_path": "~/MobileDev/iOS/BreatheAsOne/BreatheAsOne/UI/Haptics.swift", "content": "im …
File created successfully at: ~/MobileDev/iOS/Brea …
$ {"command": "python3 - <<'EOF'\np='BreatheAsOne/Signals/SimulatedSource.swift'\ns=open(p). …
61: func nudge(toward target: Double, gain: Dou …
$ {"command": "python3 - <<'EOF'\np='BreatheAsOne/Game/GameModel.swift'\ns=open(p).read()\nd …
ok
$ {"command": "python3 - <<'EOF'\np='BreatheAsOne/Game/GameModel.swift'\ns=open(p).read()\na …
405: if false {
406- lastSyncHap …
Removing the leftover dead block that replacement left behind:
Now the Heart Meet layout (labels in a fixed row, hearts keep a minimum gap), the tune-in overlap, and button haptics:
The fixes build. Running the whole ghost game again to verify, with screenshots in round 1 (mid-learning), Heart Meet and results:
[5 more tool calls omitted]
instead of the whilte glow when both are in sync can we habve a color mixing effect bertween the two lines, so that it looks nicer
$ {"command": "python3 - <<'EOF'\np='BreatheAsOne/UI/Theme.swift'\ns=open(p).read()\na=\"\"\ …
BreatheAsOne/UI/HeartMeetView.swift:21: …
Making the Heart Meet "together" glow use the blended color too, then building and checking it in round 2:
$ {"command": "sed -i '' 's/.fill(RadialGradient(colors: \\[.white.opacity(0.45), .clear\\]/ …
** BUILD SUCCEEDED **
tapped Practice with a ghost …
$ {"command": "cd <scratchpad> && python3 -I -c \"\nfrom PIL import Image\nnames=['21-r1','2 …
Shell cwd was reset to ~/MobileDev
$ {"command": "cd ~/MobileDev/iOS/BreatheAsOne && python3 - <<'EOF'\np='BreatheAsOne/UI/Them …
** BUILD SUCCEEDED **
tapped Practice with a ghost …
The basic game is built and committed (3 commits so far). On the iPhone 18 Pro simulator, the full ghost mode game runs end to end: welcome, consent, tune-in, all three rounds, results and share card. 13 unit tests pass.
Bugs and UX issues I found in the simulator and fixed:
- The ghost ignored the white tide in round 1, so solo scores were stuck near 0. It now rides the tide.
- The two Heart Meet bpm labels overlapped ("787bbpm"). The readings now sit in a fixed row and the hearts never fully overlap.
- Being a quarter-breath off scored 0, which felt punishing. It's now about a third; fully opposite breathing still scores 0.
- The tune-in wave overlapped the placeholder text.
- The …
[2 more tool calls omitted]
yes boot the second simulator and test duet mode
$ {"command": "SIM2=<device-id>; xcrun simctl boot $SIM2 2>&1; xcrun simctl bootstatus $SIM2 …
Unable to find application named 'Simulator'
insta …
also address the rewuests I made in this round as well
$ {"command": "SIM1=<device-id>; SIM2=<device-id>; S=<scratchpad>; for SIM in $SIM1 $SIM2; d …
tapped Play with a friend at 201 673
tapped Play w …
Discovery works: each simulator sees the other ("Ripple 98" and "Lull 43"). Sim 1 invites, sim 2 accepts:
$ {"command": "SIM1=<device-id>; SIM2=<device-id>; S=<scratchpad>; $S/ui.sh $SIM1 tap \"Lull …
tapped Lull 43, Invite at 201 280
<scratchpad>/d1- …
Sim 2 never showed the invitation alert. Checking what sim 1 thinks happened:
$ {"command": "SIM1=<device-id>; SIM2=<device-id>; S=<scratchpad>; echo \"--- sim1\"; $S/ui. …
--- sim1
Application 'Breathe as One' {'x': 0, 'wi …
Sim 2's advertiser never logged an incoming invitation. Checking sim 1's side of the invite:
$ {"command": "SIM1=<device-id>; xcrun simctl spawn $SIM1 log show --last 3m --predicate 'pr …
2026-11-08 09:45:47.906 Df BreatheAsOne[90996:1e19 …
The diagnostics show MultipeerConnectivity is deprecated in the new OS 27 SDKs ("Use Network Framework instead"). Checking whether that applies to iOS 27 too, since judges will read the code:
Now the pairing screen: your code shown big, the nearby players, and a code-entry sheet when you tap someone.
Builds clean, with no deprecation warnings. Duet test again: sim 1 joins sim 2 by typing sim 2's code.
Discovery works over Network.framework. Testing the wrong-code path first (it should fail the TLS handshake), then the right code, 7698:
A wrong code leaves it stuck on "Connecting..." for 5+ seconds instead of failing. Watching a bit longer and checking the network logs to see which state the connection is in:
Now the correct code, 4950:
The correct code hangs too, so it's my TLS-PSK setup, not the code check. Checking the listener (server) side logs on sim 2:
The listener is up on port 50328, but sim 2 shows no inbound connection at all. Checking which port sim 1 is actually dialling:
The client reaches the right port (50328) and the TCP connection opens, but the server never starts its side of the TLS handshake. Looking for the inbound connection object on sim 2:
The server never even logs an inbound connection. To isolate it from the simulator and the app, I'll run a small standalone macOS script with the same TLS-PSK listener and client config on localhost:
Now all three fail at discovery, including the framing test that passed twice moments ago. Before guessing, I'll check whether that one test still passes on its own:
No log lines came through, because info-level messages aren't kept by default. Rereading with info enabled:
All 16 tests pass, including the new check that the guest shows exactly the host's scores. Clearing the leaked test name on sim 1 and committing:
I tested duet mode on two simulators (iPhone 18 Pro and iPhone 18 Pro Max) and it now works end to end. The two phones pair with a code, play all three rounds with each other's breathing waves live, and land on identical results ("Breathing as one", 92 overall). All 16 unit and integration tests pass, and everything is committed.
It didn't work at first, and fixing it changed the networking:
- Multipeer was replaced. Invitations never got from one simulator to the other. While debugging I also found Apple has deprecated Multipeer in iOS 27, so a new app shouldn't ship on it. The duet link now uses Network.framework: nearby discovery over peer-to-peer Wi-Fi, no router or internet needed …
[40 more tool calls omitted]
feel free to run on my iphone, my iPhone
$ {"command": "xcrun devicectl list devices 2>&1 | head -20", "description": "List connected …
[device list redacted]
Found my iPhone (iPhone 17 Pro). Building a device build and installing it:
$ {"command": "xcodebuild -project BreatheAsOne.xcodeproj -scheme BreatheAsOne -configuratio …
** BUILD SUCCEEDED **
$ {"command": "APP=build/dd-device/Build/Products/Debug-iphoneos/BreatheAsOne.app; /usr/libe …
key length in built app: 40
• databaseUUID: <devic …
$ [redacted: touches secrets]
[redacted]
Breathe as One is installed and open on my iPhone. I couldn't test anything on it yet, because the camera check needs you in front of the phone. I also fixed one gap first: the developer screen never asked for camera permission (only the game's consent screen did), so the measurement might not have started.
The camera check (about 3 of your 100 credits):
- Set up: prop the phone at chest height, about an arm's length away, with the front camera facing you. Make sure your face and chest are in frame, the light is even, there's no flickering TV behind you, and you're not wearing stripes.
- Open the developer screen: long-press the "Breathe as One" title for about 1.5 s to o …
why does it prtefiul tyhe name with Swell 56
"Swell 56" is a placeholder name the app makes up the first time it opens: a random calm word (Tide, Breeze, Drift, Ripple, Swell or Lull) plus a two-digit number.
It's there because your partner sees this name when you pair ("Swell 56, enter code"), and the app can't read your real name. Since iOS 16, apps can only see "iPhone" as the device name unless Apple grants a special entitlement. So instead of showing a blank or "iPhone", it picks a friendly random name. The number keeps two phones from showing up with the same name.
You can change it: tap the name on the welcome screen and type your own. It's saved, so it sticks after that. I see it doesn't look editable right now, though; it re …
pre-fill
Doing it now: a pencil icon on the name field, plus a one-time "What should your partner call you?" prompt with the random name pre-filled the first time you tap "Play with a friend".
$ {"command": "python3 - <<'EOF'\np='BreatheAsOne/UI/WelcomeView.swift'\ns=open(p).read()\nd …
** BUILD SUCCEEDED **
tapped Play with a friend at …
don't pre-fill
$ {"command": "cd ~/MobileDev/iOS/BreatheAsOne && python3 - <<'EOF'\ndef patch(p, pairs):\n …
** BUILD SUCCEEDED **
tapped Play with a friend at …
$ {"command": "cd ~/MobileDev/iOS/BreatheAsOne && xcodebuild test -project BreatheAsOne.xcod …
Executed 16 tests, with 0 failures (0 unexpected) …
re-run on my device
$ {"command": "xcodebuild -project BreatheAsOne.xcodeproj -scheme BreatheAsOne -configuratio …
** BUILD SUCCEEDED **
App installed:
Launched appl …
Your iPhone is running the latest build, relaunched fresh. The name works the way you asked:
- No made-up name anymore. The welcome screen shows an empty "Add your name" field with a pencil icon. If you'd already typed a name on the phone before, that one is kept.
- The first time you tap "Play with a friend" without a name, it asks "What should your partner call you?" with an empty field. Done stays greyed out until you type something.
- Practice with a ghost never asks.
All 16 tests pass and the change is committed.
The camera check from before is next whenever you're ready: long-press the title to open the Signal Lab, leave it on Camera, and tap Start. It stops by itself af …
[Image #4] the texts are not readable properly on top of the camera
maybe make the cam view smaller so that none of the texct info or button overlaps with it?
The camera preview isn't respecting its frame. A portrait camera image with "fill" scaling grows its container to the image's full size, so it covers the title, the close button and the status rows, and the breath wave sits on top of the hint text. I'll restructure the tune-in screen so nothing overlaps:
- A smaller, fixed camera card (about 190 pt tall) with the image clipped inside it, plus a faint face-and-shoulders outline to help with framing.
- Your breath wave gets its own strip below the camera instead of drawing over the video.
- The guidance hint gets its own row with an icon, never on top of anything.
{"file_path": "~/MobileDev/iOS/BreatheAsOne/BreatheAsOne/UI/TuneInView.swift", "content": …
The file ~/MobileDev/iOS/BreatheAsOne/BreatheAsOne …
$ {"command": "grep -n \"lab\\|preview\" BreatheAsOne/UI/SignalLab.swift | grep -n \"scaledT …
** BUILD SUCCEEDED **
<scratchpad>/t1-tunein.png
also seems like it's not able to identify my breadth
$ {"command": "python3 - <<'EOF'\ndef patch(p, pairs):\n s=open(p).read()\n for a,b in …
Executed 16 tests, with 0 failures (0 unexpected) …
I feel the full fledged cam view is kinda scary to present to the user, cann we hide or show a more obscured view?
$ {"command": "python3 - <<'EOF'\np='BreatheAsOne/UI/TuneInView.swift'\ns=open(p).read()\nde …
** BUILD SUCCEEDED **
App installed:
Launched appl …
I've rebuilt the app and installed it on your iPhone, but I haven't seen the blurred preview or the breathing fix on the real camera yet. That needs you in front of the phone. The layout checks out in the simulator and all 16 tests pass.
Camera view (both requests):
- It's now a smaller, fixed card. Nothing can spill over the title, buttons or status rows anymore.
- By default it shows a soft teal silhouette: heavily blurred and tinted, so you can see where you sit in the frame but not your face in detail.
- A dashed oval for your face and an arc for your shoulders show where to sit.
- An eye button in the corner shows the clear view for 4 seconds if you need it for framing, then it blu …
[Image #5] it should be portrait right and not landspace
Right. The phone stands upright, so a portrait card makes more sense. Switching the camera card to a 3:4 portrait shape, centered, with the outline proportioned to match. Good sign in your screenshot, by the way: the "Your breath" strip is showing a real wave from your chest.
$ {"command": "python3 - <<'EOF'\np='BreatheAsOne/UI/TuneInView.swift'\ns=open(p).read()\nde …
** BUILD SUCCEEDED **
<scratchpad>/t2-portrait.png
maintian cemra aspect ratio
$ {"command": "python3 - <<'EOF'\np='BreatheAsOne/UI/TuneInView.swift'\ns=open(p).read()\nde …
** BUILD SUCCEEDED **
$ {"command": "xcrun devicectl device install app --device <device-id> build/dd-device/Build …
App installed:
Launched application with com.<user …
I've reinstalled the app on your iPhone with an upright camera card that keeps the camera's own shape. I haven't seen it with the real camera, though. The simulator has no camera, so I only checked the layout with a fixed 3:4 card, and nothing overlaps.
- Portrait: the card is centred and 260 pt tall.
- Camera's own shape: the width now follows the shape of the camera frames, so the picture shouldn't be stretched or cropped. Until the first frame arrives it shows as 3:4, so the card may change width slightly once the camera starts. Let me know if that jump bothers you.
- Everything else is unchanged: the blurred teal silhouette by default, the eye button to peek at the clear vie …
nice works prety well
can we now create a nice demo video to shocase the project in the hackathon
use a nice female + male voice from eleven labs to create the short demo view
Great to hear it works. A demo video has a few moving parts, so first I'll check what we can reuse: an earlier launch-video pipeline and any existing ElevenLabs setup.
$ {"command": "cd ~/MobileDev && ls …
[listing of unrelated local folders redacted]
video
$ {"command": "cd ~/MobileDev/iOS && sed -n 1,60p <other-project>/tools/vo.py …
[redacted]
$ [redacted: touches secrets]
[redacted]
$ [redacted: touches secrets]
[redacted]
$ {"command": "sed -n 1,30p <other-project>/tools/compose_sim.py …
[redacted]
[2 more tool calls omitted]
let me know if you ned the elevenlabs ey
$ {"command": "D=$(find /Library/Developer/CoreSimulator /Applications/Xcode.app/Contents/De …
/Library/Developer/CoreSimulator/Profiles/DeviceTy …
$ {"command": "P=\"/Library/Developer/CoreSimulator/Profiles/DeviceTypes/iPhone 18 Pro.simde …
"chromeIdentifier" => "com.apple.dt.devicekit.chro …
$ {"command": "C=/Library/Developer/DeviceKit/Chrome/phone11.devicechrome; find $C | head -4 …
/Library/Developer/DeviceKit/Chrome/phone11.device …
$ {"command": "mkdir -p <scratchpad>/video/plates && cd <scratchpad>/video && cat > pdfinfo. …
PhoneComposite: size 436.0x908.0
Screen: size 1.0x …
Status:
Done so far
- Narration script: 12 lines, a female voice (Sarah) and a male voice (Eric) trading lines like a duet, ending with both saying "Breathe as One." It's ready to generate once your key is in.
- Music: a soft synthesized pad that swells at 6 breaths a minute (the in-game tide's pace) and opens from minor to major at the "two colors become one" moment. No licensing issues.
- Bezel: an authentic iPhone 18 Pro frame, rendered from Xcode's own Device Hub assets.
- Second simulator: I created a dedicated iPhone 18 Pro, "Breathe Duet B", so the two phones match side by side.
- Recording now (about 4 minutes): a full two-phone duet on the simulators, "Maya" …
[23 more tool calls omitted]
added elevenlabs key
Thanks. Generating the narration with your key now (about 700 characters, cached so re-runs never re-bill):
$ [redacted: touches secrets]
[redacted]
$ {"command": "cd <scratchpad>/video && mkdir -p draftframes && for t in 3 10 20 30 42 50 56 …
Shell cwd was reset to ~/MobileDev
$ {"command": "cd <scratchpad>/video/take1 && for t in 3.5 5 6.5 8 10 11.5; do ffmpeg -v err …
Shell cwd was reset to ~/MobileDev
$ {"command": "W=<scratchpad>/video; mkdir -p $W/intro; H=<device-id>; G=<device-id>; B=com. …
12.778333
12.775000
Updating the edit: the title uses the intro take, the timings fit the narration, the results shot is fixed, and the stand-in for your iPhone footage is honestly labelled as simulator.
The narration inputs get numbered twice as fast as they should (every -i path pair counts as two), so the audio mix points at streams that don't exist. Fixing the indexing:
The render worked (63.6 s). Only my frame sampler tripped by seeking too close to the end. Resampling, and checking loudness:
The first full draft of the demo video is ready (64 s, 1920×1080, 7.4 MB), with narration and music:
~/MobileDev/iOS/BreatheAsOne/build/demo/BreatheAsOne-demo-draft.mp4
It still needs your real iPhone footage before it can be submitted. Shot 2 is supposed to prove the live SmartSpectra signal, and right now a simulator clip stands in, labelled "Simulator" so it doesn't claim to be live.
What's in it
| Time | Shot | Narration |
|---|---|---|
| 0–7 s | Both phones on the welcome screen, title card | He: "Two phones." She: "Two people." He: "One breath." |
| 7–18 s | Live camera tune-in (simulator stand-in for now) | She explains the camera and SmartSpectra |
| 18–26 s | Maya type … |
[7 more tool calls omitted]
can you please simulate this:
- A real iPhone screen recording (required): Control Center → screen record, then Practice with a ghost, through tune-in and into Follow the Tide (about 1–2 minutes, roughly 4 credits). AirDrop it over. It drops into shot 2 with the green "Live on iPhone: real SmartSpectra signal" tag.
let
I won't fake that one, and here's why: shot 2 is the part judges use to check that SmartSpectra really works ("judges will verify SDK use by reviewing your public code repository and demo video"). Simulated footage under a "Live on iPhone: real SmartSpectra signal" tag would be a false claim and could get the entry disqualified.
There is an honest middle ground, though. Since the last few builds, test builds on your iPhone save every real-camera game session, so your earlier ghost games may already be stored on the phone as genuine SmartSpectra data. If so, I can pull them off (no credits used), replay them in the simulator to drive the real game screens, and label the shot truthfully: …
$ {"command": "D=<scratchpad>/device-recordings; mkdir -p $D; xcrun devicectl device info fi …
1 file:
Name URL Res …
let
let's skip iusing real people
$ {"command": "D=<scratchpad>/device-recordings; xcrun devicectl device copy from --device < …
File received from Device
<scratchpad>/device-reco …
$ {"command": "python3 -I - <scratchpad>/device-recordings/session-real.json <<'EOF'\nimport …
breathing-rate updates: 47
[(18.2, 23.4, 0), (21.2 …
I'm putting the bookkeeping in a small, unit-tested RateTracker. It keeps the latest reading until it goes stale, and converts the SDK's 0–100 confidence to 0–1.
$ {"command": "cat > BreatheAsOne/Signals/RateTracker.swift <<'EOF'\nimport Foundation\n\n// …
90: let pulse = Self.newer(metrics.cardio.a …
Name clash with the pulse-wave variable. Renaming, then adding tests that replay the real update pattern:
$ {"command": "python3 - <<'EOF'\np='BreatheAsOne/Signals/PresageSource.swift'\ns=open(p).re …
Executed 20 tests, with 0 failures (0 unexpected) …
Shot 2 now shows your real breathing wave against the tide, at 48 in sync. Your iPhone still has the build with the "warming up" bug, so I'm putting the fixed build on it too:
The updated demo video is ready (64 s, 1920×1080, no real people anywhere in it):
~/MobileDev/iOS/BreatheAsOne/build/demo/BreatheAsOne-demo-draft.mp4
What I did instead of faking the screen recording. I didn't mock up iPhone footage under the "Live on iPhone" tag, because judges use the demo video to confirm SmartSpectra really works, and a false label could get the entry thrown out. Instead:
- Test builds on your phone already save every real camera session, so I copied this morning's 3-minute ghost game off your iPhone. That's real SmartSpectra data: 5,325 breathing samples, 4,110 pulse samples.
- I replayed it as "you" in a simulator ghost game, so the real ap …
[7 more tool calls omitted]
open the recrodign dir in finder
$ {"command": "S=<scratchpad>; ls \"$S/device-recordings\" \"$S/video\" ~/MobileDev/iOS/Brea …
<scratchpad>/device-recordings:
real-iphone-sessio …
$ {"command": "S=<scratchpad>; open \"$S/video/take1\" \"$S/video/replay\" \"$S/device-recor …
(Bash completed with no output)
I opened three recording folders in Finder:
-
Simulator duet take (Maya and Leo, both screens plus
events.json):<scratchpad>/video/take1 -
Replay take (the simulator game played back from your real iPhone session):
<scratchpad>/video/replay -
Your real iPhone SmartSpectra session data (the raw file and the repaired one):
<scratchpad>/device-recordings
The finished demo video is in a separate folder: ~/MobileDev/iOS/BreatheAsOne/build/demo/BreatheAsOne-demo-draft.mp4. Earlier drafts (v2 to v5) are in the video folder one level up from the first two.
The first three folders are temporary session storage, so copy anything you want to keep.
open the final recording
$ {"command": "open ~/MobileDev/iOS/BreatheAsOne/build/demo/BreatheAsOne-demo-draft.mp4", "d …
(Bash completed with no output)
I opened the final demo video, so it should now be playing in your default video player. It's the 64-second v5 cut at ~/MobileDev/iOS/BreatheAsOne/build/demo/BreatheAsOne-demo-draft.mp4.
in the final ending od the video just have one speaker, the female voice
$ {"command": "cd ~/MobileDev/iOS/BreatheAsOne/tools/demo; grep -n \"title-he\\|title-she\\| …
25:XFADE = 0.45
26:PHONE_H = 940
27:BODY = (1308, …
$ {"command": "cd ~/MobileDev/iOS/BreatheAsOne; sed -n 1,24p tools/demo/compose.py; sed -n 2 …
#!/usr/bin/env python3
"""Compose the Breathe as O …
Remove the male "Breathe as One." from the outro and from the VO script, then re-render.
$ {"command": "sed -i '' 's/, (\"12-title-he\", 5.45)\\]/]/' tools/demo/compose.py && sed -i …
body="", tag="", vo=[("11-privacy", 0.3), ("12-tit …
$ {"command": "S=<scratchpad>/video\nffmpeg -v error -ss 56 -i \"$S/demo-v6.mp4\" -af volume …
1f95a0d Demo outro: female voice only for the clos …
[Image #6] for this section can we plese use the same tiny and maybe find a portrait face video from a site like unspalsh
I'll put a blurred, tinted stock portrait clip into the camera card. Unsplash only has photos, so I'll get a free-licence clip from Mixkit or Pexels. First I'll check how the preview is wired.
$ {"command": "grep -n \"preview\\|replayMe\\|mySourceForPreview\" -r BreatheAsOne | grep -v …
BreatheAsOne/UI/DuetCanvas.swift:13: /// Fracti …
$ {"command": "curl -sL -A \"Mozilla/5.0\" \"https://mixkit.co/free-stock-video/meditation/\ …
Permission for this action was denied by the Claud …
$ {"command": "sed -n 100,115p BreatheAsOne/Game/GameModel.swift; sed -n 180,205p BreatheAsO …
case .countdown: max(0, GameStage.countdownLength …
{"query": "pexels free stock video vertical woman sitting calm breathing looking at camera …
Web search results for query: "pexels free stock v …
approved bniw
$ {"command": "curl -sL -A \"Mozilla/5.0\" \"https://mixkit.co/free-stock-video/meditation/\ …
https://assets.mixkit.co/videos/4800/4800-1080.mp4 …
The search returned clip links but no titles. I'll try downloading the small 360p previews into their own folder so I can look at them and pick one.
$ {"command": "D=<scratchpad>/stock-dl; mkdir -p $D && cd $D && for id in 4800 15017 4397 48 …
total 52808
drwxr-xr-x@ 25 <user> wheel 800 …
Make a contact sheet of a middle frame from each.
$ {"command": "D=<scratchpad>/stock-dl; O=<scratchpad>/stock-frames; mkdir -p $O; for f in $ …
15017 640,360 16s
15714 640,360 13s
15715 640,360 …
$ {"command": "D=<scratchpad>/stock-dl; cd $D && curl -sfL -o 15715-1080.mp4 https://assets. …
Exit code 56
Now prepare the clip (portrait 3:4, played forward then backward so the loop has no jump) and build.
The build succeeded. Next I'll look up how I recorded the replay take earlier so I can reproduce it with the clip.
I'm recording the replay take again with the stock clip in the camera card. After that I'll re-cut the video.
The app now draws the same blurred teal duotone over the stock clip. Next I'll check that the cut points in the new take still land on the right moments.
The tune-in scene now shows the blurred teal face and switches from "Reading breath" to Ready on screen. The tide scene ends on a 0 score, so I'll start it 3 s earlier to finish on the stronger part.
Both changes are done, and the updated video is open: ~/MobileDev/iOS/BreatheAsOne/build/demo/BreatheAsOne-demo-draft.mp4 (63.6 s).
Ending: only the female voice says "Breathe as One." now. I also took the male version of that line out of the narration script so it won't come back if the voices are regenerated.
Tune-in camera card: it now shows a blurred portrait with the same teal tint as the real camera preview, plus the face oval and shoulder guide. The app draws it, not the video edit, so it matches the app exactly.
- Clip: Unsplash only has photos, so I used a free Mixkit video (clip 15715) of a man sitting still facing the camera, with face and chest in view. It's cropp …
[19 more tool calls omitted]
aewosmer the video looks good!
let's compelte the submissiomn template nopw
Gathering the details the post has to state accurately: consent copy, SDK config, and what gets stored. Then drafting the DEV post.
[5 tool calls omitted]










Top comments (0)