# Account And Profiles

## Phone SMS Sign-In

```csharp
var sendResult = await runtime.SendPhoneSignInCodeAsync(
    "13800138000",
    captchaVerifyParam);

var session = await runtime.SignInWithPhoneCodeAsync(
    "13800138000",
    verificationCode,
    inviteCode);
```

Curio signs in an existing account or creates the missing account after the SMS challenge succeeds. There is no separate registration method.

`IGPUserSession.Profile` contains the user ID, nickname, discriminator, display tag, avatar URL, and avatar-frame URL. It does not expose access or refresh tokens. Tokens stay in memory; call `RefreshUserSessionAsync()` for an explicit refresh and `SignOut()` to clear the Core account session.

These account methods do not require `InitializeAsync()`. Direct HTTP is unsupported on Unity WebGL in v1.

## Data Structures

All result models are returned by Core; game code does not construct them. They are defined in `IGP.UnitySDK.Models` and contain display data only. Authentication credentials remain private to the runtime.

| Type | Fields | Semantics |
| --- | --- | --- |
| `IGPPhoneCodeSendResult` | `Succeeded: bool`, `DevelopmentCode: string`, `IsTestAccount: bool` | Indicates whether the code request was accepted. `DevelopmentCode` is only meaningful for the configured development/test path. |
| `IGPUserSession` | `Profile: IGPUserProfile` | Returned by sign-in and refresh. It is a snapshot, not a token container. |
| `IGPUserProfile` | `Id`, `Nickname`, `Discriminator`, `DisplayTag`, `AvatarUrl`, `AvatarFrameUrl` | Full current-user identity and display fields. Empty URL means no asset was returned. |
| `IGPPlayerProfileSummary` | `Id`, `Nickname`, `AvatarUrl`, `AvatarFrameUrl` | Lightweight profile used for room/member lists and other batch views. |

`IGPUserSession.Profile` is cloned when exposed, so retaining it does not give game code a mutable reference to Core's cache. `CurrentUserProfile` is nullable until a successful Desktop profile lookup, direct sign-in, refresh, or profile request has populated it.

## Profile Routing

```csharp
var current = await runtime.GetCurrentUserProfileAsync();
var players = await runtime.GetPlayerProfilesAsync(playerIds);
```

If Desktop is already attached with a signed-in `userContext`, Core uses the Desktop profile commands. Otherwise it uses the Direct Curio session. This decision is made before sending the request. A Desktop timeout, protocol error, or business rejection is returned to the caller and is not retried through Direct HTTP.

`GetPlayerProfilesAsync` accepts 1 to 50 unique IDs after trimming and deduplication; each ID is limited to 128 characters. Result order follows the first occurrence of each requested ID.

`CurrentUserProfile` returns the currently selected cached profile. It can be `null` before either provider has a usable user context.

The profile contract intentionally does not include phone number, access token, refresh token, or server-side account metadata. A caller that needs a stable display label should use `DisplayTag`; callers should not reconstruct it from `Nickname` and `Discriminator`.

## Identity And Retry

An explicit Direct sign-in must match an already attached Desktop user. A mismatch returns `ACCOUNT_CONTEXT_CONFLICT` without committing the new Direct credentials.

For authenticated Direct requests, Core handles one `401` by refreshing the session and retrying once. Concurrent requests share a refresh for the same stale token. A failed refresh or second `401` clears the Direct session.
