@@ -49,7 +49,8 @@ calls three `from_swift` entry points — `SetAppActive(bool)`, `SuspendApp()`,
4949- ** running** (` sceneWillEnterForeground ` / ` sceneDidEnterBackground ` , the
5050 analogue of Android onStart/onStop) → ` UnsuspendApp() ` / ` SuspendApp() ` .
5151 ` SuspendApp ` parks all event-loop threads; the audio thread's suspend
52- callback calls ` alcDevicePauseSOFT ` , which is what actually stops audio.
52+ callback fades master volume out and then calls ` alcDevicePauseSOFT ` ,
53+ which is what actually stops audio (see "Audio around suspend" below).
5354 The ` CADisplayLink ` is also paused
5455 (` UIKitGLViewController.setRenderingPaused ` ) so no GPU work happens in the
5556 background.
@@ -71,6 +72,34 @@ boot; the iOS shell avoids that.)
7172
7273Files: ` app_platform/apple/{from_swift.h,from_swift.cc,UIKitSupport.swift,UIKitSceneDelegate.swift,UIKitGLViewController.swift} ` .
7374
75+ ## Audio around suspend
76+
77+ ` alcDevicePauseSOFT ` is a hard stop: it stops the CoreAudio unit
78+ immediately, so before 2026-08-22 backgrounding cut whatever was playing
79+ off mid-waveform at full amplitude. On iOS that lands right as the OS is
80+ tearing down our audio session, and the result sounded like a moment of
81+ corrupted audio rather than a clean stop.
82+
83+ ` AudioServer::FadeMasterGainForSuspend_ ` therefore ramps OpenAL's master
84+ (listener) gain to silence over 120ms and lets the mixer run on that
85+ silence for another 60ms * before* pausing the device, then ramps back up
86+ after unsuspending. It early-outs when nothing audible is playing, so it
87+ only costs time when it buys something. Budget-wise this is fine: a
88+ measured suspend with music playing completes in ~ 211ms against
89+ ` SuspendApp ` 's 4s cap (and Apple's ~ 5s background deadline).
90+
91+ Two related facts worth knowing before touching this:
92+
93+ - ** Nobody in the process owns the ` AVAudioSession ` ** — not our code, and
94+ not the OpenAL Soft xcframework (it links AudioToolbox/CoreAudio, no
95+ AVFAudio). So we run on the implicit default category, never explicitly
96+ activated, with no interruption handler, and the system's session
97+ teardown races our own ` AudioOutputUnitStop ` . The fade hides the
98+ symptom; owning the session would remove the race. See ` followups.md ` .
99+ - ** Listener gain is otherwise unused** by the engine (only
100+ position/velocity/orientation are set), which is why it's available as
101+ a master knob here.
102+
74103## Input
75104
76105- ** Touch:** ` UIKitGLViewController ` overrides
@@ -181,6 +210,56 @@ platform-specific pieces worth knowing:
181210 --predicate 'subsystem == "net.froemling.ballistica"'` (or ` log stream`).
182211 Note ` simctl launch --console[-pty] ` does * not* capture the sim app's
183212 stderr — os_log is the reliable channel.
213+ - ** ` LowLevelDebugLog ` goes nowhere on Apple.** ` HandleLowLevelDebugLog `
214+ is only overridden on Android (crash-log breadcrumbs); the base impl in
215+ ` platform.cc ` is empty. So calls like the `"Calling alcDevicePauseSOFT
216+ at ..."` breadcrumbs in ` audio_server.cc` are invisible here — don't
217+ plan an iOS investigation around grepping for one. Use a real logger.
218+ - ** Exercising background/foreground in the Simulator** : background the
219+ app by launching another one (`xcrun simctl launch booted
220+ com.apple.Preferences` ), then foreground it with ` xcrun simctl launch
221+ booted <our-bundle-id >` — that resumes the existing process rather than
222+ relaunching (same pid back), so it exercises the real
223+ suspend/unsuspend path. Pair with ` --log 'ba=DEBUG' ` to get
224+ ` SuspendApp() completed in Nms. ` / ` UnsuspendApp() completed in Nms. ` ,
225+ which are logged on ` LogName::kBa ` at DEBUG in debug builds only.
226+ - ** ` make ios ` / ` make tvos ` device pick** (impl: lifecycle in
227+ ` tools/batools/iossim.py ` ; pcommands ` ios_sim_run ` / ` ios_sim_log ` in
228+ ` tools/batools/pcommands4.py ` ). Order, no config needed:
229+ ` IOS_SIM_DEVICE ` override (name or udid) → reuse an already-booted
230+ device → the Simulator app's ` CurrentDeviceUDID ` (`defaults read
231+ com.apple.iphonesimulator`) → newest available, booted on demand. The
232+ ` CurrentDeviceUDID ` step matters: ` open -a Simulator ` auto-boots the
233+ app's last-used device, so a disagreeing pick yields TWO sim windows.
234+ Booting is async — always ` simctl bootstatus -b ` before install, and
235+ boot BEFORE opening Simulator.app (opening first races the auto-boot
236+ → "Unable to boot device in current state: Booted").
237+
238+ ### Testing the software keyboard in the Simulator
239+
240+ Verifying anything keyboard-related needs setup the sim fights you on
241+ (cost ~ 20 min on 2026-08-19):
242+
243+ - The sim ** suppresses the software keyboard by default** (hardware
244+ keyboard connected), so keyboard-layout bugs simply cannot appear.
245+ `defaults write com.apple.iphonesimulator ConnectHardwareKeyboard
246+ -bool false` is **blocked by the unsandboxed-Bash hook**, and ` simctl
247+ ui` has no equivalent — so ask Eric to hit ⌘⇧K (I/O ▸ Keyboard ▸
248+ Connect Hardware Keyboard) once the sim is up.
249+ - iOS then shows a one-time ** swipe-typing intro** covering the keyboard;
250+ `xcrun simctl spawn booted defaults write
251+ com.apple.keyboard.ContinuousPath DidShowContinuousPathIntroduction
252+ -bool true` clears it (works sandboxed — it writes inside the sim, not
253+ host prefs).
254+ - ` automation_drive --screenshot ` captures ** only the GL framebuffer** ,
255+ so UIKit overlays (the string editor, the keyboard) are invisible in
256+ it. Use ` xcrun simctl io booted screenshot <path> ` instead — note it
257+ captures portrait-rotated for a landscape app.
258+ - Android counterpart: the emulator reports ` keysexposed-qwerty `
259+ (` adb shell am get-config ` ), so any code branching on "is a hardware
260+ keyboard attached" always takes the hardware path there and cannot be
261+ tested; Gboard's ** stylus-handwriting onboarding** blocks the keyboard
262+ until ` adb shell settings put secure stylus_handwriting_enabled 0 ` .
184263
185264### Simulator build/launch recipe (verified 2026-06-18)
186265
0 commit comments