diff --git a/unity-client/Assets/Scripts/UI/DeckGeneratorController.cs b/unity-client/Assets/Scripts/UI/DeckGeneratorController.cs
new file mode 100644
index 0000000..78049ab
--- /dev/null
+++ b/unity-client/Assets/Scripts/UI/DeckGeneratorController.cs
@@ -0,0 +1,271 @@
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+using UnityEngine.UI;
+using UnityEngine.SceneManagement;
+using TMPro;
+using Newtonsoft.Json;
+using CommanderAILab.Models;
+using CommanderAILab.Services;
+
+namespace CommanderAILab.UI
+{
+ ///
+ /// DeckGenerator scene — AI deck generation wizard:
+ /// commander pick, strategy/budget inputs, precon base,
+ /// streaming progress, result preview, save to DeckBuilder.
+ ///
+ public class DeckGeneratorController : MonoBehaviour
+ {
+ // ── Navigation ────────────────────────────────────────────────────
+ [Header("Navigation")]
+ [SerializeField] private Button backButton;
+
+ // ── Step 1: Commander Selection ───────────────────────────────────
+ [Header("Step 1 - Commander")]
+ [SerializeField] private TMP_InputField commanderSearchInput;
+ [SerializeField] private Button commanderSearchButton;
+ [SerializeField] private Transform commanderResultParent;
+ [SerializeField] private GameObject commanderResultRowPrefab;
+ [SerializeField] private TMP_Text selectedCommanderLabel;
+
+ // ── Step 2: Strategy & Budget ─────────────────────────────────────
+ [Header("Step 2 - Strategy & Budget")]
+ [SerializeField] private TMP_Dropdown strategyDropdown; // Aggro/Control/Combo/Midrange/Stax/Chaos
+ [SerializeField] private TMP_Dropdown budgetDropdown; // Pauper/$50/$100/$250/No limit
+ [SerializeField] private TMP_InputField synergiesInput; // free-text themes
+ [SerializeField] private TMP_Dropdown preconBaseDropdown; // optional precon starting point
+ [SerializeField] private Toggle ownedOnlyToggle; // restrict to owned cards
+
+ // ── Step 3: Generate ──────────────────────────────────────────────
+ [Header("Step 3 - Generate")]
+ [SerializeField] private Button generateButton;
+ [SerializeField] private GameObject progressPanel;
+ [SerializeField] private Slider progressSlider;
+ [SerializeField] private TMP_Text progressText;
+
+ // ── Step 4: Result Preview ────────────────────────────────────────
+ [Header("Step 4 - Result")]
+ [SerializeField] private GameObject resultPanel;
+ [SerializeField] private Transform resultCardListParent;
+ [SerializeField] private GameObject resultCardRowPrefab;
+ [SerializeField] private TMP_Text resultSummaryText;
+ [SerializeField] private TMP_Text resultManaCurveText;
+ [SerializeField] private Button saveToDeckBuilderButton;
+ [SerializeField] private Button regenerateButton;
+
+ // ── Error ─────────────────────────────────────────────────────────
+ [Header("Error")]
+ [SerializeField] private GameObject errorPanel;
+ [SerializeField] private TMP_Text errorText;
+ [SerializeField] private Button retryButton;
+
+ // ── State ─────────────────────────────────────────────────────────
+ private CardModel _selectedCommander;
+ private DeckGenResponse _lastResult;
+ private List _commanderRows = new();
+ private List _resultRows = new();
+ private List _preconNames = new();
+
+ // ── Lifecycle ─────────────────────────────────────────────────────
+ private void Start()
+ {
+ backButton.onClick.AddListener(() => SceneManager.LoadScene("MainMenu"));
+
+ commanderSearchButton.onClick.AddListener(OnCommanderSearch);
+ commanderSearchInput.onSubmit.AddListener(_ => OnCommanderSearch());
+
+ generateButton.onClick.AddListener(OnGenerate);
+ saveToDeckBuilderButton.onClick.AddListener(OnSaveToDeckBuilder);
+ regenerateButton.onClick.AddListener(OnGenerate);
+ retryButton.onClick.AddListener(OnGenerate);
+
+ progressPanel.SetActive(false);
+ resultPanel.SetActive(false);
+ errorPanel.SetActive(false);
+
+ LoadPrecons();
+ }
+
+ // ── Precon list ───────────────────────────────────────────────────
+ private void LoadPrecons()
+ {
+ ApiClient.Instance.GetPrecons(
+ json =>
+ {
+ try
+ {
+ var wrapper = JsonConvert.DeserializeObject(json);
+ _preconNames = wrapper?.precons ?? new List();
+ }
+ catch { _preconNames = new List(); }
+
+ var options = new List { new("None (from scratch)") };
+ foreach (var p in _preconNames) options.Add(new TMP_Dropdown.OptionData(p));
+ preconBaseDropdown.options = options;
+ preconBaseDropdown.value = 0;
+ },
+ err => Debug.LogWarning($"[DeckGen] Could not load precons: {err}"));
+ }
+
+ // ── Commander Search ──────────────────────────────────────────────
+ private void OnCommanderSearch()
+ {
+ string q = commanderSearchInput.text.Trim();
+ if (string.IsNullOrEmpty(q)) return;
+ string path = $"/api/collection?search={UnityWebRequest.EscapeURL(q)}&type=Legendary+Creature&per_page=20";
+ StartCoroutine(ApiClient.Instance.GetRaw(path,
+ json => PopulateCommanderResults(json),
+ err => ShowError($"Commander search failed:\n{err}")));
+ }
+
+ private void PopulateCommanderResults(string json)
+ {
+ foreach (var go in _commanderRows) if (go) Destroy(go);
+ _commanderRows.Clear();
+
+ List cards;
+ try
+ {
+ var resp = JsonConvert.DeserializeObject(json);
+ cards = resp?.cards ?? new List();
+ }
+ catch { return; }
+
+ foreach (var card in cards)
+ {
+ var row = Instantiate(commanderResultRowPrefab, commanderResultParent);
+ _commanderRows.Add(row);
+ var label = row.GetComponentInChildren();
+ if (label) label.text = $"{card.name} — {card.manaCost}";
+ var btn = row.GetComponentInChildren