refactor(bootstrap): add 7 load-index importers (excluding card lists)

Stage 9B of the bootstrap-seed-refactor: add per-domain importer classes
that consume the load-index seed split produced in Stage 9A.

New importers (each in its own file under SVSim.Bootstrap/Importers/):
- RotationConfigImporter: writes Rotation/Challenge/MyRotationSchedule
  GameConfig sections (atomic UpsertSection<T> pattern, copied private-static
  from GlobalsImporter so this importer stands alone post-9C).
- MyRotationImporter: settings + abilities (extractor pre-joins on rotation_id).
- AvatarAbilityImporter: per-leader_skin_id ability rows.
- ArenaSeasonImporter: singleton (Id=1) Take Two arena season.
- BattlePassImporter: per-level reward blobs.
- DailyLoginBonusImporter: per-bonus-id campaign blobs.
- PreReleaseInfoImporter: singleton (Id=1) pre-release window.

Seed DTOs under SVSim.Bootstrap/Models/Seed/ mirror the seed JSON via
[JsonPropertyName] snake_case. Raw-JSON columns (reward_data, format_info,
etc.) use JsonElement on the seed and JsonSerializer.Serialize in the
importer.

Tests: 7 new happy-path tests in LoadIndexImporterTests.cs (idempotency
covered by BattlePass spot-check). Full suite: 382/382 passing (375 + 7).

NOT modifying in this stage: GlobalsImporter.cs (Stage 9C strips the old
methods), Program.cs (Stage 9C wires up all 9 importers), SVSimTestFactory
(Stage 9C). Double-writing on bootstrap is expected and OK during 9B.
This commit is contained in:
gamer147
2026-05-26 15:29:57 -04:00
parent 8dbd52da54
commit 87d0001569
16 changed files with 625 additions and 0 deletions

View File

@@ -0,0 +1,20 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace SVSim.Bootstrap.Models.Seed;
/// <summary>
/// Mirrors <c>seeds/arena-season.json</c>. Singleton (id=1) holding the Take Two arena season.
/// <c>format_info</c> is a nested JSON object stored verbatim as the entity's <c>FormatInfo</c> jsonb.
/// </summary>
public sealed class ArenaSeasonSeed
{
[JsonPropertyName("id")] public int Id { get; set; }
[JsonPropertyName("mode")] public int Mode { get; set; }
[JsonPropertyName("enable")] public int Enable { get; set; }
[JsonPropertyName("cost")] public ulong Cost { get; set; }
[JsonPropertyName("rupy_cost")] public ulong RupyCost { get; set; }
[JsonPropertyName("ticket_cost")] public int TicketCost { get; set; }
[JsonPropertyName("is_join")] public bool IsJoin { get; set; }
[JsonPropertyName("format_info")] public JsonElement FormatInfo { get; set; }
}

View File

@@ -0,0 +1,17 @@
using System.Text.Json.Serialization;
namespace SVSim.Bootstrap.Models.Seed;
/// <summary>Mirrors <c>seeds/avatar-abilities.json</c>. One row per leader_skin_id.</summary>
public sealed class AvatarAbilitySeed
{
[JsonPropertyName("id")] public int Id { get; set; }
[JsonPropertyName("battle_start_first_player_turn_bp")] public int BattleStartFirstPlayerTurnBp { get; set; }
[JsonPropertyName("battle_start_second_player_turn_bp")] public int BattleStartSecondPlayerTurnBp { get; set; }
[JsonPropertyName("battle_start_max_life")] public int BattleStartMaxLife { get; set; }
[JsonPropertyName("ability_cost")] public string AbilityCost { get; set; } = "";
[JsonPropertyName("ability")] public string Ability { get; set; } = "";
[JsonPropertyName("passive_ability")] public string PassiveAbility { get; set; } = "";
[JsonPropertyName("ability_desc")] public string AbilityDesc { get; set; } = "";
[JsonPropertyName("passive_ability_desc")] public string PassiveAbilityDesc { get; set; } = "";
}

View File

@@ -0,0 +1,11 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace SVSim.Bootstrap.Models.Seed;
/// <summary>Mirrors <c>seeds/battle-pass-levels.json</c>. <c>reward_data</c> preserved verbatim.</summary>
public sealed class BattlePassLevelSeed
{
[JsonPropertyName("id")] public int Id { get; set; }
[JsonPropertyName("reward_data")] public JsonElement RewardData { get; set; }
}

View File

@@ -0,0 +1,11 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace SVSim.Bootstrap.Models.Seed;
/// <summary>Mirrors <c>seeds/daily-login-bonus.json</c>. <c>bonus_data</c> preserved verbatim.</summary>
public sealed class DailyLoginBonusSeed
{
[JsonPropertyName("id")] public int Id { get; set; }
[JsonPropertyName("bonus_data")] public JsonElement BonusData { get; set; }
}

View File

@@ -0,0 +1,11 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace SVSim.Bootstrap.Models.Seed;
/// <summary>Mirrors <c>seeds/my-rotation-abilities.json</c>. <c>data</c> is preserved as raw JSON.</summary>
public sealed class MyRotationAbilitySeed
{
[JsonPropertyName("id")] public int Id { get; set; }
[JsonPropertyName("data")] public JsonElement Data { get; set; }
}

View File

@@ -0,0 +1,18 @@
using System.Text.Json.Serialization;
namespace SVSim.Bootstrap.Models.Seed;
/// <summary>
/// Mirrors <c>seeds/my-rotation-settings.json</c>. The extractor pre-joins
/// <c>my_rotation_info.{setting, reprinted_base_card_ids, restricted_base_card_id_list}</c> on
/// rotation_id into one flat list. <c>reprinted_card_ids</c> and <c>restricted_card_ids</c> are
/// pre-serialized JSON strings (verbatim from the wire) — the importer stores them verbatim.
/// </summary>
public sealed class MyRotationSettingSeed
{
[JsonPropertyName("id")] public int Id { get; set; }
[JsonPropertyName("card_set_ids_csv")] public string CardSetIdsCsv { get; set; } = "";
[JsonPropertyName("abilities_csv")] public string AbilitiesCsv { get; set; } = "";
[JsonPropertyName("reprinted_card_ids")] public string ReprintedCardIds { get; set; } = "[]";
[JsonPropertyName("restricted_card_ids")] public string RestrictedCardIds { get; set; } = "[]";
}

View File

@@ -0,0 +1,25 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace SVSim.Bootstrap.Models.Seed;
/// <summary>
/// Mirrors <c>seeds/pre-release-info.json</c>. Singleton (id=1). Card-id lists are kept as raw
/// JSON elements so they round-trip verbatim into the entity's jsonb columns.
/// </summary>
public sealed class PreReleaseInfoSeed
{
[JsonPropertyName("pre_release_id")] public string PreReleaseId { get; set; } = "";
[JsonPropertyName("next_card_set_id")] public string NextCardSetId { get; set; } = "";
[JsonPropertyName("start_time")] public string StartTime { get; set; } = "";
[JsonPropertyName("end_time")] public string EndTime { get; set; } = "";
[JsonPropertyName("display_end_time")] public string DisplayEndTime { get; set; } = "";
[JsonPropertyName("free_match_start_time")] public string FreeMatchStartTime { get; set; } = "";
[JsonPropertyName("card_master_id")] public int CardMasterId { get; set; }
[JsonPropertyName("default_card_master_id")] public string DefaultCardMasterId { get; set; } = "";
[JsonPropertyName("pre_release_card_master_id")] public string PreReleaseCardMasterId { get; set; } = "";
[JsonPropertyName("is_pre_rotation_free_match_term")] public bool IsPreRotationFreeMatchTerm { get; set; }
[JsonPropertyName("rotation_card_set_id_list")] public JsonElement RotationCardSetIdList { get; set; }
[JsonPropertyName("reprinted_base_card_ids")] public JsonElement ReprintedBaseCardIds { get; set; }
[JsonPropertyName("latest_reprinted_base_card_ids")] public JsonElement LatestReprintedBaseCardIds { get; set; }
}

View File

@@ -0,0 +1,41 @@
using System.Text.Json.Serialization;
namespace SVSim.Bootstrap.Models.Seed;
/// <summary>
/// Mirrors <c>seeds/rotation-config.json</c>. Drives the Rotation <c>GameConfigSection</c>.
/// Note: <c>rotation_card_set_ids</c> is the rotation CardSet flag list — consumed by
/// RotationFlagUpdater in Stage 9C, not by RotationConfigImporter.
/// </summary>
public sealed class RotationConfigSeed
{
[JsonPropertyName("ts_rotation_id")] public string TsRotationId { get; set; } = "";
[JsonPropertyName("is_battle_pass_period")] public bool IsBattlePassPeriod { get; set; }
[JsonPropertyName("is_beginner_mission")] public bool IsBeginnerMission { get; set; }
[JsonPropertyName("card_set_id_for_resource_dl_view")] public int CardSetIdForResourceDlView { get; set; }
[JsonPropertyName("rotation_card_set_ids")] public List<int> RotationCardSetIds { get; set; } = new();
}
/// <summary>Mirrors <c>seeds/challenge-config.json</c>. Drives the Challenge <c>GameConfigSection</c>.</summary>
public sealed class ChallengeConfigSeed
{
[JsonPropertyName("use_two_pick_premium_card")] public bool UseTwoPickPremiumCard { get; set; }
[JsonPropertyName("two_pick_sleeve_id")] public long TwoPickSleeveId { get; set; }
}
/// <summary>
/// Mirrors <c>seeds/my-rotation-schedule.json</c>. Drives the MyRotationSchedule
/// <c>GameConfigSection</c>. The extractor pre-joins <c>gathering</c> and <c>free_battle</c>
/// from <c>my_rotation_info.schedules</c> into two top-level fields.
/// </summary>
public sealed class MyRotationScheduleSeed
{
[JsonPropertyName("gathering")] public ScheduleWindowSeed? Gathering { get; set; }
[JsonPropertyName("free_battle")] public ScheduleWindowSeed? FreeBattle { get; set; }
}
public sealed class ScheduleWindowSeed
{
[JsonPropertyName("begin")] public string Begin { get; set; } = "";
[JsonPropertyName("end")] public string End { get; set; } = "";
}