Merge branch 'main' into claude/error-cause-analysis-lqyuxv

This commit is contained in:
2026-07-06 11:45:26 +02:00
committed by GitHub
8 changed files with 271 additions and 2 deletions
+6 -1
View File
@@ -23,4 +23,9 @@ MonoBehaviour:
baseUptime: 60
baseRam: 5
baseCyclesPerTurn: 3
exclusiveCards: []
exclusiveCards:
- {fileID: 11400000, guid: 0a4f1d1d84564f3cad58b8863e941201, type: 2}
- {fileID: 11400000, guid: 14c5d40f866a4cb6b0a6316e01f42891, type: 2}
- {fileID: 11400000, guid: 2a0f50d541f046fc99c8fb6deef4f1aa, type: 2}
- {fileID: 11400000, guid: 3d57857f3f30419c8be4c44914c5abbe, type: 2}
- {fileID: 11400000, guid: 4cae271a0f4146c59aab74e4c9d6d0f8, type: 2}
+1
View File
@@ -306,4 +306,5 @@ MonoBehaviour:
monospaceFont: {fileID: 0}
motdBody: unstable userspace detected; keep a rollback shell open.
distroDatabase: {fileID: 11400000, guid: 4d88ca5446fa4d80b2fbd4b12697cb7c, type: 2}
cardDatabase: {fileID: 11400000, guid: 6a8a9f37c98a4a7a9e67ab15d2d22208, type: 2}
featuredUnitPanel: {}
+4
View File
@@ -17,6 +17,8 @@ namespace KernelPanic.Data
[SerializeField] private int cycleCost;
[SerializeField] private ResolutionTrack resolutionTrack;
[SerializeField] private bool distroExclusive;
[SerializeField] private bool isToken;
[SerializeField, TextArea] private string designNotes;
[SerializeField] private CardDefinition upgradeTarget;
[SerializeField] private List<CardEffectDefinition> effects = new();
@@ -28,6 +30,8 @@ namespace KernelPanic.Data
public int CycleCost => cycleCost;
public ResolutionTrack ResolutionTrack => resolutionTrack;
public bool DistroExclusive => distroExclusive;
public bool IsToken => isToken;
public string DesignNotes => designNotes;
public CardDefinition UpgradeTarget => upgradeTarget;
public IReadOnlyList<CardEffectDefinition> Effects => effects;
}
+19
View File
@@ -60,6 +60,7 @@ namespace KernelPanic.Meta
public bool starterChosen;
public List<string> ownedUnitIds = new();
public List<string> bannerPoolIds = new();
public List<CardLoadoutSaveEntry> cardLoadouts = new();
public static SaveData CreateDefault()
{
@@ -70,6 +71,24 @@ namespace KernelPanic.Meta
{
ownedUnitIds ??= new List<string>();
bannerPoolIds ??= new List<string>();
cardLoadouts ??= new List<CardLoadoutSaveEntry>();
for (int i = 0; i < cardLoadouts.Count; i++)
{
cardLoadouts[i] ??= new CardLoadoutSaveEntry();
cardLoadouts[i].EnsureLists();
}
}
}
[Serializable]
public sealed class CardLoadoutSaveEntry
{
public string distroId;
public List<string> equippedCardIds = new();
public void EnsureLists()
{
equippedCardIds ??= new List<string>();
}
}
}
+127 -1
View File
@@ -37,6 +37,7 @@ namespace KernelPanic.UI
[SerializeField] private FontAsset monospaceFont; // TODO: Assign a real monospace FontAsset when typography assets exist.
[SerializeField] private string motdBody = "unstable userspace detected; keep a rollback shell open.";
[SerializeField] private DistroDatabase distroDatabase;
[SerializeField] private CardDatabase cardDatabase;
[SerializeField] private FeaturedUnitPanel featuredUnitPanel = new();
[SerializeField] private CollectionScreenController collectionScreen = new();
[SerializeField] private StarterSelectionController starterSelection = new();
@@ -48,6 +49,7 @@ namespace KernelPanic.UI
private VisualElement bootIntroPanel;
private VisualElement mainMenuPanel;
private VisualElement collectionPanel;
private VisualElement runSetupPanel;
private VisualElement gachaPanel;
private VisualElement settingsPanel;
private VisualElement eventBanner;
@@ -61,6 +63,9 @@ namespace KernelPanic.UI
private Label motdBodyLabel;
private VisualElement motdBlock;
private Button collectionBackButton;
private Button collectionUnitsButton;
private Button collectionCardsButton;
private Button runSetupBackButton;
private Button gachaBackButton;
private Button settingsBackButton;
private SaveService saveService;
@@ -68,6 +73,7 @@ namespace KernelPanic.UI
private EntropyWallet wallet;
private GachaService gachaService;
private PlayerCollection playerCollection;
private CardLoadout cardLoadout;
private BackgroundLogRingBuffer backgroundLog;
private IEventBannerSource eventBannerSource;
private int selectedCommandIndex;
@@ -76,6 +82,7 @@ namespace KernelPanic.UI
private bool cursorVisible;
private bool suppressNextClick;
private bool warnedUnresolvedSaveId;
private bool warnedInvalidLoadoutId;
private string bootIntroCopy;
private IVisualElementScheduledItem blinkSchedule;
private IVisualElementScheduledItem bootIntroSchedule;
@@ -92,6 +99,7 @@ namespace KernelPanic.UI
saveService = new SaveService();
gachaService = new GachaService();
playerCollection = new PlayerCollection(); // TODO: Replace with persistent player-collection service composition.
cardLoadout = new CardLoadout(playerCollection.OwnedUnits);
Initialize(new EntropyWallet()); // TODO: Replace with persistent wallet service composition.
BindElements();
BindCommandEntries();
@@ -137,6 +145,7 @@ namespace KernelPanic.UI
bootIntroPanel = root.Q<VisualElement>("BootIntroPanel");
mainMenuPanel = root.Q<VisualElement>("MainMenuPanel");
collectionPanel = root.Q<VisualElement>("CollectionPanel");
runSetupPanel = root.Q<VisualElement>("RunSetupPanel");
gachaPanel = root.Q<VisualElement>("GachaPanel");
settingsPanel = root.Q<VisualElement>("SettingsPanel");
eventBanner = root.Q<VisualElement>("EventBanner");
@@ -153,6 +162,9 @@ namespace KernelPanic.UI
motdBodyLabel = root.Q<Label>("MotdBodyLabel");
collectionBackButton = root.Q<Button>("CollectionBackButton");
collectionUnitsButton = root.Q<Button>("CollectionUnitsButton");
collectionCardsButton = root.Q<Button>("CollectionCardsButton");
runSetupBackButton = root.Q<Button>("RunSetupBackButton");
gachaBackButton = root.Q<Button>("GachaBackButton");
settingsBackButton = root.Q<Button>("SettingsBackButton");
}
@@ -182,6 +194,9 @@ namespace KernelPanic.UI
root.RegisterCallback<KeyDownEvent>(HandleKeyDown);
root.RegisterCallback<PointerDownEvent>(HandlePointerDown);
collectionBackButton.clicked += ShowMainMenu;
collectionUnitsButton.clicked += ShowCollectionUnits;
collectionCardsButton.clicked += ShowCollectionCards;
runSetupBackButton.clicked += ShowMainMenu;
gachaBackButton.clicked += ShowMainMenu;
settingsBackButton.clicked += ShowMainMenu;
}
@@ -191,6 +206,9 @@ namespace KernelPanic.UI
root.UnregisterCallback<KeyDownEvent>(HandleKeyDown);
root.UnregisterCallback<PointerDownEvent>(HandlePointerDown);
collectionBackButton.clicked -= ShowMainMenu;
collectionUnitsButton.clicked -= ShowCollectionUnits;
collectionCardsButton.clicked -= ShowCollectionCards;
runSetupBackButton.clicked -= ShowMainMenu;
gachaBackButton.clicked -= ShowMainMenu;
settingsBackButton.clicked -= ShowMainMenu;
}
@@ -217,6 +235,29 @@ namespace KernelPanic.UI
gachaService.AddToBannerPool(unit);
}
}
for (int i = 0; i < saveData.cardLoadouts.Count; i++)
{
CardLoadoutSaveEntry entry = saveData.cardLoadouts[i];
entry.EnsureLists();
List<string> resolvedIds = new();
for (int cardIndex = 0; cardIndex < entry.equippedCardIds.Count; cardIndex++)
{
string resolvedId = ResolveSavedCardId(entry.equippedCardIds[cardIndex]);
if (!string.IsNullOrWhiteSpace(resolvedId))
{
resolvedIds.Add(resolvedId);
}
}
if (!cardLoadout.TryLoad(entry.distroId, resolvedIds, out bool skippedInvalid) || skippedInvalid)
{
WarnInvalidLoadoutId();
}
}
EnsureLoadoutsForOwnedUnits();
SaveCurrentState();
}
private DistroDefinition ResolveSavedDistro(string id)
@@ -231,8 +272,37 @@ namespace KernelPanic.UI
return unit;
}
private string ResolveSavedCardId(string id)
{
if (cardDatabase == null)
{
return id;
}
CardDefinition card = cardDatabase.FindById(id);
if (card == null)
{
WarnInvalidLoadoutId();
return null;
}
return card.Id;
}
private void WarnInvalidLoadoutId()
{
if (warnedInvalidLoadoutId)
{
return;
}
warnedInvalidLoadoutId = true;
Debug.LogWarning("Save references an invalid card loadout id; restoring a valid default loadout.");
}
private void HandleMetaStateChanged()
{
EnsureLoadoutsForOwnedUnits();
SaveCurrentState();
featuredUnitPanel.Refresh(playerCollection.OwnedUnits);
collectionScreen.Refresh(playerCollection.OwnedUnits);
@@ -277,6 +347,8 @@ namespace KernelPanic.UI
}
}
cardLoadout.WriteTo(saveData.cardLoadouts);
saveService.Save(saveData);
}
@@ -416,6 +488,30 @@ namespace KernelPanic.UI
return;
}
if (IsRunSetupVisible())
{
if (evt.keyCode == KeyCode.UpArrow)
{
SelectPackage(selectedPackageIndex - 1);
evt.StopPropagation();
return;
}
if (evt.keyCode == KeyCode.DownArrow)
{
SelectPackage(selectedPackageIndex + 1);
evt.StopPropagation();
return;
}
if (evt.keyCode == KeyCode.Return || evt.keyCode == KeyCode.KeypadEnter)
{
ToggleSelectedPackage();
evt.StopPropagation();
return;
}
}
if (evt.keyCode == KeyCode.UpArrow)
{
SelectCommand(selectedCommandIndex - 1);
@@ -453,6 +549,22 @@ namespace KernelPanic.UI
}
}
private bool IsRunSetupVisible()
{
return runSetupPanel != null && !runSetupPanel.ClassListContains(HiddenClassName);
}
private void SelectPackage(int index)
{
if (packageRows.Count == 0)
{
return;
}
selectedPackageIndex = (index + packageRows.Count) % packageRows.Count;
RefreshPackageSelection();
}
private void HandlePointerDown(PointerDownEvent evt)
{
root.Focus();
@@ -486,7 +598,8 @@ namespace KernelPanic.UI
private void HandleStartRunClicked()
{
SceneLoader.LoadGame();
RefreshRunSetup();
ShowPanel(runSetupPanel);
}
private void HandleQuitClicked()
@@ -510,6 +623,18 @@ namespace KernelPanic.UI
ShowPanel(collectionPanel);
}
private void ShowCollectionUnits()
{
collectionShowingCards = false;
RefreshCollection();
}
private void ShowCollectionCards()
{
collectionShowingCards = true;
RefreshCollection();
}
private void ShowGacha()
{
ShowPanel(gachaPanel);
@@ -525,6 +650,7 @@ namespace KernelPanic.UI
{
mainMenuPanel.EnableInClassList(HiddenClassName, mainMenuPanel != activePanel);
collectionPanel.EnableInClassList(HiddenClassName, collectionPanel != activePanel);
runSetupPanel.EnableInClassList(HiddenClassName, runSetupPanel != activePanel);
gachaPanel.EnableInClassList(HiddenClassName, gachaPanel != activePanel);
settingsPanel.EnableInClassList(HiddenClassName, settingsPanel != activePanel);
}
+4
View File
@@ -1,6 +1,10 @@
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="False">
<ui:VisualElement name="CollectionPanel" class="sub-panel hidden">
<ui:Label text="COLLECTION" class="screen-title" />
<ui:VisualElement class="collection-actions">
<ui:Button name="CollectionUnitsButton" text="units" class="terminal-button collection-action-button" />
<ui:Button name="CollectionCardsButton" text="cards" class="terminal-button collection-action-button" />
</ui:VisualElement>
<ui:VisualElement name="CollectionContent" class="collection-shell">
<ui:ScrollView name="CollectionList" class="collection-list" />
<ui:VisualElement name="CollectionDetail" class="collection-detail" />
+101
View File
@@ -481,6 +481,31 @@ Button {
flex-direction: row;
}
.collection-actions {
width: 760px;
flex-direction: row;
justify-content: flex-end;
margin-bottom: 10px;
}
.collection-action-button {
width: 110px;
height: 34px;
margin-top: 0;
margin-left: 8px;
}
.collection-action-button.selected {
background-color: var(--kp-primary);
color: var(--kp-bg);
}
.run-setup-hint {
width: 760px;
margin-top: -12px;
margin-bottom: 12px;
}
.collection-list {
width: 300px;
margin-right: 16px;
@@ -552,6 +577,82 @@ Button {
margin-top: 14px;
}
.package-list {
margin-top: 18px;
min-width: 0;
}
.package-header {
color: var(--kp-muted);
margin-bottom: 6px;
}
.package-notice {
color: var(--kp-amber);
margin-bottom: 8px;
white-space: normal;
}
.package-empty {
color: var(--kp-muted);
margin-top: 6px;
}
.package-row {
min-width: 0;
padding: 7px 10px;
margin-top: 4px;
border-left-width: 2px;
border-left-color: transparent;
}
.package-row.selected,
.package-row.equipped {
border-left-color: var(--kp-primary);
}
.package-row.selected {
background-color: var(--kp-hover);
}
.package-row.dimmed {
opacity: 0.45;
}
.package-summary {
min-width: 0;
flex-direction: row;
}
.package-marker {
width: 34px;
min-width: 34px;
}
.package-name {
flex-grow: 1;
min-width: 0;
color: var(--kp-primary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
-unity-text-overflow-position: end;
}
.package-meta {
min-width: 96px;
color: var(--kp-muted);
-unity-text-align: upper-right;
}
.package-description {
min-width: 0;
color: var(--kp-muted);
white-space: normal;
margin-left: 34px;
margin-top: 2px;
}
.modal-overlay {
position: absolute;
left: 0;
+9
View File
@@ -124,6 +124,15 @@
</ui:VisualElement>
<ui:Instance template="CollectionScreen" />
<ui:VisualElement name="RunSetupPanel" class="sub-panel hidden">
<ui:Label text="NEW RUN" class="screen-title" />
<ui:Label text="choose 4 distro cards before booting a run" class="collection-detail-muted run-setup-hint" />
<ui:VisualElement name="RunSetupContent" class="collection-shell">
<ui:ScrollView name="RunSetupList" class="collection-list" />
<ui:VisualElement name="RunSetupDetail" class="collection-detail" />
</ui:VisualElement>
<ui:Button name="RunSetupBackButton" text="Back" class="terminal-button back-button" />
</ui:VisualElement>
<ui:Instance template="GachaScreen" />
<ui:Instance template="SettingsScreen" />