namespace AcDream.App.Studio; /// /// Parsed options for the acdream UI Studio standalone tool. /// Constructed by from the command-line tokens that follow /// the ui-studio dispatch token. /// public sealed record StudioOptions( string DatDir, uint? LayoutId, string? MarkupPath, string? DumpSlug = null, string? DumpFile = null, string? ScreenshotPath = null) { /// /// Parse studio options from the args that come AFTER the ui-studio token. /// /// Positional (first non-flag arg): dat directory. Falls back to /// ACDREAM_DAT_DIR when omitted. /// --layout 0xNNNN: hex LayoutDesc dat id to preview. /// --markup <path>: path to a KSML markup file (Task 6, unsupported for now). /// --dump <slug>: load a panel from the retail UI dump JSON by slug /// (e.g. inventory, radar, toolbar). Static mockup — no controllers. /// --dump-file <path>: override the default dump file path /// (docs/research/2026-06-25-retail-ui-layout-dump.json from the solution root). /// Only meaningful when --dump is also given. /// --screenshot <path>: headless mode — render the loaded panel to a PNG /// at and exit without showing an interactive window. /// Combines with --dump or --layout. /// When neither --layout, --markup, nor --dump is given the /// default layout 0x2100006C (vitals) is used. /// public static StudioOptions Parse(string[] args) { string? datDir = null; uint? layoutId = null; string? markupPath = null; string? dumpSlug = null; string? dumpFile = null; string? screenshotPath = null; for (int i = 0; i < args.Length; i++) { if (args[i] == "--layout" && i + 1 < args.Length) { var raw = args[++i]; // Accept 0xNNNN or plain hex. if (raw.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) raw = raw[2..]; if (uint.TryParse(raw, System.Globalization.NumberStyles.HexNumber, null, out var id)) layoutId = id; } else if (args[i] == "--markup" && i + 1 < args.Length) { markupPath = args[++i]; } else if (args[i] == "--dump" && i + 1 < args.Length) { dumpSlug = args[++i]; } else if (args[i] == "--dump-file" && i + 1 < args.Length) { dumpFile = args[++i]; } else if (args[i] == "--screenshot" && i + 1 < args.Length) { screenshotPath = args[++i]; } else if (!args[i].StartsWith('-')) { datDir ??= args[i]; } } // Fall back to ACDREAM_DAT_DIR when no positional dat dir was given. datDir ??= Environment.GetEnvironmentVariable("ACDREAM_DAT_DIR"); if (string.IsNullOrWhiteSpace(datDir)) throw new InvalidOperationException( "ui-studio: dat directory required — pass as first arg or set ACDREAM_DAT_DIR."); // Default layout: vitals (0x2100006C), unless a dump slug or markup is requested. if (layoutId is null && markupPath is null && dumpSlug is null) layoutId = 0x2100006Cu; return new StudioOptions(datDir, layoutId, markupPath, dumpSlug, dumpFile, screenshotPath); } /// /// Resolve the dump file path for this session: /// /// if explicitly set. /// Otherwise <solutionRoot>/docs/research/2026-06-25-retail-ui-layout-dump.json. /// /// Returns null when neither the override nor the default file exists. /// public string? ResolveDumpFile() { if (!string.IsNullOrEmpty(DumpFile)) return DumpFile; // Walk up from the App binary to the solution root (same approach as // ConformanceDats.SolutionRoot in the test project). var dir = AppContext.BaseDirectory; while (!string.IsNullOrEmpty(dir)) { var candidate = Path.Combine(dir, "docs", "research", "2026-06-25-retail-ui-layout-dump.json"); if (File.Exists(candidate)) return candidate; dir = Path.GetDirectoryName(dir); } return null; } }