Use faction keys for shell identity

This commit is contained in:
Meta Vens 2026-06-16 11:43:37 +01:00
commit 8ea97a08f8
11 changed files with 77 additions and 10 deletions

Binary file not shown.

View file

@ -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(

View file

@ -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

View file

@ -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 ?? "",

View file

@ -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);
}
}

View file

@ -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) =>

View file

@ -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),

View file

@ -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]);
}
}
}

View file

@ -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;

View file

@ -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));

View file

@ -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