fix(viewer): RegisterViewer defaults to post-tutorial TutorialState=100
BuildDefaultViewer hardcoded TutorialState=1 — correct for fresh anonymous signups (RegisterAnonymousViewer) but wrong for AdminController.ImportViewer and Steam-social signups, which both go through RegisterViewer and expect a prod-replica viewer that boots to the home screen. Add an initialTutorialState parameter (default 1 preserves RegisterAnonymousViewer behavior); RegisterViewer passes 100. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -72,7 +72,11 @@ public class ViewerRepository : IViewerRepository
|
||||
public async Task<Models.Viewer> RegisterViewer(string displayName, SocialAccountType socialType,
|
||||
ulong socialAccountIdentifier, ulong? shortUdid = null)
|
||||
{
|
||||
var viewer = await BuildDefaultViewer(displayName);
|
||||
// RegisterViewer is the import / Steam-social path. Default to the post-tutorial baseline
|
||||
// (state 100) so AdminController.ImportViewer materializes prod-replicas at the home screen
|
||||
// unless the import request explicitly overrides via request.TutorialState. The anonymous
|
||||
// signup path (RegisterAnonymousViewer) uses the parameter default of 1.
|
||||
var viewer = await BuildDefaultViewer(displayName, initialTutorialState: 100);
|
||||
viewer.SocialAccountConnections.Add(new SocialAccountConnection
|
||||
{
|
||||
AccountId = socialAccountIdentifier,
|
||||
@@ -195,7 +199,7 @@ public class ViewerRepository : IViewerRepository
|
||||
await _dbContext.SaveChangesAsync();
|
||||
}
|
||||
|
||||
private async Task<Models.Viewer> BuildDefaultViewer(string displayName)
|
||||
private async Task<Models.Viewer> BuildDefaultViewer(string displayName, int initialTutorialState = 1)
|
||||
{
|
||||
Models.Viewer viewer = new Models.Viewer
|
||||
{
|
||||
@@ -211,15 +215,11 @@ public class ViewerRepository : IViewerRepository
|
||||
viewer.Currency.Crystals = grants.Crystals;
|
||||
viewer.Currency.Rupees = grants.Rupees;
|
||||
viewer.Currency.RedEther = grants.Ether;
|
||||
// TUTORIAL_STEP0 (= 1) — "fresh signup, ready to play the prologue", confirmed by the
|
||||
// prod capture in data_dumps/traffic_prod_tutorial.ndjson (game_start returned
|
||||
// now_tutorial_step="1"). Step 0 is a pre-existence state we never want to expose
|
||||
// on the wire: Wizard.Title.NextSceneSwitcher routes step==1 → Prologue scene and
|
||||
// every other non-{31,41,100} step → AreaSelect at section 0, which has no chapter
|
||||
// data (the prologue's chapters are baked into Wizard/Prologue.cs, not served) and
|
||||
// crashes AreaSelectUI.SelectChapter with a "Sequence contains no matching element"
|
||||
// LINQ Single() failure.
|
||||
viewer.MissionData.TutorialState = 1;
|
||||
// TUTORIAL_STEP0 (= 1) is the fresh-signup default — see RegisterAnonymousViewer for
|
||||
// why step==0 is unsafe. RegisterViewer (admin-import + Steam-social) passes 100 so
|
||||
// those callers land at the post-tutorial baseline; import requests can still override
|
||||
// via the explicit ImportViewerRequest.TutorialState field.
|
||||
viewer.MissionData.TutorialState = initialTutorialState;
|
||||
|
||||
// Load classes WITH their LeaderSkins — DefaultLeaderSkin iterates the nav collection
|
||||
// and would otherwise be null (audit §6 #3 latent NRE — this is the one).
|
||||
|
||||
Reference in New Issue
Block a user