# Changelog ## 2.0.0 Breaking change — major bump, required by the backend environments release. Every project now has exactly two environments, `staging` and `prod`, and the SDK key you configure decides which one the player belongs to. The environment never appears in the client API: it is resolved at login and carried inside the access and refresh tokens. Tokens issued before this release have no environment claim and are rejected with 401, so the first call after the backend upgrade refreshes, fails, clears the stored tokens and raises `AuthService.AuthStateChanged` with `RudderAuthState.SignedOut`. Log the player in again with `Auth.LoginWithDeviceAsync` / `LoginWithCustomAsync`. Quests, scenarios and offers are addressed by their slug instead of their id, because ids differ between staging and prod while slugs are stable. Renamed accordingly: `Quest.Id` is now `Quest.Slug`, `QuestsService.ClaimAsync` takes a quest slug, `ReportProgressAsync` returns the slugs of the completed quests (`ReportQuestProgressResponse.CompletedQuestSlugs`), `StoresService.PurchaseAsync` takes an offer slug and `Offer` carries a `Slug`, `QuestMetrics.PurchaseOffer` builds its metric from the offer slug, and every scenario-scoped type exposes `ScenarioSlug` instead of `ScenarioId` — `PendingEffect`, the effect objects, `ScenarioCompletedEffect`, `ScenarioFailedEffect`, the battle pass requests and `BattlePassService.GetProgressAsync`. Leaderboards, items and stores already used slugs and are unchanged. Also shipped here, previously committed but never published: the effects client reconciles against every `GET /sdk/v1/scenarios/pending` response, so a run that disappears server-side (finished elsewhere, expired after a promote) now emits `OnScenarioCompleted` instead of lingering; and `run_not_active` joins `unknown_run` and `run_expired` as a terminal rejection that drops the run and emits `OnScenarioFailed`. ## 1.0.0 Breaking change — major bump. The local scenario engine is replaced by a server-driven effects client; the realtime websocket client is removed. ### Removed - Local scenario runtime: `PlanRun`, `ExecutionPlan` walking, node sessions (`NotificationSession`, `StoreOfferSession`, `LeaderboardSession`, `WaitSession`, `QuestSession`, `BattlePassSession`, `BattlePassLevelSession`, `ConfigChangedSession`), `RespondAsync` / `Respond`, `RestoreAsync`, `Clear`, `ActiveRuns`, `IsRunning`, `CurrentNodeId`, `OnConfigChanged`. - `RudderClientOptions.PlanStateStore` / `IPlanStateStore` and `IPlanScheduler` — no local plan persistence or delayed plan work. - `RealtimeService` (`client.Realtime`), `RudderClientOptions.RealtimeUrl` / `RealtimeTransportFactory`, `IRealtimeTransport` / `IRealtimeTransportFactory`. ### Changed (breaking) - `client.Scenario` is a trigger only: `TriggerAsync(eventName)` returns `Task` (not started plans). Returned pending effects are ingested into `client.Effects`. - Subscribe to `client.Effects.On*` instead of `client.Scenario.On*`. Sessions are now effect objects (`NotificationEffect`, `StoreOfferEffect`, …). Complete them with the methods on the effect (`DoneAsync`, `PurchaseAsync`/`DeclineAsync`, `EndAsync`/`ClaimAsync`, …). - `client.Update(deltaTime)` is still required every frame — it now pumps the effects client (30s `GET /sdk/v1/scenarios/pending` heartbeat + wait-deadline checks), not a local DAG or a websocket. - After login (or when constructed with a stored access token), the next `Update` fetches pending effects. There is no `loginEvent` option; trigger login-gated scenarios yourself. ### Migration | Before | After | |---|---| | `client.Scenario.OnNotification` (and other `On*`) | `client.Effects.OnNotification` (same event names) | | `NotificationSession` / `StoreOfferSession` / … | `NotificationEffect` / `StoreOfferEffect` / … | | `session.CompleteAsync()` / `RespondAsync("output")` | `effect.DoneAsync()` (and the matching method on each effect) | | `RudderClientOptions.PlanStateStore` + `RestoreAsync` | gone — server owns run state; pending fetch after login + heartbeat | | `client.Realtime` | gone | | `client.Update(deltaTime)` | still required (effects pump) | ## 0.4.0 - Generated DTOs live under `Models/` (`RudderSdk.Core.Models`). Wire fields are nullable. `ListQuestsRequest` is gone; `Quests.ListAsync` POSTs with no body. `BoundaryNode.WaitDeadline` is `DateTimeOffset?`. - `LeaderboardSession.ClaimAsync` / `Claim` complete the leaderboard node with handle `onClaim`. The server matches live rank to an authored place. - `RewardClaimedAsync` / `RewardClaimed` are obsolete aliases and will be removed in the next SDK version. - `rank_not_eligible` on Claim leaves the session open (retry Claim or End); it no longer fails the run. ## 0.3.0 ### Removed - UGC surface: `UgcService` (`client.Ugc`), the generated `RudderSdk.Core.Models.Ugc` DTOs, `IUploadTransport` and `RudderClientOptions.UploadTransport`. The server no longer exposes the UGC endpoints. ### Changed (breaking) - `BattlePassService` simple overloads sent empty scenario/node ids and always failed server-side. Battle pass state is tied to a scenario battle pass node, so the public API now mirrors the web SDK: `GetProgressAsync(scenarioId, nodeId)` and the request-DTO overloads `AddXpAsync`, `ClaimRewardAsync`, `PurchasePremiumAsync` (carrying `ScenarioId`/`NodeId`/`RunId`) are public; the internal request overloads are gone. - `BattlePassSession` now exposes the bound battle pass operations (`GetProgressAsync`, `AddXpAsync(source, amount)`, `ClaimRewardAsync(level, track)`, `PurchasePremiumAsync()` — crosses `onPremiumPurchase` on success), mirroring the web SDK session. - `BattlePassLevelSession.CompleteAsync`/`Complete` renamed to `ClaimAsync`/`Claim`; new `Level` property reads the `levelNumber` node data key. - Unsupported scenario node types now fail the run (`OnScenarioFailed`) instead of stalling silently. - Scenario counter updates no longer read a continuation plan from the response (the server stopped returning one); when the server reports the objective completed, the node crosses `onComplete`. ### Fixed - A failed scenario counter update no longer fails the run; the error is logged and the node stays active. ## 0.2.0 ### Added - `QuestsService` (`client.Quests`) with `ListAsync` / `ClaimAsync`. - `AuthService.LoginWithDeviceAsync(region, language, nickname?)` and `AuthService.RefreshAsync()`; `AuthStateChanged` event (`RudderAuthState.SignedIn` / `SignedOut`). - Session pipeline: single-flight token refresh plus one transparent retry-on-401 for every API call; failed refresh clears tokens and reports `SignedOut`. - `HttpClientTransport` — default `System.Net.Http.HttpClient`-based transport. `RudderClientOptions.Transport` is now optional, as are `TokenStore` (`InMemoryTokenStore`) and `DeviceIdProvider` (`GuidDeviceIdProvider`). - Typed exceptions: `RudderAuthException` (401), `RudderNotFoundException` (404), `RudderRateLimitException` (429), `RudderNetworkException` (network/timeout). All carry `RequestId` from the server error payload. - `StorageService.ListAllAsync` and `UgcService.ListAllAsync` (`IAsyncEnumerable` cursor pagination helpers). - `RudderLogLevel` enum; `IRudderLogger.Log` now takes a level. - Package metadata (`Rudder.Core`, MIT) and XML documentation. ### Changed (breaking) - Generated models moved to domain folders/namespaces (`RudderSdk.Core.Models.`); the flat `RudderSdk.Core.DTO` namespace is gone. `Battlepass*` → `BattlePass*`, `SdkQuest` → `Quest`. - `BattlepassService` → `BattlePassService`, `client.Battlepass` → `client.BattlePass`. - `client.Scenarios` → `client.Scenario`. - `ScenarioService.SendAsync` → `TriggerAsync`, now returning the started `PlanRun`s. - Scenario events reduced to the canonical set: `OnNotification`, `OnStoreOffer`, `OnLeaderboard`, `OnConfigChanged`, `OnWait`, `OnQuest`, `OnBattlePass`, `OnBattlePassLevel`, `OnScenarioCompleted`, `OnScenarioFailed`. Legacy events (`OnShowNotification`, `OnShowStore`, `OnQuestEvent`, `OnBattlepassEvent`, `OnLeaderboardEvent`, `OnNodeEvent`, `OnNode`, `OnCompleted`, `OnStore`, `OnRemoteConfigOverride`, `OnRunCompleted`, `OnRunFailed`) are removed, along with the `Services/Scenarios/Legacy` folder. - Session types renamed: `StoreSession` → `StoreOfferSession` (`BuyAsync`/`Buy` → `PurchaseAsync`/`Purchase`), `RemoteConfigOverrideSession` → `ConfigChangedSession`, `BattlepassSession` → `BattlePassSession`, `BattlepassLevelSession` → `BattlePassLevelSession`, `ScenarioRunFailedEvent` → `ScenarioFailedEvent`. - `StoresService.BuyAsync` → `PurchaseAsync(storeSlug, offerId, idempotencyKey = null)`; a null key is replaced with a generated GUID. - `InventoryService.GetAsync` returns `IReadOnlyList`. - `StoresService.ListAsync` returns `IReadOnlyList`. - `QuestsService.ListAsync` returns `IReadOnlyList`. - `UgcService.GetDownloadUrlAsync` returns non-null `string` and throws `InvalidOperationException` when the server returns no URL. - `BattlePassService` simple overloads (`AddXpAsync(long amount)`, `ClaimRewardAsync(level, track)`, `GetProgressAsync()`, `PurchasePremiumAsync(idempotencyKey?)`); the request-DTO overloads are internal for the scenario runtime. - `LeaderboardsService.GetRankingAsync`/`SubmitScoreAsync` are internal; use the `FindBySlug(slug)` handle (`SubmitAsync`, `ListAsync`). - `AuthService.LoginViaDeviceAsync` removed; use `LoginWithDeviceAsync`. - `ScenarioService.Restore()` (sync-over-async) removed; use `RestoreAsync`. - `RealtimeService.ConnectAsync` and `IRealtimeTransport.ConnectAsync` take `TimeSpan` timeouts instead of `int timeoutSeconds`. - `IRudderTransport.SendAsync` takes a `string? accessToken` instead of `RequestOptions`; `RequestOptions` is removed and `EmptyResponse` is internal. - `RudderClient` getters `Options`, `Transport`, `TokenStore`, `DeviceIdProvider` are internal. - `RudderApiException.StatusCode` is `int` (was `long`). - `IRudderLogger.Log(string)` → `Log(RudderLogLevel level, string message)`. - Nullable reference types enabled (`enable`). - Remote config cache now includes every config except an explicit `"active": false` (a missing flag means active).