From bcc4a8d960a3dfad88bf28dbc7152b547422deee Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Fri, 17 Jul 2026 06:04:48 +0100 Subject: [PATCH] Prove generic Eve refit survives restart --- Aetheria.State.Daemon.Smoke/Program.cs | 99 +++++++++++++++++-- .../AetheriaRuntimeRefitTransactions.cs | 63 +++++++++++- docs/aetheria-perfect-machine-map.md | 12 ++- docs/original-game-migration-matrix.md | 4 +- 4 files changed, 161 insertions(+), 17 deletions(-) diff --git a/Aetheria.State.Daemon.Smoke/Program.cs b/Aetheria.State.Daemon.Smoke/Program.cs index e78688e24..a98d4f291 100644 --- a/Aetheria.State.Daemon.Smoke/Program.cs +++ b/Aetheria.State.Daemon.Smoke/Program.cs @@ -6953,9 +6953,20 @@ internal sealed class AetheriaDaemonYmirSmokeChecks new AetheriaRuntimeBehaviorField(3, BoolValue(true)) ])); controller.Name = "Reconnect Controller"; - var catalog = new AetheriaRuntimeCatalogSnapshot([controller], [], []); + var hull = HullCatalogItem("reconnect-hull", 9, 7, 100); + var cargoBay = CatalogItem("reconnect-cargo-bay"); + cargoBay.InteriorShapeWidth = 6; + cargoBay.InteriorShapeHeight = 4; + var addedModule = CatalogItem("reconnect-added-module"); + addedModule.ShapeWidth = 1; + addedModule.ShapeHeight = 1; + addedModule.OccupiedCells = 1; + addedModule.ShapeCells = [new AetheriaRuntimeShapeCell(0, 0)]; + addedModule.HardpointType = ""; + var catalog = new AetheriaRuntimeCatalogSnapshot( + [hull, cargoBay, controller, addedModule], [], []); var ship = Entity(0, 0, "player"); - ship.HullItemKey = "reconnect-hull"; + ship.HullItemKey = hull.ItemKey; ship.Equipment = [ new AetheriaRuntimeLoadoutItemSlotCommit @@ -6981,7 +6992,7 @@ internal sealed class AetheriaDaemonYmirSmokeChecks { Item = new AetheriaRuntimeLoadoutItemCommit { - ItemKey = "reconnect-cargo-bay", + ItemKey = cargoBay.ItemKey, Quantity = 1, Quality = 1, Durability = 1, @@ -7079,15 +7090,85 @@ internal sealed class AetheriaDaemonYmirSmokeChecks } ] }; + var station = Entity(1, 0, "player"); + station.Kind = "station"; + station.DockingBayAssignments = [ship.EntityIndex]; + station.CargoBays = + [ + new AetheriaRuntimeLoadoutItemSlotCommit + { + Item = new AetheriaRuntimeLoadoutItemCommit + { + ItemKey = cargoBay.ItemKey, + Quantity = 1, + Quality = 1, + Durability = 1, + Enabled = true + } + } + ]; + station.CargoContents = + [ + Cargo((addedModule.ItemKey, 1, 0, 0)) + ]; var run = new AetheriaRuntimeRunCheckpointCommit { RunId = "loadout-restart-smoke", CurrentZoneIndex = 0, - Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [ship] }] + Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [ship, station] }] }; run.CurrentEntityKey = run.EntityRecordKey(0, ship.EntityIndex); var frame = AetheriaRuntimeDaemonFrameDocument.Create( run, "smoke", "loadout-restart", 41, 4.1, 0.1); + var shipKey = run.EntityRecordKey(0, ship.EntityIndex); + var stationKey = run.EntityRecordKey(0, station.EntityIndex); + var surface = AetheriaRuntimeDaemonGameSurfaceBuilder.Build( + frame, + new AetheriaRuntimeDaemonHealthDocument(), + AetheriaRuntimeDaemonCommandBoundaryDocument.Create("smoke"), + catalog: catalog); + var equipOperation = AetheriaRuntimeDaemonSurfaceCommandCatalog.CommandName( + AetheriaRuntimeDaemonCommandKinds.EquipItem); + Require(surface.Commands.Any(command => command.Command == equipOperation), + "docked Eve surface must advertise the generic equip operation before reconnect"); + var request = new EveSurfaceCommandRequest( + "aetheria", + AetheriaRuntimeDaemonGameSurfaceBuilder.PilotSurfaceId, + CultMesh.OperationInvocation(equipOperation), + CultMesh.OperationPayload( + ("entityId", shipKey), + ("sourceKind", AetheriaRuntimeRefitSourceKinds.Cargo), + ("originEntityKey", stationKey), + ("originIndex", "0"), + ("destinationEntityKey", shipKey), + ("itemKey", addedModule.ItemKey), + ("sourceX", "0"), + ("sourceY", "0"), + ("destinationX", "5"), + ("destinationY", "2"), + ("hasDestinationPosition", "true")), + DateTimeOffset.UtcNow, + "loadout-restart-smoke"); + Require(AetheriaRuntimeDaemonOperationsClient.TryCreateSurfaceCommandDocument( + request, + frame, + ".", + "loadout-restart-smoke", + "refit", + out var equipCommand) && equipCommand != null, + "generic Eve equip request must translate into a typed daemon command"); + var refit = AetheriaRuntimeDaemonOperations.Execute( + run, + [equipCommand!], + new AetheriaRuntimeDaemonOperationContext { Catalog = catalog }); + Require(refit.AppliedCommandIds.Contains(equipCommand!.CommandId, StringComparer.Ordinal), + "generic Eve equip command must commit through the atomic daemon refit transaction: " + + string.Join(",", refit.RejectedCommandReasons.Values)); + Require(ship.Equipment.Any(slot => slot.Item.ItemKey == addedModule.ItemKey && + slot.X == 5 && slot.Y == 2), + "accepted generic refit must place the exact module in daemon world truth"); + RequireEqual(0, station.CargoContents.Single().Items.Count, + "accepted generic refit must remove the exact source item atomically"); using (var node = AetheriaStateNode.OpenAsync(statePath).GetAwaiter().GetResult()) { @@ -7103,8 +7184,9 @@ internal sealed class AetheriaDaemonYmirSmokeChecks AetheriaRuntimeVerseRecordKeys.DaemonFrameLatest) .ReadAsync().GetAwaiter().GetResult() ?? throw new InvalidOperationException("Durable loadout restart frame is missing."); - var restored = durable.Run.Zones.Single().Entities.Single(); - var installed = restored.Equipment.Single(); + var restored = durable.Run.Zones.Single().Entities.Single(entity => entity.EntityIndex == ship.EntityIndex); + var restoredStation = durable.Run.Zones.Single().Entities.Single(entity => entity.EntityIndex == station.EntityIndex); + var installed = restored.Equipment.Single(slot => slot.Item.ItemKey == controller.ItemKey); RequireEqual("reconnect-hull", restored.HullItemKey, "CultCache restart must preserve daemon-owned hull identity"); Require(installed.X == 3 && installed.Y == 4 && installed.Rotation == "CounterClockwise", @@ -7135,6 +7217,11 @@ internal sealed class AetheriaDaemonYmirSmokeChecks "CultCache restart must preserve active consumable execution state"); Require(restored.LoadoutGeneration?.Selections.Single().ItemKey == controller.ItemKey, "CultCache restart must preserve generated-loadout provenance"); + Require(restored.Equipment.Any(slot => slot.Item.ItemKey == addedModule.ItemKey && + slot.X == 5 && slot.Y == 2), + "CultCache restart must preserve the generic Eve refit's accepted placement"); + RequireEqual(0, restoredStation.CargoContents.Single().Items.Count, + "CultCache restart must not resurrect the generic refit's consumed source item"); var capability = AetheriaRuntimeInputCapabilityDocument.FromFrame( durable, catalog: catalog); diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeRefitTransactions.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeRefitTransactions.cs index 70543b882..7a60ac84c 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeRefitTransactions.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeRefitTransactions.cs @@ -240,7 +240,11 @@ namespace GameCult.Aetheria.State.Verse return false; } - destinationPlan.AddInstalled(placed, item.Category, source.PreservedWeaponGroups); + destinationPlan.AddInstalled( + placed, + item.Category, + source.PreservedWeaponGroups, + ReferenceEquals(originPlan, destinationPlan) ? source.OriginalEquipmentIndex : -1); originPlan.Apply(); if (!ReferenceEquals(originPlan, destinationPlan)) destinationPlan.Apply(); @@ -567,18 +571,21 @@ namespace GameCult.Aetheria.State.Verse { public AetheriaRuntimeLoadoutItemSlotCommit Slot { get; set; } = null!; public IReadOnlyList PreservedWeaponGroups { get; set; } = Array.Empty(); + public int OriginalEquipmentIndex { get; set; } = -1; } private sealed class EntityPlan { private readonly AetheriaRuntimeEntitySnapshotCommit _entity; private readonly List> _weaponGroups; + private readonly List _equipmentOrigins; public EntityPlan(AetheriaRuntimeEntitySnapshotCommit entity) { _entity = entity; Equipment = (entity.Equipment ?? Array.Empty()) .Select(CloneSlot).ToList(); + _equipmentOrigins = Enumerable.Range(0, Equipment.Count).ToList(); CargoBays = (entity.CargoBays ?? Array.Empty()) .Select(CloneSlot).ToList(); DockingBays = (entity.DockingBays ?? Array.Empty()) @@ -624,8 +631,12 @@ namespace GameCult.Aetheria.State.Verse if (string.Equals(sourceKind, AetheriaRuntimeRefitSourceKinds.Equipment, StringComparison.Ordinal)) { + var originalEquipmentIndex = sourceIndex >= 0 && sourceIndex < _equipmentOrigins.Count + ? _equipmentOrigins[sourceIndex] + : -1; if (!TryTake(Equipment, sourceIndex, itemKey, out var slot)) return Reject(AetheriaRuntimeDaemonRejectionReasons.InvalidRefitSource, out rejectionReason); + _equipmentOrigins.RemoveAt(sourceIndex); var preservedGroups = _weaponGroups .Select((group, index) => (group, index)) .Where(value => value.group.Contains(sourceIndex)) @@ -637,7 +648,12 @@ namespace GameCult.Aetheria.State.Verse for (var index = 0; index < group.Count; index++) if (group[index] > sourceIndex) group[index]--; } - source = new SourceSelection { Slot = CloneSlot(slot), PreservedWeaponGroups = preservedGroups }; + source = new SourceSelection + { + Slot = CloneSlot(slot), + PreservedWeaponGroups = preservedGroups, + OriginalEquipmentIndex = originalEquipmentIndex + }; return true; } @@ -677,7 +693,8 @@ namespace GameCult.Aetheria.State.Verse public void AddInstalled( AetheriaRuntimeLoadoutItemSlotCommit slot, string? category, - IReadOnlyList preservedWeaponGroups) + IReadOnlyList preservedWeaponGroups, + int originalEquipmentIndex) { if (string.Equals(category, AetheriaRuntimeItemCategories.CargoBay, StringComparison.Ordinal)) { @@ -695,6 +712,7 @@ namespace GameCult.Aetheria.State.Verse var equipmentIndex = Equipment.Count; Equipment.Add(slot); + _equipmentOrigins.Add(originalEquipmentIndex); foreach (var groupIndex in preservedWeaponGroups) if (groupIndex >= 0 && groupIndex < _weaponGroups.Count) _weaponGroups[groupIndex].Add(equipmentIndex); @@ -715,10 +733,45 @@ namespace GameCult.Aetheria.State.Verse _entity.WeaponGroups = _weaponGroups .Select(group => (IReadOnlyList)group.Distinct().OrderBy(index => index).ToArray()) .ToArray(); - _entity.WeaponStates = Array.Empty(); + RemapEquipmentOwnedState(); + } + + private void RemapEquipmentOwnedState() + { + var newIndices = _equipmentOrigins + .Select((originalIndex, newIndex) => (originalIndex, newIndex)) + .Where(value => value.originalIndex >= 0) + .ToDictionary(value => value.originalIndex, value => value.newIndex); + + _entity.EquipmentStates = (_entity.EquipmentStates ?? Array.Empty()) + .Where(state => state != null && newIndices.ContainsKey(state.EquipmentIndex)) + .Select(state => + { + state.EquipmentIndex = newIndices[state.EquipmentIndex]; + return state; + }) + .ToArray(); _entity.BehaviorStates = (_entity.BehaviorStates ?? Array.Empty()) .Where(state => state != null && - !string.Equals(state.OwnerKind, AetheriaRuntimeBehaviorStateProjector.EquipmentOwnerKind, StringComparison.Ordinal)) + (!string.Equals(state.OwnerKind, AetheriaRuntimeBehaviorStateProjector.EquipmentOwnerKind, StringComparison.Ordinal) || + newIndices.ContainsKey(state.OwnerIndex))) + .Select(state => + { + if (string.Equals(state.OwnerKind, AetheriaRuntimeBehaviorStateProjector.EquipmentOwnerKind, StringComparison.Ordinal)) + state.OwnerIndex = newIndices[state.OwnerIndex]; + return state; + }) + .ToArray(); + _entity.WeaponStates = (_entity.WeaponStates ?? Array.Empty()) + .Where(state => state != null && + (!string.Equals(state.OwnerKind, AetheriaRuntimeBehaviorStateProjector.EquipmentOwnerKind, StringComparison.Ordinal) || + newIndices.ContainsKey(state.OwnerIndex))) + .Select(state => + { + if (string.Equals(state.OwnerKind, AetheriaRuntimeBehaviorStateProjector.EquipmentOwnerKind, StringComparison.Ordinal)) + state.OwnerIndex = newIndices[state.OwnerIndex]; + return state; + }) .ToArray(); } diff --git a/docs/aetheria-perfect-machine-map.md b/docs/aetheria-perfect-machine-map.md index 2091b2ce1..f5c490261 100644 --- a/docs/aetheria-perfect-machine-map.md +++ b/docs/aetheria-perfect-machine-map.md @@ -896,7 +896,9 @@ lifecycle state. - Inputs: accepted daemon transactions plus the complete runtime entity state: hull, installed placement and rotation, per-instance item fields, cargo and bay contents, weapon groups, equipment/behavior/weapon/consumable state, and - loadout-generation receipts. + loadout-generation receipts. Generic Eve refit requests enter through the + advertised operation and are translated into that same typed transaction; + Eve never writes the checkpoint directly. - Outputs: one typed CultCache frame that can be closed, reopened in a fresh state node, resumed by the daemon, and projected into Eve without rebuilding gameplay state from Unity objects or client input. @@ -904,13 +906,15 @@ lifecycle state. durability, thermal state, cargo quantity, held groups, wear, specialized chain chronology, ammunition, shot sequence, active-effect execution state, and generation provenance survive the real directory store. Restored Eve - weapon and thermostat levers derive from that same frame. + weapon and thermostat levers derive from that same frame. A refit preserves + state owned by surviving equipment indices, removes state owned by removed + equipment, and gives newly installed cargo no invented runtime history. - Forbidden writers: Unity loadout mirrors, Eve lowerers, and bootstrap entity documents cannot overwrite an active latest-frame checkpoint. - Verification layer: daemon smoke hard-flushes the frame, disposes the first `AetheriaStateNode`, opens a second node, verifies the complete loadout and - behavior state, then rebuilds generic Eve input capabilities from the - restored frame. + behavior state plus an accepted generic Eve equipment placement and consumed + source, then rebuilds generic Eve input capabilities from the restored frame. ### Daemon Orbital Turret Controller diff --git a/docs/original-game-migration-matrix.md b/docs/original-game-migration-matrix.md index cd4738b5d..3747ffacb 100644 --- a/docs/original-game-migration-matrix.md +++ b/docs/original-game-migration-matrix.md @@ -16,7 +16,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. Docked, travelling, fighting, mining, looting, and refitting remain derived world conditions rather than counterfeit durable phases | full-daemon death/restart witness, multi-client convergence, and a source-backed game-mode completion rule; the fossil supplied no victory transition | -| 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. 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 refit-to-restart witness 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; 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 | | Collision | Unity physics and entity handlers | Ymir | collision/result facts | visual interpolation only | Ymir.Core world stepping wired for entity bodies, radial fields and contacts; missing-owner negative proof passes | publish authoritative collision events and prove Unity cannot write transforms back | @@ -29,7 +29,7 @@ validation proves contract shape, not wiring, exclusivity, lowering, or parity. | Tractor/scoop | analog tractor and physical timed pickups | daemon + Ymir | tractor power, pickup body and receipt | pickup world entity plus beam/interaction feedback | fossil 2/s power ramp and authored forward radius/traction/distance feed pickup bodies into the shared Ymir world; ship contact collects exactly once when capacity permits or applies the fossil 25-unit rejection kick; Aetheria maps pickup collision only against ship bodies, so stations and other world bodies cannot become cargo writers; 30s daemon lifetime; instant cargo teleport deleted; current-zone pickups occupy ordinary rows in the shared Eve entity SoA body with stable logical identity and provider-owned assets; `pilot.scoop` advertises a generic `button-hold.v1` scalar contract, the released client sends `1` on press and `0` on release through `SetTractorPower`, both receipts reconcile, and daemon power returns exactly to zero; the provider build removes the fossil's embedded tractor object from ship prefabs, and `beam.presentation` attaches the sole standalone effect to the SoA ship; released warm witnesses prove successful exactly-once Ymir collection and controlled-player `cargo-capacity` rejection with unchanged cargo | polish provider beam shape/attachment | | Dock/undock | wormhole-first interact; first eligible bay; component prerequisites | daemon + Ymir placement | dock commands/context | camera/UI transition | daemon docking now preserves the fossil transaction: stable zone order selects the first entity with a real empty bay inside the fixed 25-unit XZ center radius; undock checks installed Cockpit, Thruster-or-AetherDrive, Reactor, then empty bay cargo in source order; rejection reasons survive committed command facts into generic Eve receipts; success removes parentage without moving, stopping, or rotating the ship. Accepted transitions additionally publish retained `ship.docked` / `ship.undocked` feedback with exact actor, parent, bay, frame and position; rejected operations publish no false transition. Docking clears held-group and transient weapon state, docked actors reject later fire commands, and combat subtracts docked child identities from both attacker iteration and target lookup, so stale input, forged pulses, and turret controllers cannot fire through a bay. Ymir world entities retain gravity/query bodies but do not invent ship/station rigidbody collision response that would make the 25-unit rule unreachable; ship/pickup Begin contacts remain enabled. The live generic Unity witness also proves advertised actions, bay assignment, dock-parent SoA inclusion and sensor sharing, camera retargeting, controlled-subject hiding, movement suppression, and restoration on undock | reconnect, docked inventory/trade surfaces, and authored dock-camera blend/effect lowering | | Wormhole travel | `ActionGameManager.EnterWormhole` plus `Ship` enter/exit state | daemon fixed-step transition plus Ymir exclusion | current-entity transition state and feedback chronology | provider-selected generic transition lowering | command acceptance no longer moves an entity immediately; player interact, explicit travel, and agent routing derive the same canonical adjacent-zone wormhole and proximity gate, then persist four-second `entering`, authoritative transfer, four-second `exiting`, and one-frame `completed` phases. The daemon owns smootherstep XZ motion, the fossil 1000-unit visual-depth lever, deterministic 20-unit exit direction, 50-unit exit radius, target-adjacency discovery, and exactly-once start/transfer/completion events. Flight and every world-physics implementation receive no transitioning body; Ymir resumes on the tick after completion. Smoke serializes and rehydrates mid-entry and proves agents traverse multi-edge routes through the same command boundary | generic provider transition effect/camera lowering and live multi-zone pilot witness | -| Inventory/equipment | `Entity.ItemFits`, `TryEquip`, `TryUnequip`, `EquippedCargoBay.ItemFits`/`TryStore`, inventory panels + ServerShared | daemon atomic inventory transactions | typed inventory/equipment state, commands, exact receipts | generic UI | generated cargo and docking bays now occupy their typed collections instead of masquerading as ordinary equipment; docked equip/reposition/store validates the complete destination on cloned state before committing either side, uses authored hull interior and hardpoint geometry, forces hardpoint rotation, rejects occupied cells and deployed ships, preserves exact item instance fields and weapon-group membership, routes bay categories with aligned content/assignment arrays, and refuses to unequip nonempty/occupied bays. Cargo transfer now uses the same plan-first rule with typed bay interiors, rotated item footprints, deterministic free-cell search, exact stack maxima, and no partial source/destination mutation on rejection; exact reasons reach generic Eve receipts | live generic refit controls, reconnect after accepted and rejected moves, commodity multi-stack chronology, and full fossil inventory feedback parity | +| Inventory/equipment | `Entity.ItemFits`, `TryEquip`, `TryUnequip`, `EquippedCargoBay.ItemFits`/`TryStore`, inventory panels + ServerShared | daemon atomic inventory transactions | typed inventory/equipment state, commands, exact receipts | generic UI | generated cargo and docking bays now occupy their typed collections instead of masquerading as ordinary equipment; docked equip/reposition/store validates the complete destination on cloned state before committing either side, uses authored hull interior and hardpoint geometry, forces hardpoint rotation, rejects occupied cells and deployed ships, preserves exact item instance fields, surviving equipment behavior/weapon state, and weapon-group membership, routes bay categories with aligned content/assignment arrays, and refuses to unequip nonempty/occupied bays. Cargo transfer now uses the same plan-first rule with typed bay interiors, rotated item footprints, deterministic free-cell search, exact stack maxima, and no partial source/destination mutation on rejection; exact reasons reach generic Eve receipts. The generic Eve equip request-to-transaction-to-CultCache-restart path is covered, including source non-resurrection | live generic refit control lowering, reconnect after rejected moves, commodity multi-stack chronology, and full fossil inventory feedback parity | | Trade | trade UI/economy | daemon | stock, quote, purchase/sale commands | generic UI | daemon derives station, stock row, catalog item, current price, quantity, credits, target capacity, and hull-vs-cargo result; purchases ignore client opinions about price and destination kind. Docked sales identify only the source stack and quantity: the daemon derives station, instance-sensitive value, payout, deterministic typed stock placement, and commits cargo/credits atomically; generic Eve advertises available buy and sell actions. The generic Unity live witness receives an authoritative purchase receipt while paused | live sale receipt witness, quote expiry, contested stock, and full trade surface parity | | Action bar/input | input shell and rebinding UI | daemon advertises actions; client owns bindings | input capability and action descriptors | universal binding UI | contracts wired; consumable rows are catalog-filtered, expose daemon-owned cargo quantity, active-effect count, exact duration/remaining duration, and the fossil `remaining / duration` fill as `remaining-ratio.v1`. A depleted or non-stackable active consumable remains visible with unavailable command state until its effect ends, so a lowerer never queries Unity gameplay objects. Weapon groups advertise the fossil press/release lifecycle as generic `button-hold.v1` values routed to `SetWeaponGroupActive`; daemon entity state persists the active-group latch, press edges pulse instant/charged weapons once, constant weapons remain selected without client keepalives, release clears the latch, and the one-shot compatibility command is not the public input contract. Equipment actions derive from exact installed catalog payload/state pairs instead of blindly activating behavior zero: Switch and Trigger expose generic hold values, adjustable Thermotoggle exposes a canonical `scalar.v1` Kelvin target/current value, fixed/non-interactive rows are omitted, and offline rows retain identity as unavailable. Target nearest/previous/next/reticle/clear actions expose daemon-owned current selection, visible-hostile count, and availability; the default profile preserves fossil `R/T/Y/U`, restores the shared wormhole-first/then-dock interaction to `F`, and routes `view-direction.v1` world XYZ through the released generic runtime while target ranking remains daemon policy. Released EveUnity lowers provider action-bar suggestions with provider-owned icon asset keys, availability, one-shot, held, view-direction, and explicit scalar controls without importing Aetheria. Every value form translates through generic Eve requests into typed daemon commands under provider identity `aetheria` | generic slot editing/persistence, consumable quantity/progress chrome, provider styling, and live action-bar witness | | Pilot camera | ARPG follow/dock cameras | Eve view semantics + runtime | camera rig and semantic view id | EveUnity camera rig | daemon dock state selects distinct contracts instead of collapsing both fossil cameras: undocked flight advertises the entity-forward `Third Person Rig` framing (distance 30, screen 0.64/0.19, FOV 60, lens 1-4096) and the shared `aim.presentation` convergence point as its look-at; Aetheria translates the fossil Cinemachine top-origin screen Y of `0.81` to Eve's bottom-origin viewport Y of `0.19`; EveUnity solves native pose while preserving both optical aim and advertised entity framing, without provider types; the retained dock projection remains separate | reproduce the dock camera's separate follow/look-at subjects, blend timeline, noise, and reconnect state |