feat(envelope): push required_res_ver from ResourceConfig on game_start
A wiped/fresh client (NukeIdentityOnStartup, new install, or any path that clears PlayerPrefs) defaults its stored RES_VER to "00000000" per Cute/SavedataManager.GetResourceVersion. The client builds the Akamai manifest URL as dl/Manifest/<RES_VER>/<lang>/<Platform>/, and Akamai 404s the "00000000" path -> Toolbox.AssetManager.InitializeManifest fails -> the title screen shows "Connection Error / Reconnect" before any tutorial UI loads. Fix: - New ResourceConfig [ConfigSection] in SVSim.Database — single field RequiredResVer defaulting to "4670rPsPMVlRTd2" (the value prod returned in data_dumps/traffic_prod_tutorial.ndjson and was still returning at 2026-05-28 21:00 UTC). Lives in GameConfigs so it can be tuned via DB / appsettings without code edits. - ShadowverseTranslationMiddleware injects IGameConfigService and emits required_res_ver in data_headers ONLY on /check/game_start responses. NetworkTask.Parse opens a "new data is available" popup whenever required_res_ver is present and the URL is anything other than GameStartCheck (NetworkTask.cs:128-138); the suppression on game_start is what lets us silently bump PlayerPrefs["RES_VER"] before ResourceDownloader runs. - DataHeaders gains a nullable RequiredResVer field. DataWrapper.DataHeaders is now Dictionary<string, object?> instead of the typed DataHeaders POCO directly — the construction site stays type-safe (the middleware builds the typed POCO, then projects through the same STJ + ConvertJsonTreeToPlainObject pipeline that DataWrapper.Data uses) so null-valued optional fields are absent from the wire instead of being written as "key":null. Without this, MessagePack's ContractlessStandardResolver walked the typed properties and wrote required_res_ver=null on every non-game_start response, tripping the popup on every boot. - GameConfigurationJsonbTests updated to expect the 9th config section. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -32,4 +32,23 @@ public class DataHeaders
|
||||
[JsonPropertyName("udid")]
|
||||
[Key("udid")]
|
||||
public string Udid { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Tells the client the required version path component for asset manifests on the
|
||||
/// resource server (Akamai CDN, hardcoded to <c>shadowverse.akamaized.net/</c> in
|
||||
/// <c>Wizard/SetUp.cs:48</c>). <c>NetworkTask.setResourceVersion</c> writes the value
|
||||
/// to <c>PlayerPrefs["RES_VER"]</c>; the manifest URL becomes
|
||||
/// <c>dl/Manifest/<RES_VER>/<lang>/<Platform>/</c>. When the client
|
||||
/// has no cached <c>RES_VER</c> (e.g., after <c>NukeIdentityOnStartup</c> wipes
|
||||
/// PlayerPrefs), it defaults to <c>"00000000"</c>, which Akamai doesn't serve — the
|
||||
/// manifest fetch 404s and the client shows "Connection Error / Reconnect" before
|
||||
/// the tutorial UI ever appears.
|
||||
/// <para>
|
||||
/// Nullable to keep it off the wire on responses that don't need it (the global
|
||||
/// <c>WhenWritingNull</c> policy in Program.cs handles the omission).
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[JsonPropertyName("required_res_ver")]
|
||||
[Key("required_res_ver")]
|
||||
public string? RequiredResVer { get; set; }
|
||||
}
|
||||
|
||||
@@ -10,11 +10,20 @@ namespace SVSim.EmulatedEntrypoint.Models.Dtos.Internal;
|
||||
public class DataWrapper
|
||||
{
|
||||
/// <summary>
|
||||
/// Additional data about the request, response and user.
|
||||
/// Wire-shape projection of the response envelope headers. The middleware builds a
|
||||
/// strongly-typed <see cref="DataHeaders"/> POCO and runs it through the same STJ +
|
||||
/// <c>ConvertJsonTreeToPlainObject</c> pipeline that the controller's response goes
|
||||
/// through, yielding this dict with absent keys for null-valued optional fields.
|
||||
/// Typed as <see cref="Dictionary{TKey,TValue}"/> (not <see cref="object"/>) because
|
||||
/// the projected shape is fully known — only the per-key value type varies. Direct
|
||||
/// assignment of the typed POCO would let MessagePack's contractless resolver emit
|
||||
/// <c>"key":null</c> for nullables, which the client treats as "key present" via
|
||||
/// <c>Keys.Contains</c> (see <c>NetworkTask.isResourceVersionUp</c> for the
|
||||
/// load-bearing case).
|
||||
/// </summary>
|
||||
[JsonPropertyName("data_headers")]
|
||||
[Key("data_headers")]
|
||||
public DataHeaders DataHeaders { get; set; } = new DataHeaders();
|
||||
[Key("data_headers")]
|
||||
public Dictionary<string, object?> DataHeaders { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// The response data from the endpoint.
|
||||
|
||||
Reference in New Issue
Block a user