From fdea06f27fa513b1bed24cc3cce8edc268cbde6c Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 17 Sep 2026 14:36:14 +0200 Subject: [PATCH] Add an engine-free capability event contract Fork L (docs/headless-playground-cut.md): pickup, impact absorption, melee blade and propulsion are separate item capabilities that a future simulation will own; presentations subscribe and decide nothing. This stands up the wire shape only -- AbsorbEvent/GrabEvent/MeleeEvent/ThrustEvent plus a per-entity CapabilityEvents source (UniRx Subject per kind, matching the idiom already used on Entity) -- with no publisher wired up yet. Compiles into the headless Aetheria.Shared build with no Unity dependency. AbsorbEvent carries the hit's DamageType (operator, 2026-09-17) so a future presenter can vary visuals per type; the damage-type mask itself belongs to the absorb capability's own data and is out of scope here. Co-Authored-By: Claude Opus 5 --- .../Scripts/ServerShared/CapabilityEvents.cs | 124 ++++++++++++++++++ .../ServerShared/CapabilityEvents.cs.meta | 11 ++ 2 files changed, 135 insertions(+) create mode 100644 Assets/Scripts/ServerShared/CapabilityEvents.cs create mode 100644 Assets/Scripts/ServerShared/CapabilityEvents.cs.meta diff --git a/Assets/Scripts/ServerShared/CapabilityEvents.cs b/Assets/Scripts/ServerShared/CapabilityEvents.cs new file mode 100644 index 000000000..9e3c26635 --- /dev/null +++ b/Assets/Scripts/ServerShared/CapabilityEvents.cs @@ -0,0 +1,124 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +using System; +using UniRx; +using CultMath; + +// Capability event contract (docs/headless-playground-cut.md fork L, operator ruling 2026-09-17): +// pickup, impact absorption, melee blade and propulsion are separate item capabilities. Simulation +// (not yet built -- this file only stands up the wire shape) will own each capability's rules and +// emit these events; any number of presentations (the field shield today, later an IFS shatter +// plane or an advected particle field) subscribe to the event kinds they visualize and decide +// nothing about the rule. This file has no engine dependency: it compiles into the headless +// Aetheria.Shared build (see Aetheria.Shared.csproj) with no publisher wired up yet. + +// One absorbed impact: a hit landing on the item's shield/armor capability. DamageType rides along +// so presenters can vary visuals per type; the damage-type mask itself belongs to the absorb +// capability's own data and is out of scope for this cut (operator, 2026-09-17) -- this struct only +// carries the hit's existing type through, unfiltered. +public readonly struct AbsorbEvent +{ + public readonly float3 Position; + public readonly float3 Direction; + public readonly float Magnitude; + public readonly DamageType DamageType; + + public AbsorbEvent(float3 position, float3 direction, float magnitude, DamageType damageType) + { + Position = position; + Direction = direction; + Magnitude = magnitude; + DamageType = damageType; + } +} + +// A timed grab's lifecycle (docs fork L: "a pickup is a timed grab of a selected item in range: +// extend, envelop, pull"), generalized to any item capability that reaches out and returns with +// something. +public enum GrabPhase +{ + Started, + Extend, + Envelop, + Pull, + Completed, + Cancelled +} + +// The target is identified by a plain handle, not a Unity type: the simulation will own the real +// target identity (a loot drop or item instance id) once the pickup capability exists. Today's only +// publisher (the FieldShieldTest scene) hands out its own handles; nothing here interprets them. +public readonly struct GrabEvent +{ + public readonly int TargetHandle; + public readonly GrabPhase Phase; + public readonly float3 TargetPosition; + public readonly float PhaseDuration; + + public GrabEvent(int targetHandle, GrabPhase phase, float3 targetPosition, float phaseDuration) + { + TargetHandle = targetHandle; + Phase = phase; + TargetPosition = targetPosition; + PhaseDuration = phaseDuration; + } +} + +// One melee swing. +public readonly struct MeleeEvent +{ + public readonly float3 Direction; + public readonly float Range; + public readonly float Arc; + public readonly float Duration; + + public MeleeEvent(float3 direction, float range, float arc, float duration) + { + Direction = direction; + Range = range; + Arc = arc; + Duration = duration; + } +} + +// One frame of propulsion input. Twist is packed as a float2 (x: front, y: rear) rather than a +// single scalar, mirroring the existing FieldDriver split between front and rear turn effect; +// Throttle is carried separately from the planar thrust vector for presentations that want overall +// engine intensity independent of direction (the field shield presentation does not use it today). +public readonly struct ThrustEvent +{ + public readonly float2 PlanarThrust; + public readonly float Throttle; + public readonly float2 Twist; + + public ThrustEvent(float2 planarThrust, float throttle, float2 twist) + { + PlanarThrust = planarThrust; + Throttle = throttle; + Twist = twist; + } +} + +// Per-entity (or per-item) source of capability events. One instance is owned by whatever will +// eventually decide these rules; today the FieldShieldTest scene stands in for that owner. Follows +// the Subject-per-event-kind idiom already used on Entity (IncomingHit, HullDamage, etc): each +// event kind gets its own stream, and a subscriber to one kind never sees another. +public class CapabilityEvents +{ + private readonly Subject _absorb = new Subject(); + private readonly Subject _grab = new Subject(); + private readonly Subject _melee = new Subject(); + private readonly Subject _thrust = new Subject(); + + public UniRx.IObservable Absorb => _absorb; + public UniRx.IObservable Grab => _grab; + public UniRx.IObservable Melee => _melee; + public UniRx.IObservable Thrust => _thrust; + + public void PublishAbsorb(AbsorbEvent e) => _absorb.OnNext(e); + public void PublishGrab(GrabEvent e) => _grab.OnNext(e); + public void PublishMelee(MeleeEvent e) => _melee.OnNext(e); + public void PublishThrust(ThrustEvent e) => _thrust.OnNext(e); +} diff --git a/Assets/Scripts/ServerShared/CapabilityEvents.cs.meta b/Assets/Scripts/ServerShared/CapabilityEvents.cs.meta new file mode 100644 index 000000000..53ef711bc --- /dev/null +++ b/Assets/Scripts/ServerShared/CapabilityEvents.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f0e6d83e34144ce89bfbc7ef778f10a6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: