From 0258e9f106d3931e2168b74654ae669e3924fc03 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Tue, 16 Jun 2026 01:00:40 +0100 Subject: [PATCH] Route weapon groups through checkpoints --- .voidbot/state/aetheria.cc | Bin 142144 -> 142533 bytes Aetheria.State.Verify/Program.cs | 46 ++++++++++++++++++ Assets/Scripts/Gameplay/ActionGameManager.cs | 31 ++++++++++++ .../Scripts/UI/Menu/WeaponGroupAssignment.cs | 15 ++---- docs/aetheria-perfect-machine-map.md | 9 +++- 5 files changed, 88 insertions(+), 13 deletions(-) diff --git a/.voidbot/state/aetheria.cc b/.voidbot/state/aetheria.cc index 5d18650cac3af76d058c06050a49392a537b0840..7aa175586f2e718d308933078d24c4cc7b7e1ace 100644 GIT binary patch delta 1261 zcmaJ>O=wj|6lPvhdAhMBJf*1QRJu@j?^=_WM6(d0gph?`1H}*&ncREcy+iMvxz5ae zk5?#B+z3iR9(LSxVOuIlh0=x3m2DQ;3sNB~_ij|W?8b9{64O@Q-8nPoeCIpgch8^C z>KmK&-+rm>nVOz$PkYl7Gb^`PW3W-XQAK7>P81O}@=X;v**;N3>Z9YegV(Pe9UUGW zWp|g4EX|>fPzw%PWflYIAy1Wrkd7&EV?-zul-B)yflaU%gZ0;qHINEoB}3G}gTNfq zn5Zrqn7^EfG{s=iVggFT`LYycYeCl?trD1*R@lB*UM=Jx1_Ghcw{hQtMGHQUV*_)( z6-qAf1kZBG3CTO7l0?`%?>JrLiDxlcMQtLHHlY^@KLXX029oVCB$?%T9rx%W`+Uv2 zXHgnDZa%I$`!7i#CR|Vr4%xz2i{LA2UByjU%`6ZN>Ku&Zsfm;|kP^5swxU^b@nBB& zfl^a$Bd`&-U{&TvM1QeBqy93=F_!lUMdRfRjio_mR6U0IE)r;xa2bHfbcg$ts4V$kXp{%I9+bEi0)I8;U|bM6q@kGKroP0m_- zy~afGQ(F()<-@cEU01jS#5pyR%~i=vXqF|NoQdpQG*4hMue}Q6UC04%Ky*sb4+U3pr@qOR>-r;fX(@}2kopW-u zaCNMhFN_SAc1c(D(AnvzN5%@t-Ua7;HEuJFq{2{|lxy#G&b2-6Z?E;Ylh;fAx2L%A zc_b)ADmL5&Avd`+Dyjf}gsDKpB`tHE{JKj<7Qf9mo^Mjg1CR;=B|RPi)mnH`!-^|( z7|;rLXVd!pb*u(gM4XwRl81$~9R_HiJPnKnLGogmbYIHSZT0|c`GWaSjtwUSAT+FUAw6KR!BU2)C<)?SuMB`zKxGBiViQNar7>m3)*2NvKdtpSTMw0x zR$FcP0Ah?0(JF69ZhX|<^T9*zR=UC2O%6p*@ApXOHV(J@5N`cPMJ;m| z=LcY&^UyM&8VrA6289d}Ia5J^GFeng+6~Or$AH?xeYJ@Dk2($p)bbSRH)7mE(_tC1%rD7LMtaXSpEC2M&a58nk5*qzp6b0ejbsBMEe@t4x9N+_ zz{yjWgp+gAD6248>(rV5F&Xl6VI$@-QpvQwVl7o$;-rftXFM|5ncdlVak%u>P){ex zvPX7nCw%f|zkY-GR{he}ovUADG5HG4QR6;~MSGt+_1l~n8nL&Tl=_^9&Cs|F)&BH3 L2OW*r@KEV5<)>{P diff --git a/Aetheria.State.Verify/Program.cs b/Aetheria.State.Verify/Program.cs index b1007f4c4..772ba2cfc 100644 --- a/Aetheria.State.Verify/Program.cs +++ b/Aetheria.State.Verify/Program.cs @@ -17,6 +17,7 @@ RequirePropertiesPanelReadOnlyInspector(root); RequireRuntimeSimulationTuningCommits(root); RequireHullConductivityCommitAuthority(root); RequireInventoryEntityRenameCommitAuthority(root); +RequireWeaponGroupCommitAuthority(root); await using var node = await AetheriaStateNode.OpenAsync(statePath, "aetheria-state-verify"); @@ -463,6 +464,7 @@ Console.WriteLine("PropertiesPanel inspector authority: reflection inspection is Console.WriteLine("Runtime simulation tuning authority: UI writes flow through gameplay checkpoint commits"); Console.WriteLine("Hull conductivity authority: inventory UI toggles flow through gameplay checkpoint commits"); Console.WriteLine("Inventory entity rename authority: UI rename flows through gameplay checkpoint commits"); +Console.WriteLine("Weapon group authority: UI assignment flows through gameplay checkpoint commits"); static void RequireCount(AetheriaMigrationLedger ledger, string documentType, int actual) { @@ -871,6 +873,50 @@ static void RequireInventoryEntityRenameCommitAuthority(string root) } } +static void RequireWeaponGroupCommitAuthority(string root) +{ + var actionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs"); + var actionGameManager = File.Exists(actionGameManagerPath) + ? File.ReadAllText(actionGameManagerPath) + : throw new InvalidOperationException("Cannot verify weapon group authority; ActionGameManager.cs is missing."); + + if (!actionGameManager.Contains("CommitWeaponGroupMembership", StringComparison.Ordinal) || + !actionGameManager.Contains("QueueRunCheckpoint(\"weapon-group-membership\")", StringComparison.Ordinal)) + { + throw new InvalidOperationException( + "Weapon group membership no longer has a gameplay-owned checkpoint commit primitive."); + } + + var assignmentPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "WeaponGroupAssignment.cs"); + var assignment = File.Exists(assignmentPath) + ? File.ReadAllText(assignmentPath) + : throw new InvalidOperationException("Cannot verify weapon group authority; WeaponGroupAssignment.cs is missing."); + + var forbiddenSymbols = new[] + { + ".WeaponGroups[i1].items.Add", + ".WeaponGroups[i1].items.Remove", + ".WeaponGroups[i1].weapons.Add", + ".WeaponGroups[i1].weapons.Remove" + }; + + var hits = forbiddenSymbols + .Where(symbol => assignment.Contains(symbol, StringComparison.Ordinal)) + .ToArray(); + + if (hits.Length > 0) + { + throw new InvalidOperationException( + "WeaponGroupAssignment still mutates weapon groups directly: " + + string.Join(", ", hits)); + } + + if (!assignment.Contains("CommitWeaponGroupMembership", StringComparison.Ordinal)) + { + throw new InvalidOperationException("WeaponGroupAssignment no longer routes membership changes through ActionGameManager."); + } +} + static string ExtractMethodBody(string source, string methodName) { var signature = "private void " + methodName + "()"; diff --git a/Assets/Scripts/Gameplay/ActionGameManager.cs b/Assets/Scripts/Gameplay/ActionGameManager.cs index 34585bae8..8c51d2153 100644 --- a/Assets/Scripts/Gameplay/ActionGameManager.cs +++ b/Assets/Scripts/Gameplay/ActionGameManager.cs @@ -1424,6 +1424,37 @@ public class ActionGameManager : MonoBehaviour QueueRunCheckpoint("entity-name"); } + public bool CommitWeaponGroupMembership(EquippedItem item, int groupIndex, bool assigned) + { + if (item?.Entity?.WeaponGroups == null || + groupIndex < 0 || + groupIndex >= item.Entity.WeaponGroups.Length) + { + return false; + } + + var weapon = item.GetBehavior(); + if (weapon == null) + return false; + + var group = item.Entity.WeaponGroups[groupIndex]; + if (assigned) + { + if (!group.items.Contains(item)) + group.items.Add(item); + if (!group.weapons.Contains(weapon)) + group.weapons.Add(weapon); + } + else + { + group.items.Remove(item); + group.weapons.Remove(weapon); + } + + QueueRunCheckpoint("weapon-group-membership"); + return true; + } + public void CommitEquippedItemOverrideShutdown(EquippedItem item, bool enabled) { if (item?.EquippableItem == null) diff --git a/Assets/Scripts/UI/Menu/WeaponGroupAssignment.cs b/Assets/Scripts/UI/Menu/WeaponGroupAssignment.cs index ddd453d88..874a96368 100644 --- a/Assets/Scripts/UI/Menu/WeaponGroupAssignment.cs +++ b/Assets/Scripts/UI/Menu/WeaponGroupAssignment.cs @@ -33,18 +33,9 @@ public class WeaponGroupAssignment : MonoBehaviour group.Label.color = groupContainsItem() ? ActiveColor : InactiveColor; group.Button.onClick.AddListener(() => { - if (groupContainsItem()) - { - item.Entity.WeaponGroups[i1].items.Remove(item); - item.Entity.WeaponGroups[i1].weapons.Remove(weapon); - group.Label.color = InactiveColor; - } - else - { - item.Entity.WeaponGroups[i1].items.Add(item); - item.Entity.WeaponGroups[i1].weapons.Add(weapon); - group.Label.color = ActiveColor; - } + var assigned = !groupContainsItem(); + if (ActionGameManager.Instance.CommitWeaponGroupMembership(item, i1, assigned)) + group.Label.color = assigned ? ActiveColor : InactiveColor; }); group.BeginDragTrigger.OnBeginDragAsObservable() diff --git a/docs/aetheria-perfect-machine-map.md b/docs/aetheria-perfect-machine-map.md index aca08c753..949277752 100644 --- a/docs/aetheria-perfect-machine-map.md +++ b/docs/aetheria-perfect-machine-map.md @@ -1384,6 +1384,11 @@ First Aetheria surfaces to publish: `ActionGameManager.CommitEntityName`. `InventoryPanel` may collect the name in a dialog, but gameplay owns the entity `Name` mutation and queues the typed entity snapshot checkpoint. + - Done: route weapon group membership changes through + `ActionGameManager.CommitWeaponGroupMembership`. `WeaponGroupAssignment` + remains a uGUI control for now, but it no longer adds/removes items or + weapons from `Entity.WeaponGroups` directly; gameplay owns the membership + mutation and typed checkpoint. - Replace the next concrete uGUI screen with an Eve-owned surface. - Move the staged packages into the Eve repo once its worktree is clean, then import them back into Aetheria from Eve instead of carrying a local copy. @@ -1703,7 +1708,9 @@ gameplay-owned commit methods that queue typed checkpoint state; direct UI mutation of entity/item/behavior settings is obsolete authority. Hull conductivity changes follow the same rule: UI requests the toggle, gameplay owns the grid mutation and checkpoint. Entity renames follow the same rule: UI -collects text, gameplay owns the name mutation and checkpoint. Any +collects text, gameplay owns the name mutation and checkpoint. Weapon group +assignment follows the same rule: UI requests membership, gameplay owns the +group mutation and checkpoint. Any predicate that still needs legacy DTO objects must earn that dependency by using behavior objects or simulation-only methods that typed facets do not yet expose.