Restore patrol combat preemption
This commit is contained in:
parent
70dc0cf1b9
commit
c78fdb0361
8 changed files with 284 additions and 11 deletions
|
|
@ -103,7 +103,12 @@ internal sealed class AetheriaDaemonYmirSmokeChecks
|
|||
public void RunTutorialMaterialization() => RunCheck(TutorialPopulationPreservesFossilEntityMechanics);
|
||||
public void RunTutorialRun() => RunCheck(TutorialRunPersistsCanonicalWorldTruth);
|
||||
public void RunOrbits() => RunCheck(GeneratedOrbitsAdvanceOrbitalWorldTruth);
|
||||
public void RunPatrol() => RunCheck(AgentPatrolsHistoricalOrbitCircuitThroughMovementCommands);
|
||||
public void RunPatrol()
|
||||
{
|
||||
RunCheck(SecurityZonesOwnFossilTrespasserHostility);
|
||||
RunCheck(AgentPatrolsHistoricalOrbitCircuitThroughMovementCommands);
|
||||
RunCheck(PatrolCombatPreemptsAndResumesTheSameCircuit);
|
||||
}
|
||||
|
||||
private static void TutorialRunPersistsCanonicalWorldTruth()
|
||||
{
|
||||
|
|
@ -5199,6 +5204,137 @@ internal sealed class AetheriaDaemonYmirSmokeChecks
|
|||
"patrol is persistent work and must remain assigned after one circuit");
|
||||
}
|
||||
|
||||
private static void SecurityZonesOwnFossilTrespasserHostility()
|
||||
{
|
||||
var guard = Entity(0, 0, "owners");
|
||||
guard.SecurityLevel = 2;
|
||||
guard.SecurityRadius = 100;
|
||||
var visitor = Entity(1, 10, "visitors");
|
||||
guard.Contacts = [new AetheriaRuntimeEntityContactCommit { TargetEntityIndex = 1, InfoGathered = 1 }];
|
||||
visitor.Contacts = [new AetheriaRuntimeEntityContactCommit { TargetEntityIndex = 0, InfoGathered = 1 }];
|
||||
var zone = new AetheriaRuntimeZoneSnapshotCommit
|
||||
{
|
||||
ZoneIndex = 0,
|
||||
OwnerFactionIndex = 0,
|
||||
Entities = [guard, visitor]
|
||||
};
|
||||
var run = new AetheriaRuntimeRunCheckpointCommit
|
||||
{
|
||||
CurrentEntityKey = "zone.0.entity.1",
|
||||
FactionRelationships =
|
||||
[
|
||||
new AetheriaRuntimeFactionRelationshipCommit
|
||||
{
|
||||
FactionKey = "owners", Relationship = "Neutral"
|
||||
}
|
||||
],
|
||||
Zones = [zone]
|
||||
};
|
||||
var catalog = new AetheriaRuntimeCatalogSnapshot([], [new AetheriaRuntimeCorporation(
|
||||
"owners", "Owners", "Owners", "", "", "", 4, 0,
|
||||
Array.Empty<AetheriaRuntimeCorporationAllegiance>())], []);
|
||||
|
||||
AetheriaRuntimeSensorSimulation.StepZone(
|
||||
run, zone, [guard, visitor], null, catalog, 0.1, 0, 0.5, 1);
|
||||
Require(guard.Contacts.Single().Hostile && visitor.Contacts.Single().Hostile,
|
||||
"a neutral visitor inside an owner critical-security field must be mutually hostile");
|
||||
|
||||
visitor.PositionX = 1000;
|
||||
AetheriaRuntimeSensorSimulation.StepZone(
|
||||
run, zone, [guard, visitor], null, catalog, 0.1, 0, 0.5, 2);
|
||||
Require(!guard.Contacts.Single().Hostile && !visitor.Contacts.Single().Hostile,
|
||||
"the same neutral visitor outside security fields must cease being hostile");
|
||||
}
|
||||
|
||||
private static void PatrolCombatPreemptsAndResumesTheSameCircuit()
|
||||
{
|
||||
var patrol = Entity(0, 0, "owners");
|
||||
patrol.AgentTaskCapabilities = [AetheriaRuntimeAgentTaskTypes.Patrol];
|
||||
patrol.WeaponGroups = [new[] { 0 }];
|
||||
patrol.Equipment =
|
||||
[
|
||||
new AetheriaRuntimeLoadoutItemSlotCommit
|
||||
{
|
||||
Item = new AetheriaRuntimeLoadoutItemCommit
|
||||
{
|
||||
ItemKey = "patrol-cannon", Quality = 1, Durability = 1, Enabled = true
|
||||
}
|
||||
}
|
||||
];
|
||||
patrol.Contacts =
|
||||
[
|
||||
new AetheriaRuntimeEntityContactCommit
|
||||
{
|
||||
TargetEntityIndex = 1, InfoGathered = 1, Visible = true, Hostile = true
|
||||
}
|
||||
];
|
||||
var hostile = Entity(1, 0, "visitors");
|
||||
hostile.PositionZ = 50;
|
||||
var weapon = new AetheriaRuntimeBehaviorPayload(0, AetheriaRuntimeBehaviorKinds.InstantWeapon, 0,
|
||||
[
|
||||
new AetheriaRuntimeBehaviorField(2, PerformanceStat(10)),
|
||||
new AetheriaRuntimeBehaviorField(5, PerformanceStat(0)),
|
||||
new AetheriaRuntimeBehaviorField(6, PerformanceStat(150)),
|
||||
new AetheriaRuntimeBehaviorField(16, PerformanceStat(200)),
|
||||
new AetheriaRuntimeBehaviorField(19, PerformanceStat(0.5))
|
||||
]);
|
||||
var catalog = new AetheriaRuntimeCatalogSnapshot(
|
||||
[WearableWeaponCatalogItem("patrol-cannon", weapon)], [], []);
|
||||
var task = new AetheriaRuntimeAgentTaskCommit
|
||||
{
|
||||
TaskId = "patrol-combat",
|
||||
CorporationKey = "owners",
|
||||
TaskType = AetheriaRuntimeAgentTaskTypes.Patrol,
|
||||
Status = AetheriaRuntimeAgentTaskStatuses.Assigned,
|
||||
ZoneIndex = 0,
|
||||
AssignedEntityIndex = 0,
|
||||
CompletionRadius = 5,
|
||||
TargetOrbitKeys = ["orbit:east", "orbit:west"],
|
||||
CircuitIndex = 1
|
||||
};
|
||||
patrol.AssignedAgentTaskId = task.TaskId;
|
||||
var run = new AetheriaRuntimeRunCheckpointCommit
|
||||
{
|
||||
RunId = "patrol-combat-smoke",
|
||||
CurrentEntityKey = "",
|
||||
AgentTasks = [task],
|
||||
Zones =
|
||||
[
|
||||
new AetheriaRuntimeZoneSnapshotCommit
|
||||
{
|
||||
ZoneIndex = 0,
|
||||
Entities = [patrol, hostile],
|
||||
Orbits =
|
||||
[
|
||||
new AetheriaRuntimeOrbitSnapshotCommit { OrbitKey = "orbit:east", FixedPositionX = 20 },
|
||||
new AetheriaRuntimeOrbitSnapshotCommit { OrbitKey = "orbit:west", FixedPositionX = -20 }
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var combatCommands = AetheriaRuntimeAgentScheduler.AssignAndPlan(
|
||||
run, 1, catalog, 0.1, AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault);
|
||||
Require(combatCommands.Any(command =>
|
||||
command.Kind == AetheriaRuntimeDaemonCommandKinds.SetTarget &&
|
||||
AetheriaRuntimeRunCheckpointCommit.TryParseEntityKey(
|
||||
command.TargetEntityKey, out var zoneIndex, out var entityIndex) &&
|
||||
zoneIndex == 0 && entityIndex == 1),
|
||||
"a visible hostile ship must pre-empt patrol movement through the shared target command");
|
||||
Require(combatCommands.Any(command => command.Kind == AetheriaRuntimeDaemonCommandKinds.FireWeaponGroup),
|
||||
"an aligned in-range patrol agent must use the shared weapon command");
|
||||
RequireEqual(1, task.CircuitIndex,
|
||||
"opportunistic combat must not replace or advance the patrol circuit");
|
||||
|
||||
patrol.Contacts = Array.Empty<AetheriaRuntimeEntityContactCommit>();
|
||||
var resumed = AetheriaRuntimeAgentScheduler.AssignAndPlan(
|
||||
run, 2, catalog, 0.2, AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault);
|
||||
Require(resumed.Any(command => command.Kind == AetheriaRuntimeDaemonCommandKinds.SetMoveVector),
|
||||
"loss of hostile visibility must resume the same patrol circuit");
|
||||
RequireEqual(AetheriaRuntimeAgentTaskStatuses.Assigned, task.Status,
|
||||
"opportunistic combat must never complete or fail the patrol task");
|
||||
}
|
||||
|
||||
private static void RejectedHaulTransferDoesNotAdvanceTask()
|
||||
{
|
||||
var origin = Entity(0, 0, "workers");
|
||||
|
|
|
|||
|
|
@ -302,6 +302,16 @@ namespace GameCult.Aetheria.State.Verse
|
|||
if (string.Equals(task.TaskType, AetheriaRuntimeAgentTaskTypes.Defend, StringComparison.Ordinal) ||
|
||||
string.Equals(task.TaskType, AetheriaRuntimeAgentTaskTypes.Patrol, StringComparison.Ordinal))
|
||||
{
|
||||
var hostile = FirstVisibleHostileShip(zone!, agent);
|
||||
if (hostile != null && ResolveAgentWeapons(agent, catalog).Count > 0)
|
||||
{
|
||||
var combatDx = hostile.PositionX - agent.PositionX;
|
||||
var combatDz = hostile.PositionZ - agent.PositionZ;
|
||||
commands.AddRange(PlanAttack(
|
||||
run, task, agent, hostile, frameId, combatDx, combatDz,
|
||||
Math.Sqrt(combatDx * combatDx + combatDz * combatDz), catalog, simulationSettings));
|
||||
continue;
|
||||
}
|
||||
commands.AddRange(PlanPatrol(zone!, task, agent, frameId));
|
||||
continue;
|
||||
}
|
||||
|
|
@ -1123,6 +1133,25 @@ namespace GameCult.Aetheria.State.Verse
|
|||
return new[] { Movement(task, agent, frameId, dx, dz, 1) };
|
||||
}
|
||||
|
||||
private static AetheriaRuntimeEntitySnapshotCommit? FirstVisibleHostileShip(
|
||||
AetheriaRuntimeZoneSnapshotCommit zone,
|
||||
AetheriaRuntimeEntitySnapshotCommit agent)
|
||||
{
|
||||
var hostileIndices = (agent.Contacts ?? Array.Empty<AetheriaRuntimeEntityContactCommit>())
|
||||
.Where(contact => contact != null && contact.Visible && contact.Hostile)
|
||||
.Select(contact => contact.TargetEntityIndex)
|
||||
.ToArray();
|
||||
foreach (var index in hostileIndices)
|
||||
{
|
||||
var target = (zone.Entities ?? Array.Empty<AetheriaRuntimeEntitySnapshotCommit>())
|
||||
.FirstOrDefault(entity => entity != null && entity.EntityIndex == index &&
|
||||
entity.IsActive && string.Equals(entity.Kind, "ship", StringComparison.OrdinalIgnoreCase));
|
||||
if (target != null)
|
||||
return target;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static AetheriaRuntimeDaemonCommandDocument Movement(
|
||||
AetheriaRuntimeAgentTaskCommit task,
|
||||
AetheriaRuntimeEntitySnapshotCommit agent,
|
||||
|
|
|
|||
|
|
@ -111,7 +111,8 @@ namespace GameCult.Aetheria.State.Verse
|
|||
deltaSeconds,
|
||||
settings.TargetInfoDecay,
|
||||
activeRenderSettings.TargetDetectionInfoThreshold,
|
||||
frameId);
|
||||
frameId,
|
||||
settings.SecureAreaRadiusMultiplier);
|
||||
AetheriaRuntimeMiningSimulation.Step(run, zone, entities, intents, catalog, frameId, simulationTimeSeconds, deltaSeconds);
|
||||
AetheriaRuntimeSurveySimulation.Step(run, zone, entities, intents, catalog, frameId, simulationTimeSeconds, deltaSeconds);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -110,7 +110,8 @@ namespace GameCult.Aetheria.State.Verse
|
|||
double wormholeExitRadius = AetheriaDefaultRaw.WormholeExitRadius,
|
||||
double wormholeDepth = AetheriaDefaultRaw.WormholeDepth,
|
||||
double visibilityDecay = AetheriaDefaultRaw.VisibilityDecay,
|
||||
double targetInfoDecay = AetheriaDefaultRaw.TargetInfoDecay)
|
||||
double targetInfoDecay = AetheriaDefaultRaw.TargetInfoDecay,
|
||||
double secureAreaRadiusMultiplier = AetheriaDefaultRaw.SecureAreaRadiusMultiplier)
|
||||
{
|
||||
PawnSpeed = PositiveOr(pawnSpeed, AetheriaDefaultRaw.PawnSpeed);
|
||||
RaiderSpeed = PositiveOr(raiderSpeed, AetheriaDefaultRaw.RaiderSpeed);
|
||||
|
|
@ -165,6 +166,8 @@ namespace GameCult.Aetheria.State.Verse
|
|||
WormholeDepth = PositiveOr(wormholeDepth, AetheriaDefaultRaw.WormholeDepth);
|
||||
VisibilityDecay = NonNegativeOr(visibilityDecay, AetheriaDefaultRaw.VisibilityDecay);
|
||||
TargetInfoDecay = NonNegativeOr(targetInfoDecay, AetheriaDefaultRaw.TargetInfoDecay);
|
||||
SecureAreaRadiusMultiplier = PositiveOr(
|
||||
secureAreaRadiusMultiplier, AetheriaDefaultRaw.SecureAreaRadiusMultiplier);
|
||||
}
|
||||
|
||||
public double PawnSpeed { get; }
|
||||
|
|
@ -222,6 +225,7 @@ namespace GameCult.Aetheria.State.Verse
|
|||
public double WormholeDepth { get; }
|
||||
public double VisibilityDecay { get; }
|
||||
public double TargetInfoDecay { get; }
|
||||
public double SecureAreaRadiusMultiplier { get; }
|
||||
|
||||
private static double PositiveOr(double value, double fallback)
|
||||
{
|
||||
|
|
@ -293,6 +297,7 @@ namespace GameCult.Aetheria.State.Verse
|
|||
public const double WormholeDepth = 1000.0;
|
||||
public const double VisibilityDecay = 0.5;
|
||||
public const double TargetInfoDecay = 0.5;
|
||||
public const double SecureAreaRadiusMultiplier = 0.425;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@ namespace GameCult.Aetheria.State.Verse
|
|||
double deltaSeconds,
|
||||
double targetInfoDecay,
|
||||
double detectionThreshold,
|
||||
long frameId)
|
||||
long frameId,
|
||||
double secureAreaRadiusMultiplier = 0.425)
|
||||
{
|
||||
if (catalog == null || deltaSeconds <= 0)
|
||||
return;
|
||||
|
|
@ -44,7 +45,7 @@ namespace GameCult.Aetheria.State.Verse
|
|||
|
||||
foreach (var sensor in sensors)
|
||||
StepSensor(run, zone, observer, sensor, entities, contacts, catalog,
|
||||
deltaSeconds, targetInfoDecay, frameId);
|
||||
deltaSeconds, targetInfoDecay, secureAreaRadiusMultiplier, frameId);
|
||||
|
||||
observer.Contacts = entities
|
||||
.Where(target => target != null && target.EntityIndex != observer.EntityIndex)
|
||||
|
|
@ -56,7 +57,9 @@ namespace GameCult.Aetheria.State.Verse
|
|||
{
|
||||
TargetEntityIndex = target.EntityIndex,
|
||||
InfoGathered = information,
|
||||
Hostile = Hostile(observer, target),
|
||||
Hostile = Hostile(
|
||||
run, zone, entities, catalog, observer, target,
|
||||
secureAreaRadiusMultiplier),
|
||||
Visible = information > detectionThreshold
|
||||
};
|
||||
})
|
||||
|
|
@ -108,6 +111,7 @@ namespace GameCult.Aetheria.State.Verse
|
|||
AetheriaRuntimeCatalogSnapshot catalog,
|
||||
double deltaSeconds,
|
||||
double targetInfoDecay,
|
||||
double secureAreaRadiusMultiplier,
|
||||
long frameId)
|
||||
{
|
||||
var thermal = ThermalPerformance(observer, sensor.EquipmentIndex);
|
||||
|
|
@ -164,7 +168,9 @@ namespace GameCult.Aetheria.State.Verse
|
|||
{
|
||||
TargetEntityIndex = target.EntityIndex,
|
||||
InfoGathered = next,
|
||||
Hostile = Hostile(observer, target)
|
||||
Hostile = Hostile(
|
||||
run, zone, entities, catalog, observer, target,
|
||||
secureAreaRadiusMultiplier)
|
||||
};
|
||||
}
|
||||
sensor.State.PingedEntityIndices = pinged.OrderBy(index => index).ToArray();
|
||||
|
|
@ -250,6 +256,94 @@ namespace GameCult.Aetheria.State.Verse
|
|||
.ThermalPerformance ?? 1;
|
||||
|
||||
private static bool Hostile(
|
||||
AetheriaRuntimeRunCheckpointCommit run,
|
||||
AetheriaRuntimeZoneSnapshotCommit zone,
|
||||
IReadOnlyList<AetheriaRuntimeEntitySnapshotCommit> entities,
|
||||
AetheriaRuntimeCatalogSnapshot catalog,
|
||||
AetheriaRuntimeEntitySnapshotCommit left,
|
||||
AetheriaRuntimeEntitySnapshotCommit right,
|
||||
double secureAreaRadiusMultiplier)
|
||||
{
|
||||
var ownerKey = zone.OwnerFactionIndex >= 0 &&
|
||||
zone.OwnerFactionIndex < (catalog.Corporations?.Count ?? 0)
|
||||
? catalog.Corporations[zone.OwnerFactionIndex].CorporationKey ?? ""
|
||||
: "";
|
||||
if (string.IsNullOrWhiteSpace(ownerKey))
|
||||
return LegacyHostile(left, right);
|
||||
|
||||
return OwnerRejects(run, zone, entities, left, right, ownerKey, secureAreaRadiusMultiplier) ||
|
||||
OwnerRejects(run, zone, entities, right, left, ownerKey, secureAreaRadiusMultiplier);
|
||||
}
|
||||
|
||||
private static bool OwnerRejects(
|
||||
AetheriaRuntimeRunCheckpointCommit run,
|
||||
AetheriaRuntimeZoneSnapshotCommit zone,
|
||||
IReadOnlyList<AetheriaRuntimeEntitySnapshotCommit> entities,
|
||||
AetheriaRuntimeEntitySnapshotCommit ownerEntity,
|
||||
AetheriaRuntimeEntitySnapshotCommit visitor,
|
||||
string ownerKey,
|
||||
double secureAreaRadiusMultiplier)
|
||||
{
|
||||
if (!string.Equals(ownerEntity.FactionKey, ownerKey, StringComparison.OrdinalIgnoreCase))
|
||||
return false;
|
||||
var relationship = RelationshipTo(run, zone, visitor, ownerKey);
|
||||
var security = LocalSecurity(
|
||||
entities, visitor.PositionX, visitor.PositionZ, ownerKey, secureAreaRadiusMultiplier);
|
||||
return relationship - security <= 0;
|
||||
}
|
||||
|
||||
private static int RelationshipTo(
|
||||
AetheriaRuntimeRunCheckpointCommit run,
|
||||
AetheriaRuntimeZoneSnapshotCommit zone,
|
||||
AetheriaRuntimeEntitySnapshotCommit entity,
|
||||
string factionKey)
|
||||
{
|
||||
if (string.Equals(entity.FactionKey, factionKey, StringComparison.OrdinalIgnoreCase))
|
||||
return 4;
|
||||
if (AetheriaRuntimeRunCheckpointCommit.TryParseEntityKey(
|
||||
run.CurrentEntityKey, out var playerZoneIndex, out var playerEntityIndex) &&
|
||||
playerZoneIndex == zone.ZoneIndex && playerEntityIndex == entity.EntityIndex)
|
||||
{
|
||||
var relationship = (run.FactionRelationships ?? Array.Empty<AetheriaRuntimeFactionRelationshipCommit>())
|
||||
.FirstOrDefault(value => value != null &&
|
||||
string.Equals(value.FactionKey, factionKey, StringComparison.OrdinalIgnoreCase));
|
||||
return RelationshipValue(relationship?.Relationship);
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
|
||||
private static int LocalSecurity(
|
||||
IReadOnlyList<AetheriaRuntimeEntitySnapshotCommit> entities,
|
||||
double positionX,
|
||||
double positionZ,
|
||||
string ownerKey,
|
||||
double secureAreaRadiusMultiplier)
|
||||
{
|
||||
var security = 0;
|
||||
foreach (var entity in entities)
|
||||
{
|
||||
if (entity == null || entity.SecurityRadius <= 1 ||
|
||||
!string.Equals(entity.FactionKey, ownerKey, StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
var dx = entity.PositionX - positionX;
|
||||
var dz = entity.PositionZ - positionZ;
|
||||
var radius = entity.SecurityRadius * secureAreaRadiusMultiplier;
|
||||
if (dx * dx + dz * dz < radius * radius)
|
||||
security = Math.Max(security, entity.SecurityLevel);
|
||||
}
|
||||
return security;
|
||||
}
|
||||
|
||||
private static int RelationshipValue(string? relationship)
|
||||
{
|
||||
if (string.Equals(relationship, "Hated", StringComparison.OrdinalIgnoreCase)) return 0;
|
||||
if (string.Equals(relationship, "Hostile", StringComparison.OrdinalIgnoreCase)) return 1;
|
||||
if (string.Equals(relationship, "Friendly", StringComparison.OrdinalIgnoreCase)) return 3;
|
||||
if (string.Equals(relationship, "Beloved", StringComparison.OrdinalIgnoreCase)) return 4;
|
||||
return 2;
|
||||
}
|
||||
|
||||
private static bool LegacyHostile(
|
||||
AetheriaRuntimeEntitySnapshotCommit left,
|
||||
AetheriaRuntimeEntitySnapshotCommit right) =>
|
||||
string.Equals(left.FactionKey, "player", StringComparison.OrdinalIgnoreCase) !=
|
||||
|
|
|
|||
|
|
@ -1148,7 +1148,11 @@ the Terminus proof arena cannot award completion.
|
|||
- Agent work: each generated NPC ship receives one assigned `patrol` task with
|
||||
the fossil four-orbit shuffled circuit. Orbit circuits are typed separately
|
||||
from mining/survey body targets and survive bootstrap into the runtime
|
||||
checkpoint; Unity no longer creates `Minion` patrol state on activation.
|
||||
checkpoint. Daemon sensor policy derives the fossil owner-security trespass
|
||||
rule from faction relationships and orbital security fields. The first
|
||||
visible hostile ship temporarily drives the shared attack planner without
|
||||
replacing or advancing the patrol task; loss of contact resumes the same
|
||||
circuit. Unity no longer creates `Minion` patrol or combat state on activation.
|
||||
- Physical placement: the daemon resolves orbital entities from the canonical
|
||||
orbit graph before persistence and deterministically distributes generated
|
||||
non-player ships through the fossil half-zone volume. The controlled starter
|
||||
|
|
@ -1170,7 +1174,8 @@ the Terminus proof arena cannot award completion.
|
|||
- Verification layer: focused smokes prove deterministic population, resolved
|
||||
orbital references and poses, distributed non-player spawns,
|
||||
fixed-step orbit phase/pose/velocity through Ymir, station/turret/agent
|
||||
presence, assigned four-orbit patrol persistence and movement, loadout
|
||||
presence, assigned four-orbit patrol persistence and movement, critical-zone
|
||||
trespass hostility, combat pre-emption and same-circuit resumption, loadout
|
||||
provenance, all 64 persisted zones, canonical starter identity, discovery,
|
||||
and adjacency.
|
||||
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ validation proves contract shape, not wiring, exclusivity, lowering, or parity.
|
|||
| Starbridge | original/shared architecture intent | one daemon session | pilot + commander surfaces | Unity + Electron | commander task board and autonomous explore execution wired; Electron Eve lowering and split-target proof missing | commander orders plus four pilots and autonomous workers |
|
||||
| Behavior engine | `ServerShared/Behaviors` composition | daemon | executable action/status projection | action bar and feedback | tick reconciles equipped catalog payloads into persistent behavior state; `Switch`, one-shot `Trigger`, normalized `Cooldown`, adjustable high/low-pass `Thermotoggle`, `EnergyDraw`, equipment `Heat`, atomic `ItemUsage`, and thermal `Wear` execute in authored group order and stop downstream effects on refusal; each row records whether the chain reached it. `Thruster`, rare `AetherDrive`, and `Radiator` now pause their groups at the specialized owner, report the exact fossil success result, and resume common tails at most once, so idle or unfunded actuators cannot consume wear; radiator emission remains its independent always-update path. Cooldown projects generic progress and thermostat target is daemon-commanded state seeded from authored data. Equipped and timed-consumable `StatModifier` sources persist daemon-owned lifecycle state and every runtime stat reader applies the fossil `base × multiplier product + constant sum` rule through one query; legacy `Data` kind references and required-behavior filters are honored | remaining specialized return-value composition and canonical consumable-content proof |
|
||||
| Sensors/visibility | installed sensor sensitivity and angular curve, `Visibility`, `Reflector`, decaying signature sources, stellar light, ping state, and observer information thresholds | daemon | accumulated contacts, named visibility sources, and ping events | indicators/maps and provider environment lowering | every operational installed Sensor executes the fossil visibility/sensitivity/angle/distance information equation and decay; visibility is thresholded by the provider setting. Actor-scoped ping owns energy, cooldown, expansion radius, reconnect-stable crossed-target state, one-time information boost, and transient signature. Operational `Visibility` equipment publishes evaluated constant signature; committed weapons publish authored signature including charged-shot multiplication; transient weapon and ping sources decay by the fossil exponential rule. Reflectors multiply evaluated cross-section by the exponent-8 stellar `PowerPulse` after thermal settlement. Gameplay light and environment projection share resolved authored orbit/body centers and provider-owned sun radius inputs. Contact refresh no longer invents perfect information or overwrites emitted visibility; docked craft consume one daemon-derived union of craft and dock-parent contacts through both Eve rows and SoA bodies, with sensor-source provenance | active consumable visibility/reflector effects, complete source cardinality, classification timelines, and canonical catalog station-array data |
|
||||
| AI/tasks | corporation task scheduler plus agent state machines and installed orbital `TurretController` | daemon | commander task board, corporation survey ledger, controller aim/state, and shared semantic controls | Eve commander/pilot clients | point exploration, loadout-driven attack, haul, mining/offload, resource surveying, physical station towing, persistent orbit patrol, route-aware assignment, and autonomous return home are wired through shared commands. `patrol` is a first-class task with orbit targets distinct from survey/mining body targets; every generated tutorial NPC persists its assigned fossil four-orbit shuffled circuit before the first tick. Ship combat samples the fossil DPS/range policy, selects live weapon groups, leads projectiles, strafes relative to target attitude, and gates fire on catalog placement rotation. Chain-admitted orbital controllers independently acquire the first visible hostile ship, publish predictive look direction, and trigger only exact aligned in-range weapon behaviors through the shared combat lifecycle; the canonical preceding `EnergyDraw` now blocks acquisition, aim, and fire when power is unavailable, and non-player identity grants no implicit group-zero fire. Cross-zone task and home routes survive the full daemon-owned wormhole timeline; attack admission rejects missing/friendly targets and unavailable weapon groups before durable queueing. Cancel, complete, fail, stale-assignment repair, and duplicate-carrier repair share one scheduler-owned release primitive that zeros persisted helm input, clears the scheduler target, releases towing parentage, and prevents a terminal task from emitting another command. Real CultCache close/reopen proofs now cover both ordinary mid-travel and the first `entering` phase of a two-edge route; fresh Ymir owners resume without duplicate tasks, entities, or transition events, and Eve commander observes terminal status | multi-client command transfer and model-local barrel-direction metadata |
|
||||
| AI/tasks | corporation task scheduler plus agent state machines and installed orbital `TurretController` | daemon | commander task board, corporation survey ledger, controller aim/state, and shared semantic controls | Eve commander/pilot clients | point exploration, loadout-driven attack, haul, mining/offload, resource surveying, physical station towing, persistent orbit patrol, route-aware assignment, and autonomous return home are wired through shared commands. `patrol` is a first-class task with orbit targets distinct from survey/mining body targets; every generated tutorial NPC persists its assigned fossil four-orbit shuffled circuit before the first tick. Daemon sensors derive the fossil owner-security trespass rule from the controlled pilot's faction relationship and orbital security fields. Like the fossil `Minion`, a patrol agent temporarily enters the shared combat planner for its first visible hostile ship, keeps its circuit index, and resumes that same patrol when visibility is lost. Ship combat samples the fossil DPS/range policy, selects live weapon groups, leads projectiles, strafes relative to target attitude, and gates fire on catalog placement rotation. Chain-admitted orbital controllers independently acquire the first visible hostile ship, publish predictive look direction, and trigger only exact aligned in-range weapon behaviors through the shared combat lifecycle; the canonical preceding `EnergyDraw` now blocks acquisition, aim, and fire when power is unavailable, and non-player identity grants no implicit group-zero fire. Cross-zone task and home routes survive the full daemon-owned wormhole timeline; attack admission rejects missing/friendly targets and unavailable weapon groups before durable queueing. Cancel, complete, fail, stale-assignment repair, and duplicate-carrier repair share one scheduler-owned release primitive that zeros persisted helm input, clears the scheduler target, releases towing parentage, and prevents a terminal task from emitting another command. Real CultCache close/reopen proofs now cover both ordinary mid-travel and the first `entering` phase of a two-edge route; fresh Ymir owners resume without duplicate tasks, entities, or transition events, and Eve commander observes terminal status | multi-client command transfer and model-local barrel-direction metadata |
|
||||
| Consumables | cargo-backed timed behavior containers | daemon | dynamic actions, active effects, and lifecycle facts | action bar/schematic | daemon atomically resolves typed metadata and concrete cargo, preserves consumed-instance quality, enforces authored stacking, evaluates effectiveness/quality, and executes ordered `EnergyDraw`, fossil-compatible no-op `Heat`, `ItemUsage`, and stateful `Cooldown` payloads after energy tick initialization; every effect and payload state has stable authored identity; cooldowns update before the ordered chain, require state strictly below zero, reset to one on success, and continue aging behind earlier failures; false and unsupported payloads stop later execution with a typed fact; timers retain the fossil's exact-zero final tick; Unity reconciles presentation by effect identity and cannot restore active behavior state by list index | catalog revision pinning, stat modifiers, remaining behavior families, and reconnect presentation proof |
|
||||
|
||||
## Ownership Invariants
|
||||
|
|
|
|||
|
|
@ -158,7 +158,10 @@ step advances canonical phase and projects orbital entity pose and velocity
|
|||
before Ymir. Body queries and Eve lower the same orbit graph.
|
||||
Each generated NPC also enters with a daemon-owned assigned patrol task over
|
||||
four shuffled orbit identities, matching the fossil activation behavior without
|
||||
letting the Unity client create agent state.
|
||||
letting the Unity client create agent state. The daemon derives hostility from
|
||||
the fossil owner-security and faction-relationship rule. A visible hostile ship
|
||||
pre-empts circuit movement through the existing combat planner; the patrol task
|
||||
keeps its circuit index and resumes it when contact is lost.
|
||||
|
||||
New Game now submits its advertised Eve operation, waits for a newly published
|
||||
daemon sector map, and selects the persisted 64-zone tutorial while
|
||||
|
|
|
|||
Loading…
Reference in a new issue