# GATE 2 analysis — the title-screen → vault stall (SvT offline bring-up)

Status as of this session: boot crashes solved, loadout NRE fixed, **title screen renders**, GATE 1
(`SplashHide`) understood + force-fixable. GATE 2 (title → vault) root-caused more precisely below —
and it **corrects a wrong turn** in the earlier overseer notes.

## Correction to the earlier diagnosis
The overseer concluded GATE 2 was "`Director.OnEvent(TitlesCompleted)` never calls
`Director.OnTitlesCompleted`, and forcing `OnTitlesCompleted` has no effect." Disassembly shows why the
force had no effect: **`Director.OnTitlesCompleted` (RVA 0xFC9BAD) is a 3-instruction no-op setter** —
`mov eax,[esp+4]; mov byte[eax+0x84],1; ret` (i.e. `this._titlesHidden = true; return`). It is NOT the
scene-transition trigger, so forcing it was chasing a no-op (and `_titlesHidden` is already set by the
natural `OnEvent(TitlesCompleted=2)` dispatch handler at 0xFADA7C, which does the same single store).

## The real post-titles flow (FE.Director)
Driven by the boot coroutine `Director.<Start>c__Iterator0.MoveNext` (RVA **0xFD1C2A**). The real
transition methods (none of which fire while stuck):
- `UnloadTitleScreen(GameObject)` 0xFCA94D
- `InitiateMatchmaking()` 0xFCA136
- `RequestChangeFeState(FlowState, cb)` 0xFC9BB9  ← the actual scene/front-end state change
- `FadeAndLoadLevel()` (IEnumerator) 0xFCA9D4 · `PreloadAll(bool)` 0xFCAA66 · `DoPreload(string)` 0xFCC6B8
- vault screen: `EnterVault : UiScreen` (TypeDefIndex 4355), coroutine `EnterVault.<Start>c__Iterator0`

## Runtime trace (diag_gate2_pc.py, light $PC probe — safe, no per-frame work)
Boot coroutine `$PC` progression and its wait-flags (fields on `<Start>c__Iterator0`:
`<acceptedTOS>`@0x14, `<sync>`@0x15, `<deviceLimit>`@0x24, `<bootInto>`(FlowState)@0xC, `$PC`@0x3C):
```
$PC=0   TOS=0 sync=0
$PC=1   TOS=0 sync=0
  OnEvent what=72   (StartWaitingForTitlesProblems = the game's own "you seem stuck?" detector)
$PC=8   TOS=1 sync=1     <- TOS accepted AND gu3 sync complete (loadout fix working)
$PC=12  TOS=1 sync=1
  OnEvent what=56  (SplashVisible) -> [we force SplashHide=57] -> OnEvent what=2 (TitlesCompleted)
$PC stays 12 forever; UnloadTitleScreen / InitiateMatchmaking / RequestChangeFeState never fire.
```
**GATE 2 = the `$PC=12` yield in `Director.<Start>c__Iterator0.MoveNext` never completes.** It sits past
TOS + gu3-sync, so it is NOT waiting on our backend. Forcing the title events does not release it, so it
is waiting on more than `_titlesHidden` — the prime suspect is the `<coDependencies>` sub-coroutine
(`LoadDependencies` / `PreloadAll` / `DoPreload`) never finishing offline (a scene/asset preload that
can't complete without the real CDN), i.e. `yield return coDependencies` never returns.

## Why the frida force-invoke path is a dead end here
- `OnTitlesCompleted` is a no-op (above).
- Per-frame `Director.Update` hooking + forcing crashed the app/emulator (twice, independently — overseer
  and this session). Forcing coroutine-starting methods out of Unity's dispatch context is emulator-fatal
  suspect. Do not spam it.

## BREAKTHROUGH UPDATE — reached the vault scene (renders "ACCOUNT DISABLED")
The `$PC` state machine + gates are fully mapped and driven. The full boot chain to the vault:
1. Boot crashes fixed; loadout `UpdateRifle` NRE fixed by clearing app data (a stale CACHED profile —
   not gu3's send — LinkInkventory'd before gadgets were owned; `pm clear se.foglo.svt` gives a clean slate).
2. `Director.<Start>c__Iterator0.MoveNext` climbs `$PC 0→1→8→12`. **`$PC=12` gate = `Core.ready`** (bool
   @Core+0xA0). Forcing `[core+0xA0]=1` from the main-thread MoveNext hook (safe plain write) releases it.
3. `$PC 16→17`, then **`$PC=18` gate = `PlayerProfile.instance != null && PlayerProfile.get_initialized()`**
   (get_initialized RVA 0xd870b2; get_instance 0xd8766b). Forcing get_initialized→true releases it.
4. `$PC 19→20`, then `UnloadTitleScreen` (0xFCA94D) + `RequestChangeFeState` (0xFC9BB9, FlowState) FIRE,
   and `DoPreload scene="enter_vault"` (0xFCC6B8) loads the vault scene. **THE VAULT SCENE RENDERS.**
5. It renders the game's **"ACCOUNT DISABLED"** screen (user_disabled flow) because `profile.enabled` reads
   0/default: we force `PlayerProfile.initialized=true` at ~15s, BEFORE gu3's profile (with `enabled:1`)
   is loaded (~13-25s), so the vault checks a blank profile.

## THE FINAL GATE
Load the real profile (`enabled:1`) so the vault renders the actual menu instead of ACCOUNT DISABLED.
Two ways: (a) make `PlayerProfile.initialized` set NATURALLY after gu3's profile lands (it currently does
NOT set even without the NRE — needs `PlayerProfile.set_initialized` hooked to see why PlayerProfile.Start
doesn't complete despite receiving user data/clan/clan data/profile); or (b) time the forced
get_initialized→true to fire only AFTER the gu3 profile is processed (call-count and a naive wall-clock
`recv` signal both proved fiddly — the profile-loaded moment needs a real trigger, e.g. hook the "profile"
section OnSectionChange completing). Everything up to the vault UI is solved; this last flag-timing is it.

## Concrete next step (not yet done)
Disassemble the `$PC==12` case of `MoveNext` (0xFD1C2A) to read its exact yield condition. If it is
`yield return coDependencies` (waiting on `LoadDependencies`), the fix is to make that sub-coroutine
complete — most likely feed the preload/asset requests a local response (the same "mock the endpoints"
idea), OR find and satisfy the specific flag the preload sets on completion. This is the clean,
non-crashing path forward.

## Method/field RVA reference (this build, /tmp/dev_il2cpp.so md5 b28e23f9)
Director.OnEvent 0xFAD92E (TitlesCompleted case handler 0xFADA7C) · Director.Update 0xFC5972 ·
OnTitlesCompleted 0xFC9BAD (no-op setter, `_titlesHidden`@+0x84) · Start-coroutine MoveNext 0xFD1C2A ·
TitleScreen.OnNoteableEvent 0xDCFF4D · TitleScreen.WarmUp 0xDD0014 · Noteable: SplashVisible=56,
SplashHide=57, TitlesCompleted=2, StartWaitingForTitlesProblems=72.
