Publish guided weapon presentation profiles
This commit is contained in:
parent
dad0770174
commit
47d02441ff
6 changed files with 221 additions and 7 deletions
|
|
@ -155,6 +155,7 @@ internal sealed class AetheriaDaemonYmirSmokeChecks
|
|||
VolumeSurfaceKeepsNativeShaderAbiInAssetVariant,
|
||||
YmirMovesProjectileAndReportsStableContact,
|
||||
InstantWeaponRequestSurvivesLockAcquisition,
|
||||
GuidedWeaponPublishesPresentationProfileWithoutPhysicalAuthority,
|
||||
CombatLockSurvivesPublicFrameRestart,
|
||||
ConstantWeaponRunsOnDaemonThroughYmirBeamContact,
|
||||
ChargedWeaponCannotBypassChargeLifecycle,
|
||||
|
|
@ -2644,6 +2645,100 @@ internal sealed class AetheriaDaemonYmirSmokeChecks
|
|||
"Eve feedback must project the daemon lock-loss transition and its prior completed progress");
|
||||
}
|
||||
|
||||
private static void GuidedWeaponPublishesPresentationProfileWithoutPhysicalAuthority()
|
||||
{
|
||||
var source = Entity(0, 0, "player");
|
||||
source.DirectionX = 1;
|
||||
source.TargetEntityIndex = 1;
|
||||
source.Contacts = [new AetheriaRuntimeEntityContactCommit
|
||||
{
|
||||
TargetEntityIndex = 1, InfoGathered = 1, Hostile = true, Visible = true
|
||||
}];
|
||||
source.WeaponGroups = [new[] { 0 }];
|
||||
source.Equipment = [new AetheriaRuntimeLoadoutItemSlotCommit
|
||||
{
|
||||
Item = new AetheriaRuntimeLoadoutItemCommit
|
||||
{ ItemKey = "test-launcher", Quality = 1, Durability = 1, Enabled = true }
|
||||
}];
|
||||
var target = Entity(1, 80, "raider");
|
||||
var zone = new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [source, target] };
|
||||
var run = new AetheriaRuntimeRunCheckpointCommit
|
||||
{
|
||||
RunId = "guided-presentation-smoke", CurrentZoneIndex = 0,
|
||||
CurrentEntityKey = "zone.0.entity.0", Zones = [zone]
|
||||
};
|
||||
var guidance = Curve(
|
||||
new AetheriaRuntimeCurveKey(0, 0.25, 0, 0),
|
||||
new AetheriaRuntimeCurveKey(1, 1, 0, 0));
|
||||
var thrust = Curve(
|
||||
new AetheriaRuntimeCurveKey(0, 1, 0, 0),
|
||||
new AetheriaRuntimeCurveKey(1, 0.5, 0, 0));
|
||||
var lift = Curve(
|
||||
new AetheriaRuntimeCurveKey(0, 0, 0, 0),
|
||||
new AetheriaRuntimeCurveKey(1, 0.75, 0, 0));
|
||||
var payload = new AetheriaRuntimeBehaviorPayload(0, AetheriaRuntimeBehaviorKinds.Launcher, 0,
|
||||
[
|
||||
new AetheriaRuntimeBehaviorField(2, PerformanceStat(10)),
|
||||
new AetheriaRuntimeBehaviorField(6, PerformanceStat(150)),
|
||||
new AetheriaRuntimeBehaviorField(16, PerformanceStat(20)),
|
||||
new AetheriaRuntimeBehaviorField(17, PerformanceStat(1)),
|
||||
new AetheriaRuntimeBehaviorField(19, PerformanceStat(0.1)),
|
||||
new AetheriaRuntimeBehaviorField(21, PerformanceStat(100)),
|
||||
new AetheriaRuntimeBehaviorField(22, PerformanceStat(0)),
|
||||
new AetheriaRuntimeBehaviorField(23, PerformanceStat(180)),
|
||||
new AetheriaRuntimeBehaviorField(24, PerformanceStat(1)),
|
||||
new AetheriaRuntimeBehaviorField(25, PerformanceStat(1)),
|
||||
new AetheriaRuntimeBehaviorField(26, guidance),
|
||||
new AetheriaRuntimeBehaviorField(27, thrust),
|
||||
new AetheriaRuntimeBehaviorField(28, lift),
|
||||
new AetheriaRuntimeBehaviorField(29, PerformanceStat(5)),
|
||||
new AetheriaRuntimeBehaviorField(30, Number(2)),
|
||||
new AetheriaRuntimeBehaviorField(31, PerformanceStat(40))
|
||||
]);
|
||||
var catalog = new AetheriaRuntimeCatalogSnapshot([CatalogItem("test-launcher", payload)], [], []);
|
||||
var fire = new AetheriaRuntimeDaemonIntentState();
|
||||
fire.WeaponGroups.Add(new AetheriaRuntimeDaemonWeaponGroupIntent
|
||||
{ ActorEntityKey = run.CurrentEntityKey, WeaponGroup = 0, Fire = true, Active = true });
|
||||
|
||||
for (var frame = 0; frame < 4 && run.ShotReceipts.Count == 0; frame++)
|
||||
AetheriaRuntimeDaemonSimulation.Step(run,
|
||||
frame == 0 ? fire : new AetheriaRuntimeDaemonIntentState(), 0.1,
|
||||
AetheriaRuntimeDaemonSimulationSettings.AetheriaDefault,
|
||||
NewPhysics(), catalog, frame, frame * 0.1);
|
||||
|
||||
RequireEqual(0, zone.PhysicalPayloads.Count,
|
||||
"fossil guided presentation must not be promoted into a damage-owning Ymir body");
|
||||
var receipt = run.ShotReceipts.Single(value => value.WeaponItemKey == "test-launcher");
|
||||
RequireEqual("guided", receipt.PresentationKind,
|
||||
"Launcher receipts must select the generic guided presentation path");
|
||||
RequireEqual("target-entity", receipt.GuidanceMode,
|
||||
"Launcher guidance must retain its fossil target-entity mode");
|
||||
RequireEqual(2, receipt.GuidanceCurve.Count,
|
||||
"guided receipt must retain the complete authored guidance curve");
|
||||
RequireNear(5, receipt.GuidanceThrust, 0.000001,
|
||||
"guided receipt must retain evaluated authored thrust");
|
||||
RequireNear(40, receipt.GuidanceTopSpeed, 0.000001,
|
||||
"guided receipt must retain evaluated authored missile velocity");
|
||||
RequireNear(2, receipt.GuidanceDodgeFrequency, 0.000001,
|
||||
"guided receipt must retain authored dodge frequency");
|
||||
var restored = MessagePack.MessagePackSerializer.Deserialize<AetheriaRuntimeRunCheckpointCommit>(
|
||||
MessagePack.MessagePackSerializer.Serialize(run));
|
||||
RequireEqual("target-entity", restored.ShotReceipts.Single().GuidanceMode,
|
||||
"guided presentation levers must survive daemon checkpoint restart");
|
||||
var surface = AetheriaRuntimeDaemonGameSurfaceBuilder.Build(
|
||||
new AetheriaRuntimeDaemonFrameDocument { FrameId = 4, Run = run },
|
||||
new AetheriaRuntimeDaemonHealthDocument(),
|
||||
AetheriaRuntimeDaemonCommandBoundaryDocument.Create("daemon"));
|
||||
var shot = Flatten(surface.Surface.Root).Single(node =>
|
||||
node.Kind == "shot.receipt" && node.Props.TryGetValue("itemKey", out var itemKey) &&
|
||||
itemKey == "test-launcher");
|
||||
Require(shot.Props["guidanceMode"] == "target-entity" &&
|
||||
shot.Props["guidanceCurve"] == "0,0.25,0,0;1,1,0,0" &&
|
||||
shot.Props["guidanceThrustCurve"] == "0,1,0,0;1,0.5,0,0" &&
|
||||
shot.Props["guidanceLiftCurve"] == "0,0,0,0;1,0.75,0,0",
|
||||
"generic Eve shot facts must expose the authored guidance curves without provider internals");
|
||||
}
|
||||
|
||||
private static void DeployableWeaponRunsThroughYmirAndDetonatesOnDaemon()
|
||||
{
|
||||
var source = Entity(0, 0, "player");
|
||||
|
|
|
|||
|
|
@ -558,6 +558,13 @@ namespace GameCult.Aetheria.State.Verse
|
|||
("presentationDuration", FormatNumber(value.PresentationDurationSeconds)),
|
||||
("presentationKind", value.PresentationKind),
|
||||
("presentationIntensity", FormatNumber(value.PresentationIntensity)),
|
||||
("guidanceMode", value.GuidanceMode),
|
||||
("guidanceCurve", FormatCurve(value.GuidanceCurve)),
|
||||
("guidanceThrustCurve", FormatCurve(value.GuidanceThrustCurve)),
|
||||
("guidanceLiftCurve", FormatCurve(value.GuidanceLiftCurve)),
|
||||
("guidanceThrust", FormatNumber(value.GuidanceThrust)),
|
||||
("guidanceTopSpeed", FormatNumber(value.GuidanceTopSpeed)),
|
||||
("guidanceDodgeFrequency", FormatNumber(value.GuidanceDodgeFrequency)),
|
||||
("impactKind", value.ImpactKind),
|
||||
("impactAngleRoll", FormatNumber(value.ImpactAngleRoll)),
|
||||
("impactRadiusRoll", FormatNumber(value.ImpactRadiusRoll))
|
||||
|
|
@ -758,6 +765,15 @@ namespace GameCult.Aetheria.State.Verse
|
|||
return value.ToString("0.###", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
private static string FormatCurve(IReadOnlyList<AetheriaRuntimeCurveKey>? keys)
|
||||
{
|
||||
return keys == null || keys.Count == 0
|
||||
? ""
|
||||
: string.Join(";", keys.Select(key => string.Join(",",
|
||||
FormatNumber(key.Time), FormatNumber(key.Value),
|
||||
FormatNumber(key.InTangent), FormatNumber(key.OutTangent))));
|
||||
}
|
||||
|
||||
private static string Join(IReadOnlyList<string>? values)
|
||||
{
|
||||
return values == null || values.Count == 0
|
||||
|
|
|
|||
|
|
@ -1586,7 +1586,14 @@ namespace GameCult.Aetheria.State.Verse
|
|||
DamageSpread = weapon.DamageSpread,
|
||||
ArmorAppliedDamage = damage.ArmorAppliedDamage,
|
||||
EquipmentAppliedDamage = damage.EquipmentAppliedDamage,
|
||||
DamageCells = damage.Cells
|
||||
DamageCells = damage.Cells,
|
||||
GuidanceMode = weapon.Guidance?.Mode ?? "none",
|
||||
GuidanceCurve = weapon.Guidance?.GuidanceCurve ?? Array.Empty<AetheriaRuntimeCurveKey>(),
|
||||
GuidanceThrustCurve = weapon.Guidance?.ThrustCurve ?? Array.Empty<AetheriaRuntimeCurveKey>(),
|
||||
GuidanceLiftCurve = weapon.Guidance?.LiftCurve ?? Array.Empty<AetheriaRuntimeCurveKey>(),
|
||||
GuidanceThrust = weapon.Guidance?.Thrust ?? 0,
|
||||
GuidanceTopSpeed = weapon.Guidance?.TopSpeed ?? 0,
|
||||
GuidanceDodgeFrequency = weapon.Guidance?.DodgeFrequency ?? 0
|
||||
});
|
||||
AetheriaRuntimeGameEvents.Append(run, new AetheriaRuntimeGameEventCommit
|
||||
{
|
||||
|
|
@ -1913,7 +1920,24 @@ namespace GameCult.Aetheria.State.Verse
|
|||
Math.Max(0, behavior.EvaluateStat(15)),
|
||||
ReadEnumName(behavior.Payload, 1, "Kinetic"),
|
||||
Math.Max(0, behavior.EvaluateStat(3)),
|
||||
Math.Max(0, behavior.EvaluateStat(4)));
|
||||
Math.Max(0, behavior.EvaluateStat(4)),
|
||||
ResolveGuidedPresentation(behavior));
|
||||
}
|
||||
|
||||
private static ResolvedGuidedPresentation? ResolveGuidedPresentation(
|
||||
AetheriaRuntimeEquippedBehavior behavior)
|
||||
{
|
||||
if (string.Equals(behavior.Payload.Kind, AetheriaRuntimeBehaviorKinds.Launcher, StringComparison.Ordinal))
|
||||
return new ResolvedGuidedPresentation(
|
||||
"target-entity", ReadCurve(behavior.Payload, 26), ReadCurve(behavior.Payload, 27),
|
||||
ReadCurve(behavior.Payload, 28), Math.Max(0, behavior.EvaluateStat(29)),
|
||||
Math.Max(0, behavior.EvaluateStat(31)), Math.Max(0, ReadNumber(behavior.Payload, 30)));
|
||||
if (string.Equals(behavior.Payload.Kind, AetheriaRuntimeBehaviorKinds.GuidedWeapon, StringComparison.Ordinal))
|
||||
return new ResolvedGuidedPresentation(
|
||||
"look-direction", ReadCurve(behavior.Payload, 21), ReadCurve(behavior.Payload, 22),
|
||||
ReadCurve(behavior.Payload, 23), Math.Max(0, behavior.EvaluateStat(24)),
|
||||
Math.Max(0, behavior.EvaluateStat(26)), Math.Max(0, ReadNumber(behavior.Payload, 25)));
|
||||
return null;
|
||||
}
|
||||
|
||||
private static ResolvedWeapon ResolveFallbackWeapon(
|
||||
|
|
@ -1983,7 +2007,7 @@ namespace GameCult.Aetheria.State.Verse
|
|||
if (string.Equals(behaviorKind, AetheriaRuntimeBehaviorKinds.ConstantWeapon, StringComparison.Ordinal))
|
||||
return "stream";
|
||||
if (string.Equals(behaviorKind, AetheriaRuntimeBehaviorKinds.Launcher, StringComparison.Ordinal))
|
||||
return "bolt";
|
||||
return "guided";
|
||||
return "bolt";
|
||||
}
|
||||
|
||||
|
|
@ -2000,6 +2024,22 @@ namespace GameCult.Aetheria.State.Verse
|
|||
.FirstOrDefault(field => field != null && field.Key == key)?.Value?.NumberValue ?? 0;
|
||||
}
|
||||
|
||||
private static IReadOnlyList<AetheriaRuntimeCurveKey> ReadCurve(
|
||||
AetheriaRuntimeBehaviorPayload payload, int key)
|
||||
{
|
||||
var value = (payload.Fields ?? Array.Empty<AetheriaRuntimeBehaviorField>())
|
||||
.FirstOrDefault(field => field != null && field.Key == key)?.Value;
|
||||
var children = value?.Children ?? Array.Empty<AetheriaRuntimeBehaviorValue>();
|
||||
if (children.Count == 1 && children[0]?.Children?.Count > 0)
|
||||
children = children[0].Children;
|
||||
return children
|
||||
.Where(child => child?.Children != null && child.Children.Count >= 4)
|
||||
.Select(child => new AetheriaRuntimeCurveKey(
|
||||
child.Children[0].NumberValue, child.Children[1].NumberValue,
|
||||
child.Children[2].NumberValue, child.Children[3].NumberValue))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private static bool ReadBool(AetheriaRuntimeBehaviorPayload payload, int key)
|
||||
{
|
||||
return (payload.Fields ?? Array.Empty<AetheriaRuntimeBehaviorField>())
|
||||
|
|
@ -2102,7 +2142,8 @@ namespace GameCult.Aetheria.State.Verse
|
|||
double lockSpeed, double lockSensorImpact,
|
||||
double lockAngleDegrees, double lockDirectionImpact, double lockDecayPerSecond,
|
||||
double visibility = 0, double minRange = 0, double spread = 0, string damageType = "Kinetic",
|
||||
double penetration = 0, double damageSpread = 0)
|
||||
double penetration = 0, double damageSpread = 0,
|
||||
ResolvedGuidedPresentation? guidance = null)
|
||||
{
|
||||
State = state; ItemKey = itemKey; Damage = damage; Range = range; Cooldown = cooldown;
|
||||
ProjectileSpeed = projectileSpeed; Heat = heat; Energy = energy; AmmoItemKey = ammoItemKey;
|
||||
|
|
@ -2112,6 +2153,7 @@ namespace GameCult.Aetheria.State.Verse
|
|||
LockDirectionImpact = lockDirectionImpact; LockDecayPerSecond = lockDecayPerSecond;
|
||||
Visibility = visibility; MinRange = minRange; Spread = spread;
|
||||
DamageType = damageType; Penetration = penetration; DamageSpread = damageSpread;
|
||||
Guidance = guidance;
|
||||
}
|
||||
|
||||
public AetheriaRuntimeWeaponStateCommit State { get; }
|
||||
|
|
@ -2139,6 +2181,36 @@ namespace GameCult.Aetheria.State.Verse
|
|||
public string DamageType { get; }
|
||||
public double Penetration { get; }
|
||||
public double DamageSpread { get; }
|
||||
public ResolvedGuidedPresentation? Guidance { get; }
|
||||
}
|
||||
|
||||
private sealed class ResolvedGuidedPresentation
|
||||
{
|
||||
public ResolvedGuidedPresentation(
|
||||
string mode,
|
||||
IReadOnlyList<AetheriaRuntimeCurveKey> guidanceCurve,
|
||||
IReadOnlyList<AetheriaRuntimeCurveKey> thrustCurve,
|
||||
IReadOnlyList<AetheriaRuntimeCurveKey> liftCurve,
|
||||
double thrust,
|
||||
double topSpeed,
|
||||
double dodgeFrequency)
|
||||
{
|
||||
Mode = mode ?? "none";
|
||||
GuidanceCurve = guidanceCurve ?? Array.Empty<AetheriaRuntimeCurveKey>();
|
||||
ThrustCurve = thrustCurve ?? Array.Empty<AetheriaRuntimeCurveKey>();
|
||||
LiftCurve = liftCurve ?? Array.Empty<AetheriaRuntimeCurveKey>();
|
||||
Thrust = thrust;
|
||||
TopSpeed = topSpeed;
|
||||
DodgeFrequency = dodgeFrequency;
|
||||
}
|
||||
|
||||
public string Mode { get; }
|
||||
public IReadOnlyList<AetheriaRuntimeCurveKey> GuidanceCurve { get; }
|
||||
public IReadOnlyList<AetheriaRuntimeCurveKey> ThrustCurve { get; }
|
||||
public IReadOnlyList<AetheriaRuntimeCurveKey> LiftCurve { get; }
|
||||
public double Thrust { get; }
|
||||
public double TopSpeed { get; }
|
||||
public double DodgeFrequency { get; }
|
||||
}
|
||||
|
||||
private sealed class ResolvedConstantWeapon
|
||||
|
|
|
|||
|
|
@ -536,6 +536,13 @@ namespace GameCult.Aetheria.State.Verse
|
|||
[Key(36)] public double ArmorAppliedDamage { get; set; }
|
||||
[Key(37)] public double EquipmentAppliedDamage { get; set; }
|
||||
[Key(38)] public IReadOnlyList<AetheriaRuntimeDamageCellCommit> DamageCells { get; set; } = Array.Empty<AetheriaRuntimeDamageCellCommit>();
|
||||
[Key(39)] public string GuidanceMode { get; set; } = "none";
|
||||
[Key(40)] public IReadOnlyList<AetheriaRuntimeCurveKey> GuidanceCurve { get; set; } = Array.Empty<AetheriaRuntimeCurveKey>();
|
||||
[Key(41)] public IReadOnlyList<AetheriaRuntimeCurveKey> GuidanceThrustCurve { get; set; } = Array.Empty<AetheriaRuntimeCurveKey>();
|
||||
[Key(42)] public IReadOnlyList<AetheriaRuntimeCurveKey> GuidanceLiftCurve { get; set; } = Array.Empty<AetheriaRuntimeCurveKey>();
|
||||
[Key(43)] public double GuidanceThrust { get; set; }
|
||||
[Key(44)] public double GuidanceTopSpeed { get; set; }
|
||||
[Key(45)] public double GuidanceDodgeFrequency { get; set; }
|
||||
}
|
||||
|
||||
[MessagePackObject]
|
||||
|
|
|
|||
|
|
@ -923,6 +923,30 @@ lifecycle state.
|
|||
weapon smokes also prove those side effects for instant, constant, charged,
|
||||
and deployable transactions, including the charged visibility multiplier.
|
||||
|
||||
### Guided Shot Presentation Contract
|
||||
|
||||
- Owner: the daemon's ordinary shot transaction owns hit and damage for
|
||||
`GuidedWeapon` and `Launcher`, exactly as it does for the fossil weapon event.
|
||||
- Inputs: authored target mode, guidance/thrust/lift curves, thrust, missile top
|
||||
speed, dodge frequency, range, origin, endpoint, and stable shot identity.
|
||||
- Outputs: a retained `shot.receipt` with `presentationKind=guided` and the
|
||||
complete runtime-neutral guidance profile; it survives checkpoint restart and
|
||||
reaches generic Eve shot facts.
|
||||
- Derived state: missile steering, split/fade particles, trail geometry, and hit
|
||||
visuals are client presentation. Provider prefab parameters may style those
|
||||
effects but cannot decide damage or contact.
|
||||
- Forbidden writers: Unity casts, visual missile transforms, and generic Eve
|
||||
lowerers cannot damage, retarget canonical state, or create an ordinary Ymir
|
||||
gameplay body.
|
||||
- Shared paths: `Launcher` retains target-entity guidance and `GuidedWeapon`
|
||||
retains look-direction guidance; both commit through the same lock, resource,
|
||||
wear, visibility, shot, and damage transactions as other instant weapons.
|
||||
- Cut line: the roadmap no longer asks for guided persistent payloads. The fossil
|
||||
projectile was a visual follower after damage authority had already fired.
|
||||
- Verification: daemon smoke proves no physical payload is created, every
|
||||
authored curve/scalar reaches the receipt and Eve surface, `Launcher` selects
|
||||
the guided presentation kind, and the profile survives serialization.
|
||||
|
||||
### Daemon Damage Transaction
|
||||
|
||||
- Owner: the daemon's canonical damage transaction owns damage-layer ordering
|
||||
|
|
|
|||
|
|
@ -21,11 +21,11 @@ validation proves contract shape, not wiring, exclusivity, lowering, or parity.
|
|||
| 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 |
|
||||
| Targeting/contacts | sensors + `ActionGameManager` | daemon | contact/target semantics | target HUD and indicators | simplified owner wired/projected; every authored instant lock behavior now evaluates on every daemon step independently of trigger/burst selection, so an idle weapon cannot retain a stale completed lock; acquisition and decay derive from facing, elapsed time, and observer-local contact confidence instead of granting an immediate perfect lock; target changes and invalid targets clear the daemon state; `weapon.lock.started`, `weapon.lock.acquired`, and `weapon.lock.lost` publish stable one-time chronology with target, weapon identity, reason, and prior/current progress through the generic feedback stream; the provider cockpit and generic combat reticle consume the same continuous selected-target lock without client smoothing or reconstruction | angular loss fixture, visibility/nearest/explicit targeting cases, provider audio/effect roles, and authored reticle parity |
|
||||
| Projectile flight | weapon/projectile classes | Eve presentation for ordinary tracers; Ymir for persistent physical payloads | receipt trajectory/effect facts or persistent payload state | native projectile visual | ordinary instant, charged, and constant execution creates no projectile body; each weapon state owns a stable shot sequence and receipts carry origin, endpoint, nominal duration, and effect identity for Eve presentation; `SpawnProjectile` and its alternate writer are deleted; Ymir projectile contacts are feedback-only for explicitly persisted payload rows and cannot write damage; Ymir.Core remains embedded for world integration, radial fields, circle contacts, pickups, and genuinely persistent/interceptable payloads | split/rename the remaining physical-payload interface and remove ordinary projectile terminology from runtime contracts |
|
||||
| Projectile flight | weapon/projectile classes | Eve presentation for ordinary tracers; Ymir for persistent physical payloads | receipt trajectory/effect facts or persistent payload state | native projectile visual | ordinary instant, charged, constant, GuidedWeapon, and Launcher execution creates no projectile body; each weapon state owns a stable shot sequence and receipts carry origin, endpoint, nominal duration, and effect identity for Eve presentation. Guided receipts additionally retain target-entity/look-direction mode, complete guidance/thrust/lift curves, evaluated thrust and top speed, and dodge frequency; these survive checkpoint restart and reach generic Eve shot facts. The fossil guided projectile only steered, split, faded, and selected hit visuals after the ordinary weapon event had committed damage, so the client path remains derived presentation rather than a Ymir gameplay body. `SpawnProjectile` and its alternate writer are deleted; Ymir projectile contacts are feedback-only for explicitly persisted payload rows and cannot write damage; Ymir.Core remains embedded for world integration, radial fields, circle contacts, pickups, and genuinely persistent/interceptable payloads | generic guided trajectory lowering, provider split/fade asset parameters, and removal of ordinary projectile terminology from runtime contracts |
|
||||
| Shield/armor/equipment/hull damage | energy-funded shield, schematic armor cells, equipment and scalar hull | daemon canonical damage transaction | aggregate/exact armor grid, entity status, and layer-aware shot/damage receipts | schematic, meters, impacts, damage VFX | owner-exists; wired; exclusive; projected: daemon initializes armor/maximum-armor grids from hull plus hardpoint catalog topology; direct and deployable damage share one `shield -> armor cell -> equipment -> scalar hull` transaction; direct impacts select a cell or complete hardpoint footprint before orthogonal spread and 0.5-cell penetration, while splash payloads select the source-facing hull half; the fossil penetration infinite-loop defect is fixed; damage type is typed/pass-through without resistances because the fossil never applied them; Eve exposes aggregate and exact armor state and receipts identify reached/applied layers; fake Ymir contacts and presentation cannot decide damage | fossil parity fixtures for topology, edge impacts, orthogonal spread, multi-cell penetration, equipment interception, and generic shield/equipment effect lifecycle |
|
||||
| Thermal/energy | cell conduction, capacitor/reactor/radiator network, and cockpit thermal exposure | daemon | ship/equipment power, temperature, medical state, risk and death feedback | schematic/post effects | owner-exists; wired; exclusive; projected; generic timing and native asset delivery lowered: catalog hull and installed-item shapes derive cell mass/conductivity/topology; local temperature derives thermal performance, wear potential and online state; all consumers share capacitor-first/reactor-residual transactions; powered radiators pump before reactor settlement; reactor heat then enters the same cell network before conduction/radiation; cockpit-cell temperature drives the fossil nonlinear heatstroke/hypothermia accumulation, linear recovery, severe-risk crossings, and ordinary exactly-once destruction with a typed cause; Eve exposes cockpit, risk, heatstroke source-weight, phasing and death-transition facts; EveUnity generically resolves the original severe pulse plus live/reconnect-safe cause-specific death crossfade; Aetheria deterministically translates the five legacy profiles into URP profiles, persists every override as a real profile sub-asset, advertises them by semantic role through CultMesh CDN, and binds them in the canonical client through a weight-only native sink; rotated equipment topology, parent/child radiation, schematic composition, legacy SSR/AO/roundness equivalents, and live/reconnect death-fade parity remain incomplete | multi-consumer ordering, multi-radiator visibility, rotated topology, generic schematic meters, renderer-feature parity, and live/reconnect death-fade screenshots |
|
||||
| Weapons | ServerShared behaviors + Unity effects | daemon plus Ymir geometry/kinematics | action capability, weapon state, shot/damage receipts | effects/audio/HUD | instant and constant resource/timing lifecycles are daemon-owned; charged weapons accept one semantic request, spool without requiring a solution, hold at full charge, commit automatically when a solution appears, and perform deterministic once-per-second malfunction hazard checks after the authored safe hold interval with increasing persisted risk; successful instant, constant, charged, deployable, and turret-controlled transactions publish authored transient visibility and consume the owning equipment's temperature-derived wear, with charged visibility applying its authored multiplier and turret wear remaining exact-weapon scoped; generic Eve `weapon.state` exposes charge hold/risk and feedback exposes readiness, commit, refusal, and malfunction chronology; committed direct damage now enters the shared daemon armor-cell transaction | guided persistent payloads, remaining impact-distribution parity, and Unity effect reconciliation |
|
||||
| Destruction/drops | hull death, 25% equipment rolls, unconditional cargo drops, 25-unit launch, 30-second pickups | daemon | destruction identity, death/drop events, typed pickups | provider-advertised destruction effect and pickups | owner-exists; wired; exclusive; projected: direct and deployable lethal damage commit one deterministic destruction transaction, clear live references, retain only a non-rendered identity tombstone, and emit exact pickups/events; the reserved client `DestroyEntity` command is rejected; Aetheria advertises its original big explosion as `effect.feedback.entity.destroyed`, lowered generically by EveUnity | equipment-destruction-before-hull parity and multiplayer reconnect chronology |
|
||||
| Weapons | ServerShared behaviors + Unity effects | daemon plus Ymir geometry/kinematics | action capability, weapon state, shot/damage receipts | effects/audio/HUD | instant and constant resource/timing lifecycles are daemon-owned; charged weapons accept one semantic request, spool without requiring a solution, hold at full charge, commit automatically when a solution appears, and perform deterministic once-per-second malfunction hazard checks after the authored safe hold interval with increasing persisted risk; successful instant, constant, charged, deployable, and turret-controlled transactions publish authored transient visibility and consume the owning equipment's temperature-derived wear, with charged visibility applying its authored multiplier and turret wear remaining exact-weapon scoped; GuidedWeapon and Launcher retain ordinary authoritative shot resolution while publishing their complete fossil steering profile as derived presentation; generic Eve `weapon.state` exposes charge hold/risk and feedback exposes readiness, commit, refusal, and malfunction chronology; committed direct damage now enters the shared daemon armor-cell transaction | remaining impact-distribution parity, generic guided/effect lowering, and Unity effect reconciliation |
|
||||
| Destruction/drops | hull death, 25% equipment rolls, unconditional cargo drops, 25-unit launch, 30-second pickups | daemon | destruction identity, death/drop events, typed pickups | provider-advertised destruction effect and pickups | owner-exists; wired; exclusive; projected: direct and deployable lethal damage commit one deterministic destruction transaction, clear live references, retain only a non-rendered identity tombstone, and emit exact pickups/events; residual cell damage destroys installed equipment before hull, disables its behaviors immediately, and cockpit destruction kills with its fossil cause before scalar hull depletion; the reserved client `DestroyEntity` command is rejected; Aetheria advertises its original big explosion as `effect.feedback.entity.destroyed`, lowered generically by EveUnity | multiplayer reconnect chronology |
|
||||
| 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. 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 chronology |
|
||||
| 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 |
|
||||
|
|
|
|||
Loading…
Reference in a new issue