First-play crashes: what ended the tutorial-window sessions (2026-10-14)
First-play crashes: what ended the tutorial-window sessions
Section titled “First-play crashes: what ended the tutorial-window sessions”Overnight swarm item W1 (2026-10-14). Question from docs/RELEASE-1.1.md §3a: “4/9 real first plays
ended in a crash/OS kill (Mac at 16 ms, iPad Air M2 at 32.6 s on 255). Does the 1.1 candidate still
do it?” Method: read-only D1 queries of events (launch, prev_exit, tutorial_step, run_*,
metrickit), samples and hitches, then symbolication of the MetricKit crash stacks against the
binaries still on disk. Branch crash-first-play.
Verdict
Section titled “Verdict”- The “4/9 crashed” figure is wrong. Of the 11
channel='appstore'installs, only one session on a real store player is a genuine unexplained foreground death: the iPad Air M2 (iPad15,3, BUILD 255). The others are a local smoke test, review/Apple devices, a normal swipe-kill, or Chris’s own phone (table below). [Certain for the Mac and the background kills; Likely for which installs are Apple/App Review devices.] - The iPad death has no identifiable mechanism in telemetry. No hitch, a steady 60 fps up to the last sample, and no MetricKit crash report ever uploaded. It is not a shader stall and not a SCRIPT ERROR path (the tutorial sim runs clean through lesson 4 headless). It needs the device-side crash log (Xcode Organizer), see “For Chris” below. [Certain that telemetry can’t say more.]
- The 1.1 lineage has a real crash 255 did not have: CoreHaptics. Two MetricKit crash reports on
Chris’s Dev iPhone (BUILD 264 and 307) abort inside
-[CHHapticPatternPlayer stopAtTime:error:], called from ourDcHapticsImpl.stopAll. CoreHaptics raised an NSException, and nothing caught it. Haptics landed on 10-05/06, after store 255, so this is new in 1.1 and can hit any iPhone player, including in the tutorial (main.gd stops haptics on every background/focus-out). Fixed on this branch (exception guards), compile-checked only. A device check is still needed. [Certain about where it crashes; Likely about why: the engine had already been stopped by iOS.] - Two smaller findings: a teardown crash on app termination (
applicationWillTerminate→Main::cleanup→RenderingDeviceDriverMetal::swap_chain_free, BUILD 315 + 322, Godot fork code; harmless to the player, who is already killing the app), and a MetricKit pickup race that can leave a crash report on the device forever (fixed on this branch, GUT-tested).
The 11 “appstore” installs
Section titled “The 11 “appstore” installs”macOS non-dev exports always report appstore: BuildChannel._find_receipts only probes on
iOS/tvOS. So any local store-config Mac export lands in the store bucket.
| Install | Device / build | What ended it | Real player? |
|---|---|---|---|
| 3244bc | Mac17,2 / 258 | prev_exit crash, state modal, run_time 0.017 s, relaunched 5 s after the last marker |
No. Chris’s own M5 Mac at 19:59 UTC 10-04, 6 min before commit bb96384b (“Smoke-tested: unsigned/ad-hoc sandboxed export boots”). The smoke test killed the process. Mac 258 never shipped. |
| 6489ff | Mac17,2 / 258 | No further launch | No, the same smoke test (fresh sandbox container, 3 min later). |
| 891d0c | Mac17,2 / 259 | 7.8k events overnight | No, Chris (Mac review-candidate build). |
| fc9f19 | iPad15,3 / 255 | prev_exit crash, state modal, lesson stats step 0 |
Yes. See below. |
| 9696a1 | iPad8,6 / 259 | killed_in_background at 15.4 s |
Review-era iPad Pro on a review build. A normal swipe-kill, not a crash. |
| b5eaa6 | iPad8,5 / 255 | Quit the tutorial at 0.3 s, played Survival fine | Likely App Review (iPad Pro 2018, day of approval). |
| cde74d | iPhone99,7 / 255 | Sat in lesson wake 86 s, quit to menu, restarted the tutorial |
Not a retail model id: an Apple-internal device. |
| 323190 | iPhone16,1 / 255 | 225 s in wake with no input, then nothing |
Unknown. Idle device, no crash. |
| a8d9f4 | iPhone16,2 / 255→300 | Background kills | Chris’s own phone (MetricKit regionFormat GB, banked 4170 gold). |
| 91f78c / b020f2 | iPhone11,2 / 255 | Finished the tutorial, died in Survival at 50 s | Yes. No crash. |
The iPad Air M2 death (BUILD 255, 2026-10-14 04:21 UTC)
Section titled “The iPad Air M2 death (BUILD 255, 2026-10-14 04:21 UTC)”Timeline (UTC, launch 1a5cb254):
- 04:20:39.8 first launch → 04:20:40.1 forced tutorial. A 1.32 s first-use stall at the opening dialogue (11 pipelines, the 255-era weapon-look shaders). Normal for 255.
- Lessons
wake→blaster→levelupall completed. 60 fps throughout, max frame ≤ 48 ms apart from two 0.5 s first-use stalls at the first Blaster shot. - 04:21:46.8 lesson
statsstarts and its Echo intro dialogue opens (sim frozen:state = modal, run_time 32.5 → 32.6). - 04:21:51.7 last sample: 60 fps, max_frame 20 ms, no hitch. The exit marker was written at 04:21:51
with
backgrounded = false. - 04:21:56.8 relaunch.
prev_exit reason = crash. The process died in the foreground between ~04:21:52 and ~04:21:55, and the player relaunched within seconds. - The relaunch restarted the tutorial from lesson 0 (progress isn’t saved mid-tutorial). The player
sat in
wakefor 35 s of run time, then telemetry stops at 04:22:47 with nobackgroundevent and no later launch. That is consistent with quitting (the last batch never flushed), but a second death can’t be ruled out.
What it was not:
- Not a stall/watchdog: frames were 16.7 ms right up to the last sample, and no hitch row exists in the final 40 s.
- Not a sim error:
story_trace.gd --mode=tutorialon current main runs every lesson, includingstats, totutorial_completewithlessons: ok. - Probably not a crash with a report: the relaunch ran ~50 s. 9 of the 10 Dev MetricKit diagnostics
were on disk by the time of the boot scan and went out with the launch batch. No
metrickitevent came from this iPad. Either iOS killed it without a crash report (a jetsam / memory limit kill, which MetricKit only reports in the next day’sapplicationExitMetrics), or the report landed after the boot scan (the race fixed below). [Guessing between those two.] - Not haptics: 255 has no CoreHaptics code, and iPads report
supportsHaptics = false.
Is it still in the 1.1 candidate? Unknown. No fresh iPad installs exist on builds ≥ 310. Lesson
stats itself survived on iPhone XS (255), Chris’s iPhone and the ATV across many Dev builds.
Crash reports on the current lineage (MetricKit, Dev iPhone16,2)
Section titled “Crash reports on the current lineage (MetricKit, Dev iPhone16,2)”| Report (build) | Signal | Symbolicated cause | Status |
|---|---|---|---|
| 264, 307 | SIGABRT (uncaught ObjC exception) | libdc_haptics DcHapticsImpl.stopAll (offset 0x4c8c, inside the stopAtTime:error: loop) → CoreHaptics raises. 264 came from a FrontBoard/UIKit scene event (resign-active → main.gd _notification → haptics.output.stop_all()), 307 from a display-link frame (HapticsDirector.update pause edge → stop_all) |
Fixed here (needs a device check) |
| 289 (×2) | SIGSEGV at 0x20, deep GDScript recursion | Crashed at launch on 289 only; 290+ never repeated it | Gone |
| 315, 322 | SIGSEGV in objc_msgSend | -[GDTAppDelegate applicationWillTerminate:] → Main::cleanup → ~DisplayServerAppleEmbedded → RenderingDevice::screen_free → RenderingDeviceDriverMetal::swap_chain_free (322 binary FA3EAA7E symbolicated with its dSYM) |
Open, fork-level, low impact. It fires ~2 s after a background (the app-switcher kill), so prev_exit reads killed_in_background and the crash is invisible there. Players never see it, but it counts in Organizer. |
The haptics symbolication: libdc_haptics.ios.release UUID DB0F29F3 (every local build of the current
source) matches the crash reports. Offset 0x4c8c sits in the method whose selector is stopAll, right
after the bl that sends stopAtTime:error: with d0 = 0 (CHHapticTimeImmediate).
Fixes on this branch
Section titled “Fixes on this branch”platform/tvos/addons/dc_haptics/src/dc_haptics.mm: every CoreHaptics call is wrapped in@try/@catch(engine init, start, pattern init, player create/start, player stop, engine stop).stopAllno longer stops players at all oncestoppedHandler/resetHandlerhas clearedrunning(they are dead with their engine). A catch logs one warning per call site and marks the engine for a lazy restart.build.shre-stamps from the source hash, sobuild-ios.sh/install-dev.shrebuild the plugin automatically. Under ARC a caught exception can leak the objects in flight. That is acceptable against an abort, and-fobjc-arc-exceptionswas not added. Verified only by compiling:build.shcompiled and linked the iOS debug framework, andclang++ -fsyntax-only -Wallis clean for iOS and macOS. Not device-verified.net/metrickit_pickup.gd: the watcher rescans 5 s and 30 s after boot, then stops processing. Evidence: the BUILD 315 crash report was written 0.4 s after the launch event, missed the deferred boot scan, and went out only on a foreground 304 s later. A player who crashes, relaunches once and quits never foregrounds again, so their report never leaves the device. GUT:tests/test_metrickit_pickup.gd(late payload sent by the rescan, rescans stop, dev sessions still leave files alone).
Not fixed (recommendations)
Section titled “Not fixed (recommendations)”- Mac channel label: a Mac build with no
Contents/_MASReceipt/receiptis not an App Store or TestFlight install. Classify it as something other thanappstore(remote-config channel routing depends on this value, so this is Chris’s call, not an overnight change). - Tutorial progress after a crash: the iPad player lost three lessons and had to restart from
wake. The Skip Tutorial work onqol-skip-tutorialsoftens this. Resuming at the last lesson is a 1.2 candidate. - Teardown crash in
swap_chain_free: report to / patch in the godot-tvos fork after 1.1. - Fresh-install boot cover on the 1.1 lineage: iPhone first launches on BUILD 319 took 23.6 s and
24.9 s from
launchto the firstrun_start(the boot PSO prewarm on an empty Metal cache). On BUILD 310-312 it was ~4.8 s, and on store 255 it was 0.3 s. Already §1 “fresh-install shader-stall check”. A 25 s cover on a first play is a real quit risk.
For Chris (morning)
Section titled “For Chris (morning)”- Get the iPad crash log, if Apple has one: Xcode → Window → Organizer → Crashes → Dark Cosmos → 1.0.1 (255), iPadOS, around 2026-10-14 04:21 UTC (iPad15,3). It only exists if the player shares analytics. If it shows a crash, the stack decides the next step. No entry fits a jetsam kill.
- Haptics fix on a device, after
crash-first-playis reviewed and merged: Dev install on the iPhone (bh-deploy/install-dev.sh iphone; check that the build log shows dc_haptics rebuilt, not “up to date”). Then ten times: start a run, trigger haptics (warp, take a hit), and within a second swipe to the app switcher and back. Also: idle on the level-up panel 30 s after a haptic, then pick. Pass = nometrickitdiagnostic withCoreHapticsin it after the next two launches:The old crash rate was 2 in ~10 days, so a clean session is weak evidence. The symbolicated stack is the stronger argument. Haptics must still play after returning from the background (the lazy restart path).SELECT build, datetime(ts/1000,'unixepoch') FROM events WHERE type='metrickit'AND dev='iOS/iPhone16,2' AND instr(data,'CoreHaptics')>0 AND build>=326; - Fresh-install stall check (skill
bh-fresh-install-stall-check) on the iPhone and ATV with the 1.1 candidate. It wipes the Dev save. Look at boot time as well as hitches:SELECT l.dev, l.build, (SELECT MIN(x.ts) FROM events x WHERE x.launch=l.launchAND x.type IN ('run_start','menu')) - l.ts AS boot_ms FROM events lWHERE l.type='launch' AND json_extract(l.data,'$.first_launch')=1 AND l.build>=326;