SimClock (engine-free) turns real frame seconds into whole fixed steps; ActionGameManager.Update feeds it outside the pause gate and Zone.Update(Time.deltaTime) is gone, with IntroCutscene (dead, Unity-driven pose writes). Entity.TurnRate is the sum of the rotation actuators' applied turn per sim second, zeroed each Update; DrawAhead reads it so EntityInstance and ShipInstance draw the latest sim state ahead by Lead. Unverified at this commit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
A power request may not depend on power supply (Cut 6/F6), and continuous
consumers brown out through a power term on their own performance stats
(Cut 7). For Radiator (PumpedHeat) and AetherDrive (Torque) those were the
same stat, making six shipped catalog records illegal and leaving six
BrownoutTests cases skipped.
Resolved per the operator's ruling: a power request is evaluated
nominally -- what the item wants at full supply, not what it is currently
managing. EquippedItem.EvaluateNominalPower (mirroring ConditionRatio's
own NominalContext) pins only PowerSupplyFactor to 1; Heat/Durability
stay live, since they are not the term a request would be circular
through. Every PowerRequest/RefreshReserve/RefreshInputCapacitor
implementation now reads a StatValidation.PowerRequestFields stat through
it, so the same stat can carry a brownout curve and be a request field
without the request depending on its own answer.
StatValidation.ValidateNoPowerSupplyOnRequest (F6's static half of the
rule, forbidding the term outright) is deleted -- nominal evaluation
removes the circularity at the root instead. Only the dynamic
modifier-chain half (StatModifier.ValidateNoPowerSupplyChain) still
refuses anything, since EvaluateNominalPower forwards ScaleModifier/
ConstantModifier unchanged (real, not nominal).
Un-skips BrownoutTests' six Radiator/AetherDrive cases and adds two new
regression tests (a direct EvaluateNominalPower probe, and a two-tick
Radiator demand check) backed by a new mutation harness. Updates the four
PowerCurveTests/AetheriaStoresTests cases that pinned the now-superseded
static refusal to instead assert acceptance. GameData/Aetheria.cc is
unchanged and still validates.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Operator ask (2026-09-19): thruster particles only reflected thrust intent,
so a cooked or starved thruster looked identical to a healthy one.
EquippedItem.ConditionRatio (Entity.cs) resolves a stat's actual value
against what the same item, same lot, same modifiers would produce with
Heat/Durability/PowerSupply pinned to their identity (NominalContext) --
quality and lot are not part of "perfect", so a cheap item still reads 1
at full health. The nominal side bypasses the resolver's cache (which is
keyed only by (owner, stat) and would otherwise collide with the real
cached value) with one direct Evaluate call, a handful of flops.
Thruster.Condition and AetherDrive.Condition expose that ratio against
each behaviour's own governing stat (Thrust, Torque) for presentation to
read next.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
docs/stats-and-power-target.md: "Continuous consumers brown out through a
power supply curve on their performance stats." Cut 6 wired the curve
(EquippedItem.PowerSupplyFactor) but every continuous consumer still gated
its own effect on Item.PowerSupply >= 1f, so an 80% grant produced nothing
instead of 80% performance.
- Thruster/AetherDrive: gate narrowed from ">= 1f" to a true-zero floor
(> 1e-4f, PowerBus's own epsilon); Thrust/Torque are plain Evaluate()
reads, so a curved stat already degrades once the full-grant demand is
dropped.
- Radiator: the gate is deleted outright. PumpedHeat is already a curved
Evaluate() read; waste heat is unaffected, so a starved radiator now falls
behind instead of refusing to pump, and zero supply already zeroes
PumpedHeat via the curve with no special case needed.
- ConstantWeapon: the weapon no longer safes itself off (_firing = false)
on a partial grant -- only true zero still stops it. Damage already
degrades via base.Execute's own Evaluate() read.
- EnergyDraw: has no performance stat of its own -- its Execute return
value IS the effect (gates its BehaviorGroup). Narrowed the same way:
a partial grant still passes, only true zero closes the gate.
InputCapacitor and the three instant consumers (InstantWeapon, Sensor,
Shield) are untouched: all-or-nothing on a partial charge still holds.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
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>
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>
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>
Every Unity.Mathematics using, alias and qualified name under Assets\Scripts
and tools now names CultMath. The nine `using static ...noise;` lines are
gone: snoise lives on CultMath's math. UnityExtensions loses float4.ToColor
and Color.ToFloat4, which duplicate the bridge's ToColor and ToCultMath; the
float3 colour pair stays because the bridge has none.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Refactor item instance to use DatabaseLink
Labels now pop up when aiming at loot
Yeeted HUD trigger group UI into the abyss
Created rebindable action bar (missing: assigning actions)
Created Consumable Items
Refactored behaviors to work on consumable items
Implemented Velocity Conversion Behavior
Added Aetheric Drive hardpoint and item behavior
Added Drive RPM UI to HUD
Fixed death not disabling continue option
Disabling global input when dialog is visible
Fixed loadout generator failure when zero price item present
Replaced fixed top speed with drag coefficients
Ships are now more visible in lit areas (depending on cross section stat in Reflector behavior)
Refactored all behaviors to remove context & entity references (can get them from equipped item reference if needed)
Fixed crash when enemy dies of heatstroke while targeted
Exponential decay for visibility now uses correct time-independent formula
Refactored item inspection in properties panel (now consistent between equipped items vs. inventory and store items, shows effect of heat on stats)
Reordered item properties and added spacing between groups
Copied Main Menu to gameplay scene
Added Pause functionality (ESC key)
Save & Restore discovered zones
Applied vertical offset to asteroids (no more intersections!)
Nebula shader optimized to improve early exit
Nebula shader outputs light scattering to second render target
Modified depth of field shader to use nebula light scattering
Extracted all inspector code for database inspector into a single class per supported field type and wrote reflection code to invoke them automatically
Removed all Aetheria-specific code from database tools
Database View editor is now abstract and supports arbitrary grouping of entry types; specific users must implement their own database view
Removed blueprint system for crafting (and excised anything depending on that)
Implemented smootherstep and used it for wormhole animation
Quality dependent durability and heat exponents are now global instead of item specific
Database list view now groups equipment by manufacturer
Thrusters can now consume energy
Weapons can have reloads even when they don't have ammo
Items will switch themselves off below a customizable threshold to avoid damage
Player can toggle thermal override to disable safety shutdown
Selecting an item allows player to select whether the item will override
Clicking on the background of an inventory panel will display general ship settings (including thermal shutdown threshold)
Performance Stat evaluation has been moved to EquippedItem, slight performance improvement
Item online and active status are now reactive properties
Created player settings file to store e.g. shutdown threshold
Fixed crash when entering wormhole while enemies are visible
Added heat variation option for Performance Stats
Ability to toggle radiators on/off using H key
Item durability loss (Wear) is now heat dependent and incurred only when used
Restricted equip/unequip to docked (inactive) entities
Fixed race condition causing exceptions in database sync code
Weapons are now monolithic behaviors
Various new weapon behavior types implemented
All weapons have optional magazine and reload mechanic
Visibility now decays globally
Added MPL license headers
SectorRenderer renders entities in sector, manages particle systems for ship thrusters
Thrusters apply torque based on orientation and position relative to ship center of mass
GameManager controls the player ship's thrusters
Player can dock with station if they are near
Removed universal GameContext references
Refactored GameContext: It's now only responsible for item management. As such it's now called ItemManager
GlobalData is gone, relevant settings are distributed amongst specialized settings classes
Entities are no longer database entries, references to them are now direct - Zone is reponsible for storing them in ZonePacks
Controller subtypes are now generic classes, class argument is task type
Inventory is now a 2D grid instead of a list
Entity temperature is now a 2D grid instead of a number
Item sizes are now arbitrary shapes instead of a number
Hardpoints are now arbitrary shapes instead
Equipped items have their own temperatures
Map view now displays sunlight colors
Integrated Allegorithmic Substance Plugin
Imported Djinni ship and assigned Substance materials
Removed Node Editor framework (might come back later but needs work to integrate with behavior system)
Massively refactored everything related to entity inventory management
We are no longer recalculating mass by iterating over the entire inventory any time anything changes, for example
Gear is explicitly associated with hardpoints in the entity code
This did introduce some bugs, will clean those up tomorrow
Properties panel now allows nested lists with foldouts, item selection and click callbacks
Properties Panel UI has a new look
Refactored lambdas in control module code into methods for readability
Agent behaviors are no longer hard coded but instead created by equippable control modules
Game Context has a job system which assigns appropriate tasks to drone control modules