diff --git a/Assets/Data/Distros.meta b/Assets/Data/Distros.meta new file mode 100644 index 0000000..9fd6c94 --- /dev/null +++ b/Assets/Data/Distros.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b5d4e612df874934a2fe5e4ce5cf43de +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Data/Distros/Art.meta b/Assets/Data/Distros/Art.meta new file mode 100644 index 0000000..d7a6f11 --- /dev/null +++ b/Assets/Data/Distros/Art.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 296f57316cdb4f9ba2227b877b091c23 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Data/Distros/Art/fedora.txt b/Assets/Data/Distros/Art/fedora.txt new file mode 100644 index 0000000..88e22d5 --- /dev/null +++ b/Assets/Data/Distros/Art/fedora.txt @@ -0,0 +1,16 @@ + %#%#%#%#%# + %#%#%#%%#%#%#%#%#% + %#%#%%#%#%#% *%#% + #%#%%#%#%#% ***%# + %%#%#%#%%#% %#%#%#****%# + %#%#%#%#%#% %#%%#%%***%%# +#%#%%#%#%#%# %#%#%#%***%#%% +#%%#%#%%#%#% %#%#%****#%#%# +%#%#**** ****%#%#%# +%#%****%#%#% %#%#%%%#%#%%#% +#%***%%#%#%# %#%%#%#%#%#%# +%#***#%#%%#% %#%#%#%#%#%# +%%****%%#%#% #%#%#%%#%#%# +#%%*** %%#%#%#%#%%# +%#%#%* %#%#%%#%#%# * + %#%#%#%#%#%#%#%%#%# diff --git a/Assets/Data/Distros/Art/fedora.txt.meta b/Assets/Data/Distros/Art/fedora.txt.meta new file mode 100644 index 0000000..ccb460c --- /dev/null +++ b/Assets/Data/Distros/Art/fedora.txt.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: f2373174b6954f2d9088d83d10e0d9e4 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Data/Distros/Art/mint.txt b/Assets/Data/Distros/Art/mint.txt new file mode 100644 index 0000000..9eb5bc0 --- /dev/null +++ b/Assets/Data/Distros/Art/mint.txt @@ -0,0 +1,16 @@ + + :======: + ============== + .==================. + === ================= + ==== === ==== + ==== ==: == ..== .==== + ===== .==: == .== ===== + ===== ==: == .== ===== + ==== ==: == ..== .==== + ==== ========== ==== + ===== .-==== + .==================. + ============== + :======: + diff --git a/Assets/Data/Distros/Art/mint.txt.meta b/Assets/Data/Distros/Art/mint.txt.meta new file mode 100644 index 0000000..1c8a843 --- /dev/null +++ b/Assets/Data/Distros/Art/mint.txt.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 412c9be402ec4d8c8510e71066ec22ff +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Data/Distros/Art/ubuntu.txt b/Assets/Data/Distros/Art/ubuntu.txt new file mode 100644 index 0000000..3cf5c3e --- /dev/null +++ b/Assets/Data/Distros/Art/ubuntu.txt @@ -0,0 +1,16 @@ + + ============ + ================== + ============== ===== + ========= = ====== + ======= ===== ======= + ====== ======== ====== + == = ========== ===== + == = ========== ===== + ====== ======== ====== + ======= ===== ======= + ========= = ====== + ============== ===== + ================== + ============ + diff --git a/Assets/Data/Distros/Art/ubuntu.txt.meta b/Assets/Data/Distros/Art/ubuntu.txt.meta new file mode 100644 index 0000000..fa21f82 --- /dev/null +++ b/Assets/Data/Distros/Art/ubuntu.txt.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 975f1f5de289435eb81c91c3eddd31dc +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Data/Distros/DistroDatabase.asset b/Assets/Data/Distros/DistroDatabase.asset new file mode 100644 index 0000000..a8d085e --- /dev/null +++ b/Assets/Data/Distros/DistroDatabase.asset @@ -0,0 +1,18 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5af93d291e7d4d51b312bdf1e47dc6ef, type: 3} + m_Name: DistroDatabase + m_EditorClassIdentifier: + allDistros: + - {fileID: 11400000, guid: 1b0892a0d4c34b03a54c3d511ae59356, type: 2} + - {fileID: 11400000, guid: 245dfb73afe047d5a3a3ef54c30d5135, type: 2} + - {fileID: 11400000, guid: 366e71361387449abcfaa1a50c9af87b, type: 2} diff --git a/Assets/Data/Distros/DistroDatabase.asset.meta b/Assets/Data/Distros/DistroDatabase.asset.meta new file mode 100644 index 0000000..6453a4c --- /dev/null +++ b/Assets/Data/Distros/DistroDatabase.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4d88ca5446fa4d80b2fbd4b12697cb7c +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Data/Distros/Fedora.asset b/Assets/Data/Distros/Fedora.asset new file mode 100644 index 0000000..69cf57b --- /dev/null +++ b/Assets/Data/Distros/Fedora.asset @@ -0,0 +1,26 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: bd550368fd0c75d43a53aa4fb3b6541e, type: 3} + m_Name: Fedora + m_EditorClassIdentifier: + id: fedora + displayName: Fedora + primaryLanguage: 2 + secondaryLanguage: 8 + passiveName: Bleeding Edge + description: First to every new feature. Occasionally first to the bugs that come with it. + asciiArt: {fileID: 4900000, guid: f2373174b6954f2d9088d83d10e0d9e4, type: 3} + accentColor: {r: 0.23529412, g: 0.43137255, b: 0.7058824, a: 1} + baseUptime: 55 + baseRam: 5 + baseCyclesPerTurn: 3 + exclusiveCards: [] diff --git a/Assets/Data/Distros/Fedora.asset.meta b/Assets/Data/Distros/Fedora.asset.meta new file mode 100644 index 0000000..dda654c --- /dev/null +++ b/Assets/Data/Distros/Fedora.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 245dfb73afe047d5a3a3ef54c30d5135 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Data/Distros/Mint.asset b/Assets/Data/Distros/Mint.asset new file mode 100644 index 0000000..2cb1df9 --- /dev/null +++ b/Assets/Data/Distros/Mint.asset @@ -0,0 +1,26 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: bd550368fd0c75d43a53aa4fb3b6541e, type: 3} + m_Name: Mint + m_EditorClassIdentifier: + id: mint + displayName: Mint + primaryLanguage: 3 + secondaryLanguage: 4 + passiveName: It Just Works + description: Comfort incarnate. No surprises, no drama, ever. + asciiArt: {fileID: 4900000, guid: 412c9be402ec4d8c8510e71066ec22ff, type: 3} + accentColor: {r: 0.5294118, g: 0.8117647, b: 0.24313726, a: 1} + baseUptime: 65 + baseRam: 5 + baseCyclesPerTurn: 3 + exclusiveCards: [] diff --git a/Assets/Data/Distros/Mint.asset.meta b/Assets/Data/Distros/Mint.asset.meta new file mode 100644 index 0000000..0b6c5ea --- /dev/null +++ b/Assets/Data/Distros/Mint.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 366e71361387449abcfaa1a50c9af87b +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Data/Distros/Ubuntu.asset b/Assets/Data/Distros/Ubuntu.asset new file mode 100644 index 0000000..ba174de --- /dev/null +++ b/Assets/Data/Distros/Ubuntu.asset @@ -0,0 +1,26 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: bd550368fd0c75d43a53aa4fb3b6541e, type: 3} + m_Name: Ubuntu + m_EditorClassIdentifier: + id: ubuntu + displayName: Ubuntu + primaryLanguage: 3 + secondaryLanguage: 4 + passiveName: Long Term Support + description: The friendly one. Everything works out of the box, and she will not stop helping. + asciiArt: {fileID: 4900000, guid: 975f1f5de289435eb81c91c3eddd31dc, type: 3} + accentColor: {r: 0.9137255, g: 0.32941177, b: 0.1254902, a: 1} + baseUptime: 60 + baseRam: 5 + baseCyclesPerTurn: 3 + exclusiveCards: [] diff --git a/Assets/Data/Distros/Ubuntu.asset.meta b/Assets/Data/Distros/Ubuntu.asset.meta new file mode 100644 index 0000000..76aaed8 --- /dev/null +++ b/Assets/Data/Distros/Ubuntu.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1b0892a0d4c34b03a54c3d511ae59356 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scenes/MainMenuScene.unity b/Assets/Scenes/MainMenuScene.unity index c8a1d4a..27ca081 100644 --- a/Assets/Scenes/MainMenuScene.unity +++ b/Assets/Scenes/MainMenuScene.unity @@ -305,4 +305,5 @@ MonoBehaviour: m_EditorClassIdentifier: monospaceFont: {fileID: 0} motdBody: unstable userspace detected; keep a rollback shell open. + distroDatabase: {fileID: 11400000, guid: 4d88ca5446fa4d80b2fbd4b12697cb7c, type: 2} featuredUnitPanel: {} diff --git a/Assets/Scripts/Data/DistroDatabase.cs b/Assets/Scripts/Data/DistroDatabase.cs new file mode 100644 index 0000000..d388af3 --- /dev/null +++ b/Assets/Scripts/Data/DistroDatabase.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace KernelPanic.Data +{ + /// + /// Resolves persisted distro ids to playable distro definitions. + /// + public sealed class DistroDatabase : ScriptableObject + { + [SerializeField] private List allDistros = new(); + + public IReadOnlyList AllDistros => allDistros; + + public DistroDefinition FindById(string id) + { + if (string.IsNullOrWhiteSpace(id)) + { + return null; + } + + for (int i = 0; i < allDistros.Count; i++) + { + DistroDefinition distro = allDistros[i]; + if (distro != null && string.Equals(distro.Id, id, StringComparison.OrdinalIgnoreCase)) + { + return distro; + } + } + + return null; + } + } +} diff --git a/Assets/Scripts/Data/DistroDatabase.cs.meta b/Assets/Scripts/Data/DistroDatabase.cs.meta new file mode 100644 index 0000000..edaf02c --- /dev/null +++ b/Assets/Scripts/Data/DistroDatabase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5af93d291e7d4d51b312bdf1e47dc6ef +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Data/DistroDefinition.cs b/Assets/Scripts/Data/DistroDefinition.cs index ff6f480..8adda18 100644 --- a/Assets/Scripts/Data/DistroDefinition.cs +++ b/Assets/Scripts/Data/DistroDefinition.cs @@ -14,6 +14,8 @@ namespace KernelPanic.Data [SerializeField] private Language primaryLanguage; [SerializeField] private Language secondaryLanguage; [SerializeField] private string passiveName; + [SerializeField, TextArea] private string description; + [SerializeField] private TextAsset asciiArt; [SerializeField] private Color accentColor = new(0.36f, 1f, 0.57f); [SerializeField] private int baseUptime; [SerializeField] private int baseRam; @@ -25,6 +27,8 @@ namespace KernelPanic.Data public Language PrimaryLanguage => primaryLanguage; public Language SecondaryLanguage => secondaryLanguage; public string PassiveName => passiveName; + public string Description => description; + public TextAsset AsciiArt => asciiArt; public Color AccentColor => accentColor; public int BaseUptime => baseUptime; public int BaseRam => baseRam; diff --git a/Assets/Scripts/Meta/GachaService.cs b/Assets/Scripts/Meta/GachaService.cs index 3c16949..4236f45 100644 --- a/Assets/Scripts/Meta/GachaService.cs +++ b/Assets/Scripts/Meta/GachaService.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using KernelPanic.Data; namespace KernelPanic.Meta { @@ -7,7 +9,23 @@ namespace KernelPanic.Meta /// public sealed class GachaService { + private readonly List bannerPool = new(); + public int PullTokens => 0; // TODO: Replace with the real pull-token wallet when that meta system exists. + public IReadOnlyList BannerPool => bannerPool; + + public event Action BannerPoolChanged; + + public void AddToBannerPool(DistroDefinition distro) + { + if (distro == null || bannerPool.Contains(distro)) + { + return; + } + + bannerPool.Add(distro); + BannerPoolChanged?.Invoke(); + } public GachaPullResult PerformPull(EntropyWallet wallet, int cost) { diff --git a/Assets/Scripts/Meta/PlayerCollection.cs b/Assets/Scripts/Meta/PlayerCollection.cs new file mode 100644 index 0000000..61dee38 --- /dev/null +++ b/Assets/Scripts/Meta/PlayerCollection.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using KernelPanic.Data; +using System; + +namespace KernelPanic.Meta +{ + /// + /// Tracks player-owned persistent unlocks for menu presentation and collection views. + /// + public sealed class PlayerCollection + { + private readonly List ownedUnits = new(); + + // TODO: Populate owned units from SaveService during bootstrap. + // TODO: Add newly unlocked units from GachaService rewards when pulls exist. + public IReadOnlyList OwnedUnits => ownedUnits; + + public event Action Changed; + + public void Add(DistroDefinition unit) + { + if (unit == null || ownedUnits.Contains(unit)) + { + return; + } + + // Persistence is wired by the owner so this domain class stays IO-free. + ownedUnits.Add(unit); + Changed?.Invoke(); + } + } +} diff --git a/Assets/Scripts/Meta/PlayerCollection.cs.meta b/Assets/Scripts/Meta/PlayerCollection.cs.meta new file mode 100644 index 0000000..eeac422 --- /dev/null +++ b/Assets/Scripts/Meta/PlayerCollection.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 4a10ff65b6e9478d9fe638f8a39f8144 diff --git a/Assets/Scripts/Meta/SaveService.cs b/Assets/Scripts/Meta/SaveService.cs index 76fc6cf..84c0229 100644 --- a/Assets/Scripts/Meta/SaveService.cs +++ b/Assets/Scripts/Meta/SaveService.cs @@ -1,4 +1,7 @@ using System; +using System.Collections.Generic; +using System.IO; +using UnityEngine; namespace KernelPanic.Meta { @@ -7,22 +10,66 @@ namespace KernelPanic.Meta /// public sealed class SaveService { + private const string SaveFileName = "kernel-panic-save.json"; + + public string SavePath => Path.Combine(Application.persistentDataPath, SaveFileName); + public void Save(SaveData data) { - throw new NotImplementedException(); + data ??= new SaveData(); + data.EnsureLists(); + + string directory = Path.GetDirectoryName(SavePath); + if (!string.IsNullOrEmpty(directory)) + { + Directory.CreateDirectory(directory); + } + + string json = JsonUtility.ToJson(data, true); + File.WriteAllText(SavePath, json); } public SaveData Load() { - throw new NotImplementedException(); + if (!File.Exists(SavePath)) + { + return SaveData.CreateDefault(); + } + + try + { + SaveData data = JsonUtility.FromJson(File.ReadAllText(SavePath)); + data ??= SaveData.CreateDefault(); + data.EnsureLists(); + return data; + } + catch (Exception ex) + { + Debug.LogWarning($"Save file could not be loaded; starting fresh. {ex.Message}"); + return SaveData.CreateDefault(); + } } } /// /// Stores persistent progression data for serialization. /// + [Serializable] public sealed class SaveData { - public int EntropyBalance { get; set; } + public bool starterChosen; + public List ownedUnitIds = new(); + public List bannerPoolIds = new(); + + public static SaveData CreateDefault() + { + return new SaveData(); + } + + public void EnsureLists() + { + ownedUnitIds ??= new List(); + bannerPoolIds ??= new List(); + } } } diff --git a/Assets/Scripts/UI/AsciiArtFitter.cs b/Assets/Scripts/UI/AsciiArtFitter.cs new file mode 100644 index 0000000..2953fbc --- /dev/null +++ b/Assets/Scripts/UI/AsciiArtFitter.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.TextCore.Text; +using UnityEngine.UIElements; + +namespace KernelPanic.UI +{ + /// + /// Fits monospace ASCII art into a fixed UI Toolkit label region. + /// + public sealed class AsciiArtFitter + { + private const float ReferenceFontSize = 20f; + private const float LineHeightRatio = 1.05f; + private const float MinFontSize = 6f; + private const float MaxFontSize = 24f; + private const float MonospaceTolerance = 0.02f; + private static readonly Dictionary AdvanceRatioByFont = new(); + private static readonly HashSet WarnedFonts = new(); + private static bool hasDefaultAdvanceRatio; + private static float defaultAdvanceRatio; + + private readonly Label label; + private readonly FontAsset font; + private string art; + + public AsciiArtFitter(Label label, FontAsset font) + { + this.label = label; + this.font = font; + label.RegisterCallback(_ => Fit()); + } + + public void SetArt(string value) + { + art = value; + Fit(); + } + + private void Fit() + { + if (string.IsNullOrWhiteSpace(art)) + { + return; + } + + Rect region = label.layout; + if (!IsUsable(region.width) || !IsUsable(region.height)) + { + return; + } + + GetArtDimensions(art, out int rows, out int columns); + if (rows <= 0 || columns <= 0) + { + return; + } + + float advanceRatio = GetAdvanceRatio(); + float widthFit = region.width / (columns * advanceRatio); + float heightFit = region.height / (rows * LineHeightRatio); + float fontSize = Mathf.Clamp(Mathf.Floor(Mathf.Min(widthFit, heightFit)), MinFontSize, MaxFontSize); + float blockWidth = columns * fontSize * advanceRatio; + float blockHeight = rows * fontSize * LineHeightRatio; + + label.style.fontSize = fontSize; + label.style.paddingLeft = Mathf.Max(0f, (region.width - blockWidth) * 0.5f); + label.style.paddingTop = Mathf.Max(0f, (region.height - blockHeight) * 0.5f); + label.style.paddingRight = 0f; + label.style.paddingBottom = 0f; + } + + private float GetAdvanceRatio() + { + if (font == null && hasDefaultAdvanceRatio) + { + return defaultAdvanceRatio; + } + + if (font != null && AdvanceRatioByFont.TryGetValue(font, out float cached)) + { + return cached; + } + + float previousFontSize = label.resolvedStyle.fontSize; + label.style.fontSize = ReferenceFontSize; + + float mWidth = label.MeasureTextSize("MMMMMMMMMM", 0f, VisualElement.MeasureMode.Undefined, 0f, VisualElement.MeasureMode.Undefined).x; + float iWidth = label.MeasureTextSize("iiiiiiiiii", 0f, VisualElement.MeasureMode.Undefined, 0f, VisualElement.MeasureMode.Undefined).x; + label.style.fontSize = previousFontSize; + + float advanceRatio = mWidth > 0f ? mWidth / (10f * ReferenceFontSize) : 0.6f; + if (font == null) + { + defaultAdvanceRatio = advanceRatio; + hasDefaultAdvanceRatio = true; + } + else + { + AdvanceRatioByFont[font] = advanceRatio; + } + + if (font != null && mWidth > 0f && iWidth > 0f) + { + float difference = Mathf.Abs(mWidth - iWidth) / Mathf.Max(mWidth, iWidth); + if (difference > MonospaceTolerance && WarnedFonts.Add(font)) + { + Debug.LogWarning($"ASCII art font '{font.name}' is not monospace; art may misalign."); + } + } + + return advanceRatio; + } + + private static void GetArtDimensions(string value, out int rows, out int columns) + { + string normalized = value.Replace("\r\n", "\n").Replace('\r', '\n'); + if (normalized.EndsWith("\n", StringComparison.Ordinal)) + { + normalized = normalized[..^1]; + } + + rows = 0; + columns = 0; + string[] lines = normalized.Split('\n'); + for (int i = 0; i < lines.Length; i++) + { + rows++; + columns = Mathf.Max(columns, lines[i].Length); + } + } + + private static bool IsUsable(float value) + { + return value > 0f && !float.IsNaN(value) && !float.IsInfinity(value); + } + } +} diff --git a/Assets/Scripts/UI/AsciiArtFitter.cs.meta b/Assets/Scripts/UI/AsciiArtFitter.cs.meta new file mode 100644 index 0000000..71deae5 --- /dev/null +++ b/Assets/Scripts/UI/AsciiArtFitter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6f677d4ac6de4dca8e92a3efa687ff1b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/UI/BackgroundLogRingBuffer.cs b/Assets/Scripts/UI/BackgroundLogRingBuffer.cs new file mode 100644 index 0000000..4ea252d --- /dev/null +++ b/Assets/Scripts/UI/BackgroundLogRingBuffer.cs @@ -0,0 +1,134 @@ +using System; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.UIElements; + +namespace KernelPanic.UI +{ + /// + /// Keeps the ambient boot-log background filled by rewriting a fixed stack of labels. + /// + public sealed class BackgroundLogRingBuffer + { + private const float LineHeight = 18f; + private const int ExtraLines = 2; + private const int TickMilliseconds = 450; + + private readonly VisualElement layer; + private readonly IReadOnlyList sourceLines; + private readonly List