2020-12-22 03:13:43 +00:00
|
|
|
/* 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/. */
|
|
|
|
|
|
Move the ServerShared data model onto CultLib's CultCache
Cut 8b. Aetheria.Shared builds headless against GameCult.Caching and
GameCult.Caching.MessagePack at the pinned CultLib; the pin guard also
checks the Caching project exists.
Documents: [CultDocument("aetheria.<type>", "1")] with [MessagePackObject]
and integer [Key]s on the Q7 concrete types (the eight ItemData leaves,
Faction, FactionProductData, PersonalityAttribute, NameFile, OrbitData, the
four BodyData leaves, SavedZone, SavedGame, PlayerSettings, InputLayout).
SavedGame and PlayerSettings are [CultGlobal]; Name members are [CultName].
ItemData, CraftedItemData, EquippableItemData, BodyData and AgentTask lose
[MessagePackObject]; BodyData and AgentTask lose their [Union]s. InputLayout
moves into ServerShared so AetheriaStores can route it.
References: Guid and DatabaseLink<T> members become CultRecordRef<T>;
Guid-keyed maps become ref-keyed with [CultReference(..., many: true)];
ZonePack holds refs to orbit and body records. Runtime maps (Zone orbits,
bodies and belts, cargo ItemsOfType, agent targets) key by CultRecordKey.
Identity is the record key: ID comparisons become reference or key
comparisons. StatModifierData.RequireBehavior is a simple type name.
AetheriaStores.Open composes the catalog (read-only unless writable), run
and player stores, one home per type. CultRecordRefs carries IsSet, a
null-safe Get(ref), RefOf (key from TryGetHandle) and Upsert.
ItemManager.GetData is the one item resolution path. MathResolver is
declared by [assembly: CultCacheFormatterResolver].
Inspector attributes: text, range and asset path map onto CultLib's
CultInspector attributes (asset path only on string members); every
replaced Aetheria attribute keeps [Inspectable] for the runtime panel.
Builds: Aetheria.Shared. Does not build: AetherDb and the Unity project,
which follow.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 19:00:42 +02:00
|
|
|
using GameCult.Caching;
|
2026-08-10 14:57:50 +01:00
|
|
|
using MessagePack;
|
|
|
|
|
using Newtonsoft.Json;
|
2026-09-14 17:39:12 +02:00
|
|
|
using CultMath;
|
|
|
|
|
using static CultMath.math;
|
2026-08-10 14:57:50 +01:00
|
|
|
|
|
|
|
|
[Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn), EntityTypeRestriction(HullType.Ship), RuntimeInspectable]
|
|
|
|
|
public class ThrusterData : BehaviorData
|
|
|
|
|
{
|
|
|
|
|
[Inspectable, JsonProperty("thrust"), Key(1), RuntimeInspectable]
|
|
|
|
|
public PerformanceStat Thrust = new PerformanceStat();
|
|
|
|
|
|
|
|
|
|
[Inspectable, JsonProperty("visibility"), Key(2), RuntimeInspectable]
|
|
|
|
|
public PerformanceStat Visibility = new PerformanceStat();
|
|
|
|
|
|
|
|
|
|
[Inspectable, JsonProperty("heat"), Key(3), RuntimeInspectable]
|
|
|
|
|
public PerformanceStat Heat = new PerformanceStat();
|
|
|
|
|
|
|
|
|
|
[Inspectable, JsonProperty("energy"), Key(4), RuntimeInspectable]
|
|
|
|
|
public PerformanceStat EnergyUsage = new PerformanceStat();
|
|
|
|
|
|
Switch engine asset references from Resources paths to Addressables GUIDs
Cut 2 of docs/addressables-cut.md. The asset move (git mv
Resources/{Prefabs,Schematics,Sprites/Icons} -> Content/...) and the
UnityHelpers.cs deletion already landed in 1fdf8151, picked up by a
concurrent session sharing this working tree; this commit is the rest
of Cut 2 landing on top of that shared state, atomically:
- Bump CultLib to cultlib-unity-v1.0.60 / caching-unity-v1.4.0 (both
45c2f40) in Packages/manifest.json, packages-lock.json and
Directory.Build.props, together with the 8 ServerShared
[CultInspectorAssetPath] usages becoming [CultInspectorAssetGuid] -
the .NET and Unity sides move together or nothing compiles.
- Assets/Scripts/EngineAssets.cs replaces UnityHelpers: loads an
Addressables key (bare GUID or guid[subAsset]) synchronously,
loading the GameObject and taking the component for the four sites
that load a component. ActionBarSlot, EntityInstance, ShipInstance
and ZoneRenderer call it instead of Resources.Load/UnityHelpers; the
inline "Assets/Resources/" prefix strips are gone with them.
CultCacheDrawers' schematic preview goes through
AssetDatabase.GUIDToAssetPath first.
- Assets/Scripts/Editor/EngineAssetCheck.cs validates every stored
[CultInspectorAssetGuid] value: 32 lowercase hex, resolves to a file,
is addressable, and satisfies its member's consumer-type contract
(Texture2D for icons/logos/schematics, a GameObject with the right
component for prefabs) - table-driven, so an attributed member with
no row fails loudly instead of being skipped.
- GameData/Aetheria.cc: the scratch migration (Aetheria.Shared + the
pinned CultLib revision, a throwaway console app, never landed)
walked all 138 attributed field slots across 180 documents,
rewriting the 30 non-empty Resources paths to their .meta GUIDs -
108 stayed empty, 0 unresolved, matching the map's census exactly.
Verification: dotnet test tests/Aetheria.Shared.Tests 61/61 green
against CultLib 45c2f40; Unity 6000.3.24f1 batchmode compiles clean;
EngineAssetCheck.Run passes all 30 stored references; the map's
negative greps are clean; tests/mutation_tests.py's 5 mutations (wrong
type, non-addressable, random guid, an old path, and an untabled
attributed member) are all caught and named, the control case and the
post-revert recompile stay clean, and the catalog/source are restored
byte-identical afterward.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 05:04:50 +02:00
|
|
|
[Inspectable, CultInspectorAssetGuid, JsonProperty("Particles"), Key(5)]
|
2026-08-10 14:57:50 +01:00
|
|
|
public string ParticlesPrefab;
|
|
|
|
|
|
|
|
|
|
public override Behavior CreateInstance(EquippedItem item)
|
|
|
|
|
{
|
|
|
|
|
return new Thruster(this, item);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override Behavior CreateInstance(ConsumableItemEffect item)
|
|
|
|
|
{
|
|
|
|
|
return new Thruster(this, item);
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-04-09 07:48:45 +01:00
|
|
|
|
Cut 3: PowerBus replaces TryConsumeEnergy/CanConsumeEnergy
Deletes Entity.CanConsumeEnergy/TryConsumeEnergy and Reactor's own capacitor
draining/filling (the demand-mechanism halves of Reactor.cs:90-141); adds
PowerBus (one per Entity, stepped once per tick before equipment executes),
IPowerConsumer on the five continuous draw sites (EnergyDraw, Thruster,
AetherDrive, Radiator, ConstantWeapon), and EquippedItem.PowerSupply as the
bus's grant ratio. Reactor keeps only its heat/throttle arithmetic, now driven
by PowerBus.NetDraw instead of an accumulator any behaviour could add into.
The four instant draws (InstantWeapon's burst/shot, Sensor's ping, Shield's hit
taken) are out of scope for the bus this cut per the map; they spend capacitor
charge directly and atomically via the new Entity.TrySpendCapacitorCharge/
CanSpendCapacitorCharge, replacing TryConsumeEnergy's old partial-drain-then-
refuse bug for that path too. Named, temporary exception closed by Cut 4.
docs/stats-and-power-cut.md Cut 3.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 21:39:38 +02:00
|
|
|
public class Thruster : Behavior, IAnalogBehavior, IPowerConsumer
|
2020-04-09 07:48:45 +01:00
|
|
|
{
|
2026-10-07 05:39:39 +02:00
|
|
|
// Live: heat, quality, durability and power move this stat after construction, so it is read, never cached.
|
|
|
|
|
public float Thrust => Evaluate(_data.Thrust);
|
2020-12-22 03:13:43 +00:00
|
|
|
public float Torque { get; }
|
2020-04-09 07:48:45 +01:00
|
|
|
|
2020-06-05 04:16:33 +01:00
|
|
|
public float Axis
|
|
|
|
|
{
|
|
|
|
|
get => _input;
|
|
|
|
|
set => _input = saturate(value);
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
private ThrusterData _data;
|
2026-09-19 04:41:13 +02:00
|
|
|
|
2020-05-07 03:53:54 +01:00
|
|
|
private float _input;
|
2020-04-09 07:48:45 +01:00
|
|
|
|
2026-09-19 04:41:13 +02:00
|
|
|
// Cut 8 (operator ask 2026-09-19): presentation's read of "how healthy does this thruster actually look" --
|
|
|
|
|
// Thrust is the stat that governs this behaviour's real output, so it is the equivalent quantity to condition
|
|
|
|
|
// against. 1f for the ConsumableItemEffect constructor's item-less case; that path has no durability, heat or
|
|
|
|
|
// power-supply state to be broken by, so it always reads perfect.
|
|
|
|
|
public float Condition => Item?.ConditionRatio(_data.Thrust) ?? 1f;
|
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
public Thruster(ThrusterData data, EquippedItem item) : base(data, item)
|
2020-04-09 07:48:45 +01:00
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
_data = data;
|
|
|
|
|
var hullData = ItemManager.GetData(Entity.Hull) as HullData;
|
|
|
|
|
var hullCenter = hullData.Shape.CenterOfMass;
|
|
|
|
|
var itemData = ItemManager.GetData(item.EquippableItem);
|
|
|
|
|
var itemCenter = hullData.Shape.Inset(itemData.Shape, item.Position, item.EquippableItem.Rotation).CenterOfMass;
|
2020-12-22 03:13:43 +00:00
|
|
|
var toCenter = hullCenter - itemCenter;
|
2026-08-10 14:57:50 +01:00
|
|
|
Torque = -dot(normalize(toCenter), float2(1, 0).Rotate(item.EquippableItem.Rotation));
|
2021-04-18 05:13:20 +01:00
|
|
|
}
|
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
public Thruster(ThrusterData data, ConsumableItemEffect item) : base(data, item)
|
2021-04-18 05:13:20 +01:00
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
_data = data;
|
2021-04-18 05:13:20 +01:00
|
|
|
Torque = 0;
|
2020-04-09 07:48:45 +01:00
|
|
|
}
|
|
|
|
|
|
Cut 3: PowerBus replaces TryConsumeEnergy/CanConsumeEnergy
Deletes Entity.CanConsumeEnergy/TryConsumeEnergy and Reactor's own capacitor
draining/filling (the demand-mechanism halves of Reactor.cs:90-141); adds
PowerBus (one per Entity, stepped once per tick before equipment executes),
IPowerConsumer on the five continuous draw sites (EnergyDraw, Thruster,
AetherDrive, Radiator, ConstantWeapon), and EquippedItem.PowerSupply as the
bus's grant ratio. Reactor keeps only its heat/throttle arithmetic, now driven
by PowerBus.NetDraw instead of an accumulator any behaviour could add into.
The four instant draws (InstantWeapon's burst/shot, Sensor's ping, Shield's hit
taken) are out of scope for the bus this cut per the map; they spend capacitor
charge directly and atomically via the new Entity.TrySpendCapacitorCharge/
CanSpendCapacitorCharge, replacing TryConsumeEnergy's old partial-drain-then-
refuse bug for that path too. Named, temporary exception closed by Cut 4.
docs/stats-and-power-cut.md Cut 3.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 21:39:38 +02:00
|
|
|
// Cut 3 (docs/stats-and-power-cut.md): the resolved stat times the behaviour-supplied throttle scalar (§1.2),
|
|
|
|
|
// exactly the shape the map names. _input is set externally (the ship's controls) before Entity.Update calls
|
|
|
|
|
// PowerBus.Step, so it is already current when this runs.
|
2026-09-19 06:58:57 +02:00
|
|
|
//
|
|
|
|
|
// Nominal-request ruling (docs/stats-and-power-cut.md, operator ruling 2026-09-19): EnergyUsage is a
|
|
|
|
|
// registered request field (StatValidation.PowerRequestFields) -- read nominally, same reasoning as every
|
|
|
|
|
// other IPowerConsumer in this cut, though EnergyUsage carries no PowerSupply term in Thruster's own shipped
|
|
|
|
|
// catalog today; Thrust (the field Cut 7 curves) is a separate stat Execute reads with the real Evaluate.
|
|
|
|
|
public float PowerRequest(float dt) => _input > .01f ? _input * EvaluateNominalPower(_data.EnergyUsage) : 0f;
|
Cut 3: PowerBus replaces TryConsumeEnergy/CanConsumeEnergy
Deletes Entity.CanConsumeEnergy/TryConsumeEnergy and Reactor's own capacitor
draining/filling (the demand-mechanism halves of Reactor.cs:90-141); adds
PowerBus (one per Entity, stepped once per tick before equipment executes),
IPowerConsumer on the five continuous draw sites (EnergyDraw, Thruster,
AetherDrive, Radiator, ConstantWeapon), and EquippedItem.PowerSupply as the
bus's grant ratio. Reactor keeps only its heat/throttle arithmetic, now driven
by PowerBus.NetDraw instead of an accumulator any behaviour could add into.
The four instant draws (InstantWeapon's burst/shot, Sensor's ping, Shield's hit
taken) are out of scope for the bus this cut per the map; they spend capacitor
charge directly and atomically via the new Entity.TrySpendCapacitorCharge/
CanSpendCapacitorCharge, replacing TryConsumeEnergy's old partial-drain-then-
refuse bug for that path too. Named, temporary exception closed by Cut 4.
docs/stats-and-power-cut.md Cut 3.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 21:39:38 +02:00
|
|
|
|
Cut 5: priority tiers replace PowerBus's single shared GrantRatio
PowerTiers.cs names five tiers (Critical/High/Medium/Low/Utility) and their
per-behaviour-kind defaults: Radiator is Critical (the closest thing this game
has to life support -- a starved radiator cascades into wear and shutdown
across the whole hull), Shield is High, Thruster/AetherDrive are Medium,
ConstantWeapon/InstantWeapon are Low, and Sensor/EnergyDraw are Utility.
IPowerConsumer.DefaultPowerTier lets each behaviour own its own default.
EquippableItem.PowerTier (Key 12) is the stored, player-choosable tier on the
unit rather than the design; EquippedItem's constructor seeds a kind default
the first time a unit is equipped, only when the field is still
PowerTiers.Unassigned, and never again -- a tier is a stored choice, not
something the bus re-derives.
PowerBus.AllocateTiers walks tiers lowest-number-first: a tier is granted in
full before the next tier sees anything left, an empty tier passes its whole
share down, and within a tier every consumer rations by the same
grant/demand fraction so unequal requests still divide proportionally. A new
TierGrantRatio[] exposes each tier's own ratio for a future UI (Cut 8) to
read starvation from directly, instead of reconstructing it from every item's
PowerSupply.
Deletes EquippedItem.SortPosition, IOrderedBehavior and Reactor.Order (its
only implementer): the bus already steps before any equipment's Execute, so
equip/behaviour order never decided who got fed even before this cut, and
nothing else needed the interface (subject to the map's O3 operator check).
docs/stats-and-power-cut.md Cut 5 / §1.3.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 02:58:38 +02:00
|
|
|
// Cut 5 (docs/stats-and-power-cut.md §1.3, PowerTiers.cs): Medium -- mobility. Losing thrust for a tick
|
|
|
|
|
// under brownout is an inconvenience, not the cascading failure a starved radiator or shield causes.
|
|
|
|
|
public int DefaultPowerTier => PowerTiers.Medium;
|
|
|
|
|
|
Evasion term: reachable spread from the thrust and turn envelope, realised by changes of vector
Every hit price gains one factor, PDeviation(Evasion, Tracking): how far the target can
and does move off the line of fire within SolutionWindow (0.75 s). Capability is Reach, an
eight-face closed form over the ship's ManoeuvreEnvelope (summed from what each live
thruster and aether drive reports with its own Execute arithmetic, read live: a thruster's
Thrust property is stale until it fires, which is why the Djinni moved 1.6 times its stats,
M25). Behaviour is ManoeuvreTrack, observed once per Entity.Update from the measured
acceleration; a steady burn, coasting and a dither much faster than the window do not
evade, and sideways speed alone does not (ruling evasion-speed). The commit price adds the
term at any flight time, so an instant weapon feels a jinking target. Forecast owns the
term for HitProbability and Inspect, which gain Evasion and PEvasion.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 00:48:27 +02:00
|
|
|
// What this thruster adds to the ship's manoeuvre envelope: Execute's own arithmetic at full input, read live.
|
2026-10-07 05:39:39 +02:00
|
|
|
// Its push is Thrust / Mass along its mount, into the matching body
|
Evasion term: reachable spread from the thrust and turn envelope, realised by changes of vector
Every hit price gains one factor, PDeviation(Evasion, Tracking): how far the target can
and does move off the line of fire within SolutionWindow (0.75 s). Capability is Reach, an
eight-face closed form over the ship's ManoeuvreEnvelope (summed from what each live
thruster and aether drive reports with its own Execute arithmetic, read live: a thruster's
Thrust property is stale until it fires, which is why the Djinni moved 1.6 times its stats,
M25). Behaviour is ManoeuvreTrack, observed once per Entity.Update from the measured
acceleration; a steady burn, coasting and a dither much faster than the window do not
evade, and sideways speed alone does not (ruling evasion-speed). The commit price adds the
term at any flight time, so an instant weapon feels a jinking target. Forecast owns the
term for HitProbability and Inspect, which gain Evasion and PEvasion.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 00:48:27 +02:00
|
|
|
// axis, even for a flank thruster that only turns (it pushes whenever it turns); its turn is |Torque| * Thrust *
|
|
|
|
|
// TorqueMultiplier / Mass into the side its Torque sign fires on, when |Torque| clears the floor. Zero when the
|
|
|
|
|
// item is offline or unpowered, the same gates Execute applies.
|
|
|
|
|
public ManoeuvreEnvelope Manoeuvre()
|
|
|
|
|
{
|
|
|
|
|
if (Item == null || !Item.Active.Value || Item.PowerSupply <= 1e-4f) return default;
|
|
|
|
|
var settings = ItemManager.GameplaySettings;
|
2026-10-07 05:39:39 +02:00
|
|
|
var thrust = Thrust;
|
Evasion term: reachable spread from the thrust and turn envelope, realised by changes of vector
Every hit price gains one factor, PDeviation(Evasion, Tracking): how far the target can
and does move off the line of fire within SolutionWindow (0.75 s). Capability is Reach, an
eight-face closed form over the ship's ManoeuvreEnvelope (summed from what each live
thruster and aether drive reports with its own Execute arithmetic, read live: a thruster's
Thrust property is stale until it fires, which is why the Djinni moved 1.6 times its stats,
M25). Behaviour is ManoeuvreTrack, observed once per Entity.Update from the measured
acceleration; a steady burn, coasting and a dither much faster than the window do not
evade, and sideways speed alone does not (ruling evasion-speed). The commit price adds the
term at any flight time, so an instant weapon feels a jinking target. Forecast owns the
term for HitProbability and Inspect, which gain Evasion and PEvasion.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 00:48:27 +02:00
|
|
|
var acceleration = thrust / Entity.Mass;
|
|
|
|
|
var forward = normalize(Entity.Direction);
|
|
|
|
|
var right = forward.Rotate(ItemRotation.Clockwise);
|
|
|
|
|
var push = -forward.Rotate(Item.EquippableItem.Rotation);
|
|
|
|
|
var along = dot(push, forward);
|
|
|
|
|
var across = dot(push, right);
|
|
|
|
|
var turn = abs(Torque) > settings.TorqueFloor ? abs(Torque) * thrust * settings.TorqueMultiplier / Entity.Mass : 0f;
|
|
|
|
|
return new ManoeuvreEnvelope(
|
|
|
|
|
max(along, 0f) * acceleration, max(-along, 0f) * acceleration,
|
|
|
|
|
max(-across, 0f) * acceleration, max(across, 0f) * acceleration,
|
|
|
|
|
Torque > 0f ? turn : 0f, Torque < 0f ? turn : 0f);
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-18 05:13:20 +01:00
|
|
|
public override bool Execute(float dt)
|
2020-04-09 07:48:45 +01:00
|
|
|
{
|
2021-04-30 06:39:57 +01:00
|
|
|
Item.SetAudioParameter(SpecialAudioParameter.Intensity, _input);
|
2026-09-19 04:10:19 +02:00
|
|
|
// Cut 7 (docs/stats-and-power-target.md): "continuous consumers brown out ... through a power supply
|
|
|
|
|
// curve on their performance stats." Thrust below is a plain Evaluate() read, so once the catalog's own
|
|
|
|
|
// Thrust stat carries a PowerSupply term, a partial grant already comes back reduced -- this gate no
|
|
|
|
|
// longer demands a full grant, only that the thruster is being asked to do anything (_input) and that it
|
|
|
|
|
// has not been cut to true zero supply (the epsilon PowerBus itself already treats as "nothing granted").
|
|
|
|
|
if(_input > .01f && Item.PowerSupply > 1e-4f)
|
2021-03-22 05:22:31 +00:00
|
|
|
{
|
2026-10-07 05:39:39 +02:00
|
|
|
var thrust = Thrust;
|
|
|
|
|
Entity.Velocity -= Direction.xz * _input * thrust / Entity.Mass * dt;
|
2026-10-08 18:26:02 +02:00
|
|
|
var turnRate = _input * Torque * thrust * ItemManager.GameplaySettings.TorqueMultiplier / Entity.Mass;
|
|
|
|
|
Entity.Direction = mul(Entity.Direction, CultMath.float2x2.Rotate(turnRate * dt));
|
|
|
|
|
Entity.TurnRate += turnRate;
|
2026-08-10 14:57:50 +01:00
|
|
|
AddHeat(_input * Evaluate(_data.Heat) * dt);
|
|
|
|
|
var vis = _input * Evaluate(_data.Visibility);
|
|
|
|
|
if (!Entity.VisibilitySources.ContainsKey(this) || vis > Entity.VisibilitySources[this])
|
2021-04-18 05:13:20 +01:00
|
|
|
Entity.VisibilitySources[this] = vis;
|
2021-03-22 05:22:31 +00:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
2020-05-11 01:17:10 +01:00
|
|
|
}
|
2026-08-10 14:57:50 +01:00
|
|
|
}
|