From 8ea97a08f893b7be2fb8625a51ad23158cc1feed Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Tue, 16 Jun 2026 11:43:37 +0100 Subject: [PATCH] Use faction keys for shell identity --- .voidbot/state/aetheria.cc | Bin 168243 -> 170452 bytes Aetheria.State.Verify/Program.cs | 44 ++++++++++++++++++ Aetheria.State/README.md | 4 ++ Assets/Scripts/Gameplay/ActionGameManager.cs | 2 +- Assets/Scripts/ServerShared/Corporations.cs | 11 ++++- Assets/Scripts/ServerShared/Entity.cs | 4 +- Assets/Scripts/ServerShared/Galaxy.cs | 5 ++ .../ServerShared/Narrative/ZoneConstraints.cs | 6 +-- Assets/Scripts/ServerShared/Zone.cs | 2 +- Assets/Scripts/UI/Menu/SectorRenderer.cs | 2 +- docs/aetheria-perfect-machine-map.md | 7 +++ 11 files changed, 77 insertions(+), 10 deletions(-) diff --git a/.voidbot/state/aetheria.cc b/.voidbot/state/aetheria.cc index 8e1febdd218e19ac1184085f41c240712854b002..429826dfb57d445256ae7b71e770e33f15a324ec 100644 GIT binary patch delta 2216 zcmai0&u<$=7-c6>NYX-4ekWAw6vQDVcH+hfX>te=RcQIqq@{qA7Lv*CtUb+o$DNr? z>Zro40*z1wYURVULX~I(r9vE#da#7Ji3SS4q{2W$(;< z`@Q$Q_dIuRwf=gy_3PWHIh`6mm`;ujm9`qXOW&c(tg?=@s^Z6zK*&} z&5irehkC8QvE1KiO*R)_K1p+VE{G`LX<{qJeJr!o^DuL$ub3)epRZt>=ki2QiHYyv zEP)*oFIG>yN(;%)ZI7_rA6Cy4Raq(+!8yK69jvyMr)LA=LE!;Yh-I0d8Nfaff+)63 zu^TmEb6+Y!n6Kmjmef{UV28=PM+(?wo`P4HgR9)9W;qb1Rled=A+s!x?JTvI;H1y} zggHZno?SqlZ4nYCxjzts5qka<+FRtp2`ggmy4a%`Vpq{6j~)(pa$oV7BS1WOgvd8Y zjvDY^DC$d~?%wT)Lvuzd1~k%hOe)|_;=JHz0Eb~1m}E{s@glWJASs@mtgKZh5FQ1X zp2SRHSMVGLR-t)eW);?mnyfUZZ!Dsx+kj61te`CQu|qwo^wuKk>e)EV!{>qmoq{`o zO|b`L>-$C2UBtj_3_ppd0~H90SGZULA@IO+DwU|&<_4Zuz%V~SKpVh(9bDqVOaNI9 zW(K2)R0QaRX$pJX&%nA*K@&_Rza+k$<>CZ&8|9ymt(MpJSiKD=Uq1ojV1h6E(ei!vv>5IQx&+04fR#R#JjjltfBixMI=+C%AP%j)% zoeaZjwc?e=?v0hMruKFnipt^>oAq@IgcvJibHZVY^zJG9mY zqz)jY5J<38bK_uC1j42^rDw1U?;?=jz+V`*Rn0ooW+v#fGG*;{Xf($9}iS_wUW8ujj}!|H$!%b%P^ z2dy&CqSvg()cEk2-hUq9%D2rUs7L?sCfcn(nn$}^swUQ-O4O5=@N%KQzl1Kvw~G;T0V!wK(Pm4$ JYxIS={{bKtAYcFh delta 1060 zcmaJ=%WD%s81GI>Nvoo$eL>Oh;z3QfiG67czL2!hT1!=gg5Y#_njJgYi8B+^c(4s3 z2-*ikn5+0kLET`qjfB9jq2qx>d^6< zsI8Gve^%p5sCC|c;xJ5eU#%S{i1LV_7+k`LA}*M}phzyy1r{YpV@FZ$+arYgI7iZy z5x~A%ub<>njuse$k&BsA5E8%)CCrsJj9`Z6O7?`{SBaxGfxzcQa5}LNlt}mk^$aP6 zz`A`NC9Wi@9McB7`v&(35Jt0fnlh?N;CRHDl@JkUjMnfi^`#=%1wI29@>ob?B?JI3 zQfEI?#Fxg*msP#C&FZ{tY)D0o_Gug9K)mP^Aw8P6Utr(yxR@j} za=l?LNN=|~W0}mP!J>j(DCLQ}$#6d895WjtAzQ%0m4LH292>Bz%s=e>&LDWX(|R;K zb~aj{s&JSkB>%7N`QKL57qqf*>wsRGLkIQeaf=423G03$_;45XTYB$4)D*x?>vn8s z>hMs1((WJZn|iEp)>tbBLD@RpwDoz;OfuZw^@csVtWayvbH}>(Q?D$deOpp}L&@a9 zWXk+jR%0E}arRg5^;3NFq6hT1MbvJtw@I%pAs%%N)UI07WmJw@2P+$~_MmwMy=!jS Je0?M}^$Vq)baDUy diff --git a/Aetheria.State.Verify/Program.cs b/Aetheria.State.Verify/Program.cs index 39eb0009a..95968385d 100644 --- a/Aetheria.State.Verify/Program.cs +++ b/Aetheria.State.Verify/Program.cs @@ -15,6 +15,7 @@ RequireTypedGalaxyFactionRelationships(root); RequireRuntimeCatalogKeyOnlyLookups(root); RequireTypedBehaviorBodyKeys(root); RequireTypedFactionShellLinks(root); +RequireFactionKeyIdentity(root); RequireEveRuntimeBootstrap(root); RequireNoRendererLocalConsole(root); RequireNoRendererLocalDebugPanels(root); @@ -809,6 +810,49 @@ static void RequireTypedFactionShellLinks(string root) } } +static void RequireFactionKeyIdentity(string root) +{ + var checkedFiles = new[] + { + Path.Combine(root, "Assets", "Scripts", "ServerShared", "Corporations.cs"), + Path.Combine(root, "Assets", "Scripts", "ServerShared", "Entity.cs"), + Path.Combine(root, "Assets", "Scripts", "ServerShared", "Galaxy.cs"), + Path.Combine(root, "Assets", "Scripts", "ServerShared", "Zone.cs"), + Path.Combine(root, "Assets", "Scripts", "ServerShared", "Narrative", "ZoneConstraints.cs"), + Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs"), + Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "SectorRenderer.cs") + }; + + var forbiddenSymbols = new[] + { + "ID.GetHashCode", + "faction.ID == ID", + "Faction.ID ==", + "faction.ID ==", + ".ID == TargetFaction", + ".ID == GalaxyZone.Owner", + ".ID != zone.Owner", + "pair.Key.ID", + "f.ID != zone.Owner", + "Owner.ID ==" + }; + + var hits = checkedFiles + .Where(File.Exists) + .SelectMany(path => File.ReadLines(path) + .Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line })) + .Where(line => forbiddenSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal))) + .Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}") + .ToArray(); + + if (hits.Length > 0) + { + throw new InvalidOperationException( + "Faction identity must compare and order by FactionKey; Faction.ID is temporary projection residue only: " + + string.Join("; ", hits)); + } +} + static void RequireEveRuntimeBootstrap(string root) { var bootstrapPath = Path.Combine( diff --git a/Aetheria.State/README.md b/Aetheria.State/README.md index 3498d7c0a..237475682 100644 --- a/Aetheria.State/README.md +++ b/Aetheria.State/README.md @@ -114,6 +114,10 @@ resolver are deleted from the simulation shell. The remaining temporary `Faction` shell also uses `GeonameFileKey` and `BossHullItemKey`; it no longer parses name-file or boss-hull legacy IDs into GUID link fields. +`Faction` equality, hashing, narrative constraints, sector display filtering, +zone security ownership checks, and runtime faction-relationship ordering now +use `FactionKey`. The legacy `Faction.ID` remains projection residue only until +the temporary simulation shell is removed. Run checkpoint commit DTOs carry typed `OrbitKey`, `ParentOrbitKey`, and `BodyKey` only for zone orbit/body snapshots. The old pending-command `OrbitLegacyId`, `ParentLegacyId`, and `BodyLegacyId` fallback fields are diff --git a/Assets/Scripts/Gameplay/ActionGameManager.cs b/Assets/Scripts/Gameplay/ActionGameManager.cs index ead3a6712..38952938e 100644 --- a/Assets/Scripts/Gameplay/ActionGameManager.cs +++ b/Assets/Scripts/Gameplay/ActionGameManager.cs @@ -677,7 +677,7 @@ public class ActionGameManager : MonoBehaviour { return CurrentGalaxy?.FactionRelationships? .Where(pair => pair.Key != null) - .OrderBy(pair => pair.Key.ID) + .OrderBy(pair => pair.Key.FactionKey ?? "", StringComparer.OrdinalIgnoreCase) .Select(pair => new AetheriaRuntimeFactionRelationshipCommit { FactionKey = pair.Key.FactionKey ?? "", diff --git a/Assets/Scripts/ServerShared/Corporations.cs b/Assets/Scripts/ServerShared/Corporations.cs index 5750267a1..e327d661b 100644 --- a/Assets/Scripts/ServerShared/Corporations.cs +++ b/Assets/Scripts/ServerShared/Corporations.cs @@ -56,13 +56,20 @@ public class Faction : INamedEntry set => Name = value; } + public bool HasSameKey(Faction other) + { + return other != null && + !string.IsNullOrWhiteSpace(FactionKey) && + string.Equals(FactionKey, other.FactionKey, StringComparison.OrdinalIgnoreCase); + } + public override int GetHashCode() { - return ID.GetHashCode(); + return StringComparer.OrdinalIgnoreCase.GetHashCode(FactionKey ?? ""); } public override bool Equals(object obj) { - return obj is Faction faction && faction.ID == ID; + return obj is Faction faction && HasSameKey(faction); } } diff --git a/Assets/Scripts/ServerShared/Entity.cs b/Assets/Scripts/ServerShared/Entity.cs index 91cb9a549..819e88c4f 100644 --- a/Assets/Scripts/ServerShared/Entity.cs +++ b/Assets/Scripts/ServerShared/Entity.cs @@ -289,7 +289,7 @@ public abstract class Entity // TODO: Inter-faction hostility // When the entity faction owns the zone, they are hostile to trespassers or those hostile to them - if (Faction.ID == Zone.GalaxyZone.Owner?.ID) + if (Faction.HasSameKey(Zone.GalaxyZone.Owner)) return recursive ? !(other.PresencePermitted?.Value ?? true) : !(other.PresencePermitted?.Value ?? true)|| other.IsHostileTo(this, true); return !recursive && other.IsHostileTo(this, true); @@ -302,7 +302,7 @@ public abstract class Entity return FactionRelationship.Neutral; if (this is Ship {IsPlayerShip: true}) return Zone.Galaxy.FactionRelationships[faction]; - return faction.ID == Faction.ID ? FactionRelationship.Beloved : FactionRelationship.Neutral; + return faction.HasSameKey(Faction) ? FactionRelationship.Beloved : FactionRelationship.Neutral; } public static bool IsPresencePermitted(FactionRelationship relationship, SecurityLevel securityLevel) => diff --git a/Assets/Scripts/ServerShared/Galaxy.cs b/Assets/Scripts/ServerShared/Galaxy.cs index 4af3b6e68..05dbc387f 100644 --- a/Assets/Scripts/ServerShared/Galaxy.cs +++ b/Assets/Scripts/ServerShared/Galaxy.cs @@ -254,6 +254,11 @@ public class Galaxy private static Faction ProjectFaction(AetheriaRuntimeCorporation corporation) { + if (string.IsNullOrWhiteSpace(corporation.CorporationKey)) + { + throw new InvalidOperationException($"Typed catalog corporation {corporation.Name} has no corporation key."); + } + return new Faction { ID = ParseLegacyId(corporation.LegacyId, nameof(corporation.LegacyId), corporation.Name), diff --git a/Assets/Scripts/ServerShared/Narrative/ZoneConstraints.cs b/Assets/Scripts/ServerShared/Narrative/ZoneConstraints.cs index f707dc399..ace1e81ef 100644 --- a/Assets/Scripts/ServerShared/Narrative/ZoneConstraints.cs +++ b/Assets/Scripts/ServerShared/Narrative/ZoneConstraints.cs @@ -34,7 +34,7 @@ public class FactionPresenceConstraint : ZoneConstraint protected override bool TestZone(GalaxyZone zone) { - return zone.Factions.Any(f=>f.ID == TargetFaction?.ID); + return zone.Factions.Any(f => f.HasSameKey(TargetFaction)); } } @@ -49,7 +49,7 @@ public class FactionOwnerConstraint : ZoneConstraint protected override bool TestZone(GalaxyZone zone) { - return zone.Owner.ID == TargetFaction?.ID; + return zone.Owner?.HasSameKey(TargetFaction) ?? false; } } @@ -86,4 +86,4 @@ public class DistanceConstraint : ZoneConstraint { return _targetZone != null && _test(zone.Distance[_targetZone]); } -} \ No newline at end of file +} diff --git a/Assets/Scripts/ServerShared/Zone.cs b/Assets/Scripts/ServerShared/Zone.cs index 95c4c24c8..10a0291dd 100644 --- a/Assets/Scripts/ServerShared/Zone.cs +++ b/Assets/Scripts/ServerShared/Zone.cs @@ -269,7 +269,7 @@ public class Zone var security = SecurityLevel.Open; foreach (var entity in Entities) { - if (entity is OrbitalEntity orbitalEntity && orbitalEntity.SecurityRadius > 1 && entity.Faction.ID == GalaxyZone.Owner.ID) + if (entity is OrbitalEntity orbitalEntity && orbitalEntity.SecurityRadius > 1 && entity.Faction.HasSameKey(GalaxyZone.Owner)) { if (orbitalEntity.SecurityLevel > security && length(orbitalEntity.Position.xz - pos) < orbitalEntity.SecurityRadius * Settings.SecureAreaRadiusMultiplier) security = orbitalEntity.SecurityLevel; diff --git a/Assets/Scripts/UI/Menu/SectorRenderer.cs b/Assets/Scripts/UI/Menu/SectorRenderer.cs index cbc015770..e40d48075 100644 --- a/Assets/Scripts/UI/Menu/SectorRenderer.cs +++ b/Assets/Scripts/UI/Menu/SectorRenderer.cs @@ -63,7 +63,7 @@ public class SectorRenderer : MonoBehaviour, IBeginDragHandler, IDragHandler, IS Properties.Title.text = zone.Name; Properties.AddProperty("Owner", () => zone.Owner?.Name ?? "None"); var otherFactions = zone.Factions - .Where(f => f.ID != zone.Owner?.ID) + .Where(f => !f.HasSameKey(zone.Owner)) .Select(f=>f.Name).ToArray(); if (otherFactions.Length > 0) Properties.AddProperty("Factions Present", () => string.Join(", ", otherFactions)); diff --git a/docs/aetheria-perfect-machine-map.md b/docs/aetheria-perfect-machine-map.md index 9b6c26a6d..e7f579fda 100644 --- a/docs/aetheria-perfect-machine-map.md +++ b/docs/aetheria-perfect-machine-map.md @@ -1022,6 +1022,10 @@ First Aetheria surfaces to publish: - Done: delete the temporary `Faction` shell's `GeonameFile` and `BossHull` GUID link fields; galaxy name generation and boss-zone eligibility use `GeonameFileKey` and `BossHullItemKey`. + - Done: move temporary `Faction` equality, hashing, narrative constraints, + sector display filtering, zone security ownership checks, and runtime + faction-relationship ordering to `FactionKey`. `Faction.ID` is now + projection residue rather than comparison/order authority. - Done: move package and canonical manufacturer/corporation/name-file lookup to typed keys (`ManufacturerKey`, `CorporationKey`, `GeonameFileKey`, and `NameFileKey`), and move loadout manufacturer-distance weighting to @@ -1764,6 +1768,9 @@ First Aetheria surfaces to publish: return. - `Aetheria.State.Verify` guards temporary `Faction` shell links: geoname and boss-hull relationships use typed keys, not legacy GUID projection fields. +- `Aetheria.State.Verify` guards temporary `Faction` identity: equality, + hashing, ordering, narrative checks, sector filtering, and zone security + checks must use `FactionKey`, leaving `Faction.ID` as projection residue only. - Unity batchmode compile returned cleanly after the runtime blueprint rename; live Unity source has no `EntityPack`, `ShipPack`, `OrbitalEntityPack`, `ZonePack`, `PackedContents`, `PackZone`, `EntitySerializer.Pack`, or