Place tutorial entities in daemon world

This commit is contained in:
Meta Vens 2026-07-17 08:57:40 +01:00
commit a30e4ee497
4 changed files with 53 additions and 4 deletions

View file

@ -154,6 +154,17 @@ internal sealed class AetheriaDaemonYmirSmokeChecks
first.Zones.Values.Any(zone => zone.Entities.Any(entity =>
entity.Kind == "ship" && entity.AgentTaskCapabilities.Contains("attack", StringComparer.Ordinal))),
"fossil station, defensive turret, and hostile-agent population must all survive materialization");
Require(first.Zones.Values.SelectMany(zone => zone.Entities)
.Where(entity => entity.Kind == "ship" && entity.AgentTaskCapabilities.Contains("attack", StringComparer.Ordinal))
.All(entity => entity.Position.X != 0 || entity.Position.Z != 0),
"generated non-player ships must retain the fossil activation rule that distributes zero-pose ships through the zone");
Require(first.Zones.Values.SelectMany(zone => zone.Entities)
.Where(entity => !string.IsNullOrWhiteSpace(entity.OrbitKey))
.All(entity => entity.Position.X != 0 || entity.Position.Z != 0),
"orbital stations and turrets must enter world truth at their resolved orbit position, not at the player origin");
var player = first.Zones[first.PlayerZoneIndex].Entities[first.PlayerEntityIndex];
Require(player.Position.X == 0 && player.Position.Z == 0,
"the controlled starter ship alone must retain the tutorial entrance origin");
Require(first.Zones.Values.All(zone =>
{
var orbitKeys = zone.Orbits.Select(orbit => orbit.OrbitKey).ToHashSet(StringComparer.Ordinal);

View file

@ -46,6 +46,7 @@ public static class AetheriaDaemonTutorialWorldMaterializer
{
var celestial = world.Zones[topology.ZoneIndex];
var random = new CultMath.Random(celestial.PostBodyRandomState);
var spawnRandom = new CultMath.Random(celestial.PostBodyRandomState ^ 0x51A7_10C5u);
var nearest = factions
.OrderBy(faction => distances[faction.CorporationKey][topology.ZoneIndex])
.First();
@ -131,11 +132,14 @@ public static class AetheriaDaemonTutorialWorldMaterializer
var enemyCount = (int)(random.NextFloat() * factionPresence * 2) + stationCount;
for (var enemyIndex = 0; enemyIndex < Math.Max(0, enemyCount); enemyIndex++)
{
var halfRadius = celestial.Radius * 0.5;
var enemy = AetheriaDaemonGeneratedEntityFactory.Create(
$"{nearest.ShortName} Ship {enemyIndex + 1}",
"ship",
nearest.CorporationKey,
Generator(nearest.CorporationKey).Build("ship", nearest.CorporationKey));
Generator(nearest.CorporationKey).Build("ship", nearest.CorporationKey),
spawnRandom.NextFloat((float)-halfRadius, (float)halfRadius),
spawnRandom.NextFloat((float)-halfRadius, (float)halfRadius));
enemy.AgentTaskCapabilities = ["attack", "defend", "explore"];
entities.Add(enemy);
}
@ -160,6 +164,8 @@ public static class AetheriaDaemonTutorialWorldMaterializer
playerEntityIndex = localPlayerIndex;
}
ApplyOrbitalPositions(orbits, entities);
zones.Add(topology.ZoneIndex, new AetheriaDaemonMaterializedTutorialZone(
topology,
celestial,
@ -179,6 +185,33 @@ public static class AetheriaDaemonTutorialWorldMaterializer
playerEntityIndex);
}
private static void ApplyOrbitalPositions(
IReadOnlyList<AetheriaOrbitSnapshot> orbits,
IReadOnlyList<AetheriaEntitySnapshot> entities)
{
var runtimeZone = new AetheriaRuntimeZoneSnapshotCommit
{
Orbits = orbits.Select(orbit => new AetheriaRuntimeOrbitSnapshotCommit
{
OrbitKey = orbit.OrbitKey,
ParentOrbitKey = orbit.ParentOrbitKey,
Distance = orbit.Distance,
Phase = orbit.Phase,
FixedPositionX = orbit.FixedPosition?.X ?? 0,
FixedPositionY = orbit.FixedPosition?.Y ?? 0
}).ToArray()
};
var positions = AetheriaRuntimeOrbitQueries.BuildPositions(runtimeZone);
foreach (var entity in entities.Where(entity => !string.IsNullOrWhiteSpace(entity.OrbitKey)))
{
if (!positions.TryGetValue(entity.OrbitKey, out var position))
throw new InvalidDataException(
$"Generated orbital entity '{entity.Name}' references missing orbit '{entity.OrbitKey}'.");
entity.Position.X = position.x;
entity.Position.Z = position.z;
}
}
private static IReadOnlyDictionary<int, int> DistancesFrom(
int start,
IReadOnlyDictionary<int, IReadOnlyList<int>> adjacency)

View file

@ -1145,6 +1145,10 @@ the Terminus proof arena cannot award completion.
- Outputs: 64 canonical zone documents, stable entity records, loadout
generation receipts, one controlled entrance ship, tutorial discovery state,
and an Aetheria-mode game session.
- 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
alone enters at the origin; generic clients do not repair spawn positions.
- Derived state: wormhole exits derive from adjacency; generic Eve maps,
cameras, scene objects, fog, and HUD state lower the persisted run.
- Forbidden writers: Unity generators, menu callbacks, scene materializers, and
@ -1155,8 +1159,9 @@ the Terminus proof arena cannot award completion.
- Cut line: New Game reloading the previous frame without sending its advertised
operation is dead. Celestial-only tutorial graphs are not playable runs.
- Verification layer: focused smokes prove deterministic population, resolved
orbital references, station/turret/agent presence, loadout provenance, all 64
persisted zones, canonical starter identity, discovery, and adjacency.
orbital references and poses, distributed non-player spawns,
station/turret/agent presence, loadout provenance, all 64 persisted zones,
canonical starter identity, discovery, and adjacency.
### Daemon Generated Zone Body Ownership

View file

@ -15,7 +15,7 @@ validation proves contract shape, not wiring, exclusivity, lowering, or parity.
| Organ | Original authority/evidence | Target owner | Eve contract | Runtime lowering | Current status | Required proof |
|---|---|---|---|---|---|---|
| Run/session state | `ServerShared`, `ActionGameManager` | Aetheria daemon/CultCache | provider/session advertisement plus run lifecycle bindings | all clients | active/failed lifecycle is daemon-owned and restart-stable: controlled-entity destruction commits the fossil cause, terminal frame, one `run.failed` fact, releases control, rejects later gameplay commands, and freezes the world; Eve receives terminal presentation/input levers without becoming an owner. Terminus keeps persistent requested simulation rate distinct from effective rate: compressed batches inspect authoritative facts after each fixed step, stop at the causal damage/destruction/cargo/thermal/docking/weapon/wormhole/run event, pause effectively without erasing the request, and project the exact cause and executed-step count through Eve. Docked, travelling, fighting, mining, looting, and refitting remain derived world conditions rather than counterfeit durable phases. The distinct tutorial is now daemon-owned and selectable: authored 64-zone topology, bodies, post-body station/turret/agent population, starter entry ship, loadout receipts, discovery, and canonical persistence feed an Aetheria-mode session. New Game submits its Eve operation and waits for the new tutorial sector-map publication. Terminus preserves `TutorialPassed` and cannot impersonate completion | source-backed tutorial completion rule (none survived in the fossil); replace the post-tutorial Terminus fallback with the authored regular-sector generator; full-daemon death/restart witness, broader attention taxonomy, and multi-client convergence |
| Run/session state | `ServerShared`, `ActionGameManager` | Aetheria daemon/CultCache | provider/session advertisement plus run lifecycle bindings | all clients | active/failed lifecycle is daemon-owned and restart-stable: controlled-entity destruction commits the fossil cause, terminal frame, one `run.failed` fact, releases control, rejects later gameplay commands, and freezes the world; Eve receives terminal presentation/input levers without becoming an owner. Terminus keeps persistent requested simulation rate distinct from effective rate: compressed batches inspect authoritative facts after each fixed step, stop at the causal damage/destruction/cargo/thermal/docking/weapon/wormhole/run event, pause effectively without erasing the request, and project the exact cause and executed-step count through Eve. Docked, travelling, fighting, mining, looting, and refitting remain derived world conditions rather than counterfeit durable phases. The distinct tutorial is now daemon-owned and selectable: authored 64-zone topology, bodies, post-body station/turret/agent population, resolved orbital placement, deterministic half-zone NPC distribution, starter entry ship, loadout receipts, discovery, and canonical persistence feed an Aetheria-mode session. New Game submits its Eve operation and waits for the new tutorial sector-map publication. Terminus preserves `TutorialPassed` and cannot impersonate completion | dynamic orbit-period simulation; source-backed tutorial completion rule (none survived in the fossil); replace the post-tutorial Terminus fallback with the authored regular-sector generator; full-daemon death/restart witness, broader attention taxonomy, and multi-client convergence |
| Entity identity/loadout | `ServerShared/Entity*` | daemon | typed entity and inventory surfaces | EveUnity/Electron | daemon-owned hull identity, exact installed placement/rotation and instance state, cargo stacks, weapon membership/held state, equipment wear, executable behavior state, weapon chronology, active-effect state, and loadout-generation provenance survive a hard CultCache close/reopen through the durable latest-frame checkpoint. An advertised generic Eve equip operation now translates into the typed daemon transaction before that checkpoint; its accepted placement survives restart and its consumed station-cargo source does not resurrect. Generic Eve rebuilds weapon and scalar thermostat controls from that restored truth; legacy per-entity documents are bootstrap inputs only when no live frame exists | live client refit witness and multi-client convergence |
| Loadout generation | map/faction availability, item cost, hull hardpoints and role constraints | daemon using canonical galaxy, faction knowledge/economy and typed catalog | generated loadout plus provenance/availability facts | Eve inspection and refit surfaces | daemon now uses the fossil candidate filter and seeded weighted selection: manufacturer must be present and allied, ally weight decays by home-zone graph distance, price uses the authored exponent, and hardpoints require matching semantic type, rotated footprint containment, and controller role. Prelude generation retains the fossil exception that allied catalog manufacturers need not own one of the six tutorial home zones; unknown manufacturers remain zero-weight. The April 2021 catalog fossil restores the distinct common `Longinus`, rare AetherDrive-compatible `LonginusX`, and `Djinni` rows with their original IDs, shapes, hardpoints, prices, drag, and prefabs, plus missing Thruster rows required to fill their authored mounts; typed `rarity:rare` tags apply a 0.05 generation multiplier to `LonginusX` and AetherDrive equipment; the generator owns one mutable CultMath stream, fixing the readonly-struct defensive-copy bug that restarted every weighted draw; placement rotation survives durable state and reconnect; generation fills the largest compatible footprint before economic weighting; cargo/capacitor plus station docking equipment must fit remaining hull space; stations pack faction-available inventory largest-first; ordinary ships begin without invented scenario cargo; every generated entity persists a source/selection receipt that reaches Eve as `loadout.item` semantics | multi-zone generated-world parity and exact root-to-zone stream sequencing against the fossil |
| Flight intent | `Ship`, `Thruster`, `AetherDrive`, `VelocityConversion`, `VelocityLimit`, input paths | daemon actuator simulation + Ymir integration | analog local helm/look/tractor plus actuator state | generic Eve input and scene lowerers | direct faction-speed velocity ownership is deleted; daemon persists local strafe/forward helm and desired look, allocates ordinary directional thruster banks by authored rotation, derives torque from hull/item placement, compensates lateral torque, gates execution through the shared energy ledger, deposits equipment-cell heat, publishes plume visibility and actuator state, applies hull drag, then runs operational velocity conversion with the fossil exponential damp equation and clamps the result through evaluated velocity limits before giving velocity/direction to Ymir. The evaluated limit remains in durable behavior state for reconnect and Eve inspection. Autonomous tasks translate world travel into the same local helm axes and issue look commands instead of mutating heading. AetherDrive rotor/coupling simulation is additive and explicitly non-default: common `Longinus` and `Djinni` hulls use ordinary thrusters; the rare `LonginusX` replaces those mounts with the single rare `Traction` AetherDrive path | hardpoint-transform direction parity, exact cross-equipment behavior execution ordering, CultMath/float32 parity harness, live production-frequency evidence, generic thrust/turn feedback lowering, and live handling screenshots |