Skip to content

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.

  1. 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.]
  2. 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.]
  3. 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 our DcHapticsImpl.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.]
  4. 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).

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 → levelup all 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 stats starts 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 wake for 35 s of run time, then telemetry stops at 04:22:47 with no background event 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=tutorial on current main runs every lesson, including stats, to tutorial_complete with lessons: 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 metrickit event 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’s applicationExitMetrics), 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).

  1. 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). stopAll no longer stops players at all once stoppedHandler/resetHandler has cleared running (they are dead with their engine). A catch logs one warning per call site and marks the engine for a lazy restart. build.sh re-stamps from the source hash, so build-ios.sh / install-dev.sh rebuild the plugin automatically. Under ARC a caught exception can leak the objects in flight. That is acceptable against an abort, and -fobjc-arc-exceptions was not added. Verified only by compiling: build.sh compiled and linked the iOS debug framework, and clang++ -fsyntax-only -Wall is clean for iOS and macOS. Not device-verified.
  2. 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).
  • Mac channel label: a Mac build with no Contents/_MASReceipt/receipt is not an App Store or TestFlight install. Classify it as something other than appstore (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 on qol-skip-tutorial softens 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 launch to the first run_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.
  1. 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.
  2. Haptics fix on a device, after crash-first-play is 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 = no metrickit diagnostic with CoreHaptics in it after the next two launches:
    SELECT build, datetime(ts/1000,'unixepoch') FROM events WHERE type='metrickit'
    AND dev='iOS/iPhone16,2' AND instr(data,'CoreHaptics')>0 AND build>=326;
    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).
  3. 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.launch
    AND x.type IN ('run_start','menu')) - l.ts AS boot_ms FROM events l
    WHERE l.type='launch' AND json_extract(l.data,'$.first_launch')=1 AND l.build>=326;