Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
1e54484
Add TFTMAC 1920x1080 splash screen asset
flashls1 Sep 4, 2026
9bf67c5
Plan TFTMAC production startup curtain
flashls1 Sep 4, 2026
c050c98
feat: add DEV startup splash curtain and protect Control
flashls1 Sep 4, 2026
8c65320
plan: fix DEV login friction and session persistence
flashls1 Sep 4, 2026
c4e72f6
Checkpoint DEV login isolation and 6GB HighPerf target state
flashls1 Sep 5, 2026
30c5009
Remove the Android PIN requirement from DEV startup
flashls1 Sep 5, 2026
56e5d6c
Apply verified DEV HighPerf profile and repair Riot login renderer
flashls1 Sep 5, 2026
d582dfc
Preserve original device compatibility when applying DEV performance …
flashls1 Sep 5, 2026
7f233fe
Correct DEV frame-history gaps and preserve genuine hitch measurements
flashls1 Sep 5, 2026
c26b020
Preserve short frame bursts in the DEV presenter
flashls1 Sep 5, 2026
73dd2b3
Checkpoint DEV launch recovery and guarded graphics diagnostics
flashls1 Sep 6, 2026
7f32ddb
Use WebView CDP for DEV saved Riot sign-in
flashls1 Sep 8, 2026
4a6140f
Make DEV saved Riot sign-in automatic locally
flashls1 Sep 8, 2026
0e4775a
Record two recent TFT capture reviews
flashls1 Sep 8, 2026
c213f50
Add evidence-gated fast wins 60fps plan
flashls1 Sep 8, 2026
35be5fa
docs: record ANGLE reuse gate failure
flashls1 Sep 8, 2026
1af7bc7
docs: record stock DEV lifecycle comparator
flashls1 Sep 8, 2026
ab3e0e2
tftmac: unblock candidate launch diagnostics
flashls1 Sep 8, 2026
8abea2e
docs: seal fast-win candidate receipt
flashls1 Sep 8, 2026
8f2ee84
docs: record ANGLE reuse real match result
flashls1 Sep 8, 2026
aa70050
docs: seal real match hashes
flashls1 Sep 8, 2026
a03b39a
tftmac: move evidence finalization off runtime actor
flashls1 Sep 8, 2026
f540beb
docs: seal shutdown test hashes
flashls1 Sep 8, 2026
53d97c0
docs: record autonomous Tocker trials run
flashls1 Sep 8, 2026
ec735e0
test: add autonomous strict 60fps campaign
flashls1 Sep 8, 2026
225d613
tftmac: add autonomous silent launch policy
flashls1 Sep 9, 2026
706f5ba
tftmac: propagate opt-in causal recorder to host
flashls1 Sep 9, 2026
c9a4d84
docs: record vcpu6 screening evidence
flashls1 Sep 9, 2026
528eaa8
docs: record instrumented tft trace blocker
flashls1 Sep 9, 2026
eb524b5
trace: parse angle timeline sideband
flashls1 Sep 9, 2026
31b9aac
trace: record ANGLE sideband admission result
flashls1 Sep 9, 2026
ba6b600
docs: record direct Vulkan marker admission result
flashls1 Sep 9, 2026
4e8f9f4
trace: record sync submit admission result
flashls1 Sep 9, 2026
9d68324
trace: record async submit admission result
flashls1 Sep 9, 2026
32043d4
trace: record backend load and submit2 admission results
flashls1 Sep 9, 2026
2aa6035
trace: admit actual GLES2 decoder stream
flashls1 Sep 9, 2026
50f41bf
trace: record GLES2 decoder durations
flashls1 Sep 9, 2026
1a87086
trace host EGL swap admission
flashls1 Sep 9, 2026
62a0435
trace render control color buffer flush
flashls1 Sep 9, 2026
e3c04b4
link render control frame markers
flashls1 Sep 9, 2026
5186b61
trace host color buffer blits
flashls1 Sep 9, 2026
114af98
classify hidden display post path
flashls1 Sep 9, 2026
2f1ea34
docs: record traced graphics boundary and admission blocker
flashls1 Sep 9, 2026
db09b24
checkpoint overnight optimization lab plan authority
flashls1 Sep 10, 2026
3b70429
Checkpoint overnight optimization lab implementation
flashls1 Sep 10, 2026
6490b10
Re-anchor TFTMAC optimization execution to original plan
flashls1 Sep 10, 2026
d9895d0
Repair TFTMAC control launch and resume semantics
flashls1 Sep 10, 2026
903c72b
Constrain TFTMAC optimization to results-first one-at-a-time testing
flashls1 Sep 10, 2026
f936332
Tighten TFTMAC candidate blocker adjustment rule
flashls1 Sep 10, 2026
7a9f0e1
Record verified no-global-sync frame-pacing win
flashls1 Sep 10, 2026
66f704a
Add running DEV optimization test ledger
flashls1 Sep 11, 2026
06bf7a0
Record standing DEV versioning policy
flashls1 Sep 11, 2026
848bfd9
Make TFTMAC DEV record books current and mandatory
flashls1 Sep 11, 2026
9674294
Reconcile OvernightLab with current DEV authority
flashls1 Sep 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
# TFTMAC DEV Login-Friction Repair — Implementation Plan

**Change:** `bddd2d6c-a2ed-46fb-9e9e-6674bfdf3541`
**Base:** `c050c98ec9bd93501c2ce5e0f8583129946b61ab`
**Request class:** IMPLEMENT_SHIP
**Target:** `/Applications/TFTMAC DEV.app` only

## Outcome

Make DEV behave like a production launcher:

```text
launch DEV
-> no macOS Keychain authorization sheet
-> splash / Android boot continues normally
-> TFT opens
-> if Riot login is needed, first unstable MobileFRE instance is recycled automatically
-> user sees only the stable Riot login page
-> user authenticates once in Riot's official UI
-> Riot's own Android session persists in DEV userdata
-> later launches go directly to TFT while that session remains valid
```

TFTMAC never stores Riot credentials.

## Phase 1 — Isolate DEV Android-PIN Keychain storage

Modify `TFTMACGuestUnlockSecretStore` to select a service by runtime mode:

- Control: keep `com.flashls1.tftmac.android-unlock.v2` exactly unchanged.
- DEV: use `com.flashls1.tftmac.dev.android-unlock.v1`.
- Candidate: no new behavior until candidate is built/accepted.

Pass the selected runtime mode from `AppCoordinator` into `loadOrPrompt`.

Do not migrate/read the old shared item automatically. On the first repaired DEV launch, absence of the DEV item opens TFTMAC's existing secure Android-PIN setup dialog once and creates the DEV-owned item. This avoids another macOS SecurityAgent authorization request against Control's ACL.

Add tests proving Control and DEV namespaces differ and the Control service remains frozen.

## Phase 2 — Add bounded Riot login stabilization state

Add a small runtime state machine for official TFT in `advanced_diagnostics` only:

```text
notObserved
-> firstLoginObserved
-> recycling
-> stableLoginReady
```

Rules:

1. Trigger only when `GameFrameTelemetryStatus == .unavailable(.loginPromptActive)`.
2. Run at most once per DEV app session.
3. Immediately block user touch/mouse/keyboard forwarding while recycling.
4. Reassert `settings put secure show_ime_with_hard_keyboard 0`.
5. Record only non-secret telemetry.
6. `am force-stop com.riotgames.league.teamfighttactics`.
7. Wait for the old PID to disappear.
8. Relaunch the official resolved/Splash/Game component using the existing official-package launch helper semantics.
9. Require a new TFT PID and a newly active `MobileFREWebViewActivity` before unblocking input.
10. No loop: if the bounded recovery cannot reach a stable replacement, keep input blocked and raise a native/runtime error instead of repeatedly killing the package.

This reproduces the exact successful runtime transition already observed after the user's Android `Close app`, but performs it before Android reaches the ANR dialog.

## Phase 3 — Keep login stabilization invisible/controlled

The existing startup curtain is one-way after TFT is first revealed. Do not repurpose it into a recurrent activity mask.

Instead, add a bounded input gate in the runtime/coordinator path:

- while Riot stabilization is active, ignore emulator user input and present a small native `Signing in…`/`Preparing Riot login…` overlay if needed;
- after the replacement login activity is proven, remove the gate and restore normal input;
- if the user is already authenticated and no Riot login appears, this path is never entered.

No Android ANR dialog should become actionable to the user.

## Phase 4 — Preserve Riot's own authenticated session

Do **not** add a Riot password vault or credential checkbox to TFTMAC.

Persistence authority is the official TFT Android app's own private userdata/WebView/session state. Keep all of the following unchanged:

- persistent DEV AVD;
- no `pm clear`;
- no `-wipe-data`;
- no package reinstall on launch;
- no token/cookie inspection.

When a Riot login activity transitions back to GameActivity in the replacement process, record a non-secret `RIOT_LOGIN_COMPLETED` marker and issue a bounded guest filesystem `sync` after a short settle period. On normal DEV shutdown, issue `sync` before the existing TFT force-stop/emulator clean-shutdown sequence. This improves durability without reading any credential/session content.

If Riot later expires the session, the official login page may legitimately return and the user authenticates manually.

## Phase 5 — Telemetry

Allowed new events:

- `DEV_UNLOCK_KEYCHAIN_NAMESPACE_SELECTED`
- `RIOT_LOGIN_STABILIZATION_STARTED`
- `RIOT_LOGIN_PROCESS_RECYCLED`
- `RIOT_LOGIN_STABLE`
- `RIOT_LOGIN_STABILIZATION_FAILED`
- `RIOT_LOGIN_COMPLETED`
- `RIOT_SESSION_STATE_SYNC_REQUESTED`

Never log typed characters, username, password, MFA, cookies, tokens, page contents, or screenshots.

## Phase 6 — Tests

Add unit tests for:

1. Control keychain service frozen at v2.
2. DEV keychain service distinct and stable.
3. first login prompt enters recovery once;
4. recovery blocks input;
5. second login observation marks stable without a second recycle;
6. replacement PID must differ from original;
7. recovery timeout fails closed and never loops;
8. authenticated GameActivity path never invokes recovery;
9. session-expired later login can use one new recovery on a new app session;
10. no credential values enter telemetry payloads.

Keep all existing splash/startup tests green.

## Phase 7 — Source acceptance

Run `scripts/verify-tftmac.command` and focused new tests. Update only stale test-count/authority hashes directly caused by this change.

## Phase 8 — DEV build/install acceptance

Use `scripts/build-dev-launcher.command`; install only `/Applications/TFTMAC DEV.app`.

Before and after install verify:

```text
/Applications/TFTMAC.app/Contents/MacOS/TFTMAC
SHA-256 = d3bf7c249a3e5f11b81f778b063e1a8cfe2e7fdeec0537ee6bd8447b1c2268d2
```

## Phase 9 — Live acceptance

### Keychain

First repaired DEV launch may ask once for the Android PIN inside TFTMAC's own secure dialog. It must not require the Mac login/Apple password. Close and relaunch DEV twice; both subsequent launches must retrieve the DEV PIN noninteractively.

### Riot ANR

If Riot login is required:

- first MobileFRE activity may be detected internally;
- input is gated;
- TFT process is recycled exactly once;
- replacement login activity becomes stable;
- user can click username immediately;
- no Android Wait/Close dialog is shown or required.

### Riot session persistence

After the user completes one official Riot login:

1. observe return to GameActivity;
2. sync guest state;
3. cleanly close DEV;
4. relaunch DEV;
5. if Riot's session is still valid, no credentials are requested and TFT reaches GameActivity directly.

If Riot itself requires MFA/re-authentication, that is an external authentication boundary, not a TFTMAC failure.

## Rollback

Revert this managed change and reinstall the previous DEV build. No Control, Riot APK, or AVD data migration is performed.
58 changes: 58 additions & 0 deletions .clara/plans/bddd2d6c-a2ed-46fb-9e9e-6674bfdf3541/PREFLIGHT.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
{
"schema": 1,
"date_local": "2026-09-04",
"project": "TFTMAC",
"change_id": "bddd2d6c-a2ed-46fb-9e9e-6674bfdf3541",
"base_sha": "c050c98ec9bd93501c2ce5e0f8583129946b61ab",
"request_class": "IMPLEMENT_SHIP",
"target": "TFTMAC DEV / advanced_diagnostics only",
"control_policy": {
"installed_path": "/Applications/TFTMAC.app",
"protected_executable_sha256": "d3bf7c249a3e5f11b81f778b063e1a8cfe2e7fdeec0537ee6bd8447b1c2268d2",
"mutation_authorized": false
},
"findings": [
{
"id": "KEYCHAIN_SHARED_NAMESPACE",
"classification": "SOURCE_AND_HOST_VERIFIED",
"evidence": "GuestUnlockSecret.swift uses service com.flashls1.tftmac.android-unlock.v2 for all runtime modes. Existing login-keychain item has that service/account. Current DEV core is separately signed/identified as TFTMACDEVCore under the stable TFTMAC Local Code Signing root.",
"conclusion": "Control and DEV share one creator ACL namespace. DEV must use its own stable Keychain item; Control service remains unchanged."
},
{
"id": "RIOT_FIRST_WEBVIEW_ANR",
"classification": "RUNTIME_VERIFIED",
"capture": "2026-09-04T22-09-46.542Z-61021ef8-8292-4392-a80a-d4090808e3ef",
"evidence": "First MobileFREWebViewActivity displayed at 17:10:23.620; first MotionEvent timed out after 5001 ms; ANR recorded at 17:10:31.420; user Close killed TFT PID 2554 at 17:10:35; replacement PID 4474 started immediately; replacement MobileFREWebViewActivity displayed in 124 ms at 17:10:42.076 and remained responsive.",
"conclusion": "The reliable current path is one bounded TFT-process recycle on the first Riot login activity before user interaction."
},
{
"id": "IME_SETTING_ALREADY_CORRECT",
"classification": "RUNTIME_VERIFIED",
"evidence": "Live DEV secure setting show_ime_with_hard_keyboard is already 0.",
"conclusion": "Reapplying that setting is harmless defense-in-depth but is not the root fix."
},
{
"id": "PERSISTENT_DEV_USERDATA",
"classification": "RUNTIME_VERIFIED",
"evidence": "DEV AVD is TFTMAC_Diagnostic_StockShadow_R1 with fastboot.forceFastBoot=yes, forceColdBoot=no. userdata-qemu.img.qcow2 is persistent and actively changing; no current source clears package/user data or uses -wipe-data.",
"conclusion": "Riot's own session can persist in Android userdata. TFTMAC does not need to store Riot username/password."
},
{
"id": "CURRENT_SOURCE_RECOVERY_GAP",
"classification": "SOURCE_VERIFIED",
"evidence": "Current runtime logs ANR/input-timeout counts but contains no automatic MobileFRE recovery implementation. Historical docs describe a recovery but no committed current source contains it.",
"conclusion": "Implement the recovery explicitly and test it."
}
],
"unknowns": [
"Whether Riot's current server session is presently valid after the user's latest successful login; only a relaunch can prove it.",
"Whether a DEV-specific default Keychain ACL survives future rebuilds without SecurityAgent prompts; repeated installed-DEV launch acceptance will prove it."
],
"win_conditions": [
"After one DEV Android-PIN setup, repeated DEV launches do not display a macOS Keychain password/Always Allow sheet.",
"The first Riot login activity is automatically stabilized; the Android Wait/Close ANR dialog is never required.",
"TFTMAC never records or stores Riot username/password, MFA, cookies, or tokens.",
"After one successful Riot login, a clean DEV close/relaunch reuses Riot's own persistent Android session when Riot still considers it valid.",
"Protected Control executable hash remains unchanged before and after build/install/acceptance."
]
}
23 changes: 23 additions & 0 deletions .clara/plans/bddd2d6c-a2ed-46fb-9e9e-6674bfdf3541/SCOPE_LOCK.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
TFTMAC DEV LOGIN-FRICTION SCOPE LOCK

AUTHORIZED:
- DEV-specific Android-unlock Keychain namespace and call-site wiring.
- Bounded one-time-per-session Riot MobileFRE process stabilization in advanced_diagnostics only.
- Input gating/native progress state during that bounded stabilization.
- Non-secret login/recovery telemetry.
- Guest `sync` for persistence durability after login and before normal DEV shutdown.
- Focused tests, verifier/test-count updates, and authority/doc updates directly required by these changes.
- Build/install/live acceptance of `/Applications/TFTMAC DEV.app` only.

FORBIDDEN:
- Any mutation, rebuild, overwrite, install, AVD change, or configuration change to protected Control `/Applications/TFTMAC.app`.
- Storing, reading, logging, intercepting, autofilling, or transmitting Riot username/password/MFA/cookies/tokens.
- `pm clear`, `-wipe-data`, package re-signing/modification, third-party login UI, CAPTCHA/MFA automation.
- Unbounded restart loops.
- Unrelated graphics/performance/refactor/dependency work.

ACCEPTANCE REQUIRES:
- Control hash unchanged.
- Repeated DEV launch with no macOS Keychain authorization sheet after one DEV PIN setup.
- No manual Android Wait/Close recovery needed on Riot login.
- Official Riot session persists across a clean DEV relaunch when Riot still considers it valid.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"schema": 1,
"date_local": "2026-09-04",
"change_id": "bddd2d6c-a2ed-46fb-9e9e-6674bfdf3541",
"zenmc_qualification": "ZENMC_REQUIRED",
"zenmc_result": "PASS",
"score": 97,
"result": "PASS",
"execution_contract": [
"DEV-only implementation; Control remains exact protected rollback.",
"Use a distinct DEV Android-PIN Keychain service; do not migrate/read Control's item.",
"Never store Riot credentials; preserve only Riot's own official Android session.",
"On first MobileFRE login observation in a DEV app session, gate input and recycle TFT exactly once.",
"Require replacement process identity and stable replacement login activity before restoring input.",
"Recovery failure fails closed; no loop, no data clear, no credential automation.",
"Use guest sync for persistence durability only; do not inspect private auth state.",
"Install/test only TFTMAC DEV and prove protected Control hash unchanged."
],
"proceed": true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"schema": 1,
"qualification": "ZENMC_REQUIRED",
"reason": [
"restart/recovery lifecycle",
"authentication state transition",
"persistent-session boundary",
"input gating during asynchronous process replacement",
"risk of recovery loops"
],
"modeled_design": "DEV-specific keychain namespace + one bounded first-login process recycle + input gate + official-session persistence",
"trajectories": 10000,
"seed": 20260904,
"violations": 0,
"checked_invariants": [
"DEV and Control Keychain namespaces never collide",
"automatic Riot recovery occurs at most once per app session",
"input remains blocked while recovery is in progress",
"no Riot credential value is stored by TFTMAC",
"Control is never mutated",
"timeout does not trigger a destructive retry or data clear"
],
"result": "PASS"
}
Loading
Loading