diff --git a/Assets/Resources/Settings.asset b/Assets/Resources/Settings.asset index 692fb08fe..e1b1933e3 100644 --- a/Assets/Resources/Settings.asset +++ b/Assets/Resources/Settings.asset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b2e346f533ccee5d2c655b3d8e0817e3f93e9234af06557ee9c6ec13cecb9acd -size 13137 +oid sha256:070f5797cb93cad308c38f3c87e05a729cfb21b87004fcdc2e146dba336045d6 +size 13073 diff --git a/Assets/Scripts/ServerShared/Scenarios/Arcs.cs b/Assets/Scripts/ServerShared/Scenarios/Arcs.cs index d1ceb61e0..bbbb867be 100644 --- a/Assets/Scripts/ServerShared/Scenarios/Arcs.cs +++ b/Assets/Scripts/ServerShared/Scenarios/Arcs.cs @@ -16,18 +16,21 @@ public sealed class Arcs : Scenario public override void Stage(ScenarioStage stage) { - var fighter = stage.Fit("LonginusX", + var fighter = stage.Fit("Longinus", ("Cockpit 2x2", int2(2, 6), None), - ("Traction", int2(2, 3), None), - ("Core Power", int2(2, 1), None), + ("Large Drive", int2(1, 0), Reversed), + ("Large Drive", int2(3, 0), Reversed), + ("Talaria", int2(2, 14), CounterClockwise), + ("Talaria", int2(3, 14), Clockwise), + ("Core Power", int2(2, 4), None), ("GT 3K", int2(0, 5), None), ("GT 3K", int2(5, 5), None), ("FastBlast+-", int2(1, 8), None), ("FastBlast+-", int2(4, 8), None), - ("Iapyx", int2(1, 2), CounterClockwise), - ("Iapyx", int2(4, 2), Clockwise), + ("Iapyx", int2(2, 2), CounterClockwise), + ("Iapyx", int2(3, 2), Clockwise), ("not if i see you first", int2(3, 10), None), - ("Fire Control Array", int2(2, 5), None)); + ("Fire Control Array", int2(1, 6), None)); var turret = stage.Fit("Turret", ("ClearPath", int2(1, 2), None), ("ClearPath", int2(5, 2), None), @@ -39,8 +42,8 @@ public sealed class Arcs : Scenario ("Fire Control Array", int2(3, 6), None)); stage.Player(fighter, float2(0, 0), facing: float2(0, 1)); - stage.Place(stage.Bare("LonginusX"), Bow); - stage.Place(stage.Bare("LonginusX"), Stern); + stage.Place(stage.Bare("Longinus"), Bow); + stage.Place(stage.Bare("Longinus"), Stern); stage.Place(turret, Turret, stance: ScenarioStance.Hostile); } } diff --git a/Assets/Scripts/ServerShared/Scenarios/Duel.cs b/Assets/Scripts/ServerShared/Scenarios/Duel.cs index fa270a4bd..457197186 100644 --- a/Assets/Scripts/ServerShared/Scenarios/Duel.cs +++ b/Assets/Scripts/ServerShared/Scenarios/Duel.cs @@ -4,7 +4,7 @@ using static ItemRotation; public sealed class Duel : Scenario { public override string Name => "Duel"; - public override string Brief => "One piloted, hostile LonginusX with slow GT 3K missiles and a Spectra in its hold. Verify: rolled " + + public override string Brief => "One piloted, hostile Longinus with slow GT 3K missiles and a Spectra in its hold. Verify: rolled " + "hits and near-misses, subsystem aim, beam reads above bow, armour takes the missile, loot keeps its brand."; public override uint Seed => 104; public override bool Ambient => false; @@ -13,19 +13,22 @@ public sealed class Duel : Scenario public override void Stage(ScenarioStage stage) { - var fighter = stage.Fit("LonginusX", + var fighter = stage.Fit("Longinus", ("Cockpit 2x2", int2(2, 6), None), - ("Traction", int2(2, 3), None), - ("Core Power", int2(2, 1), None), + ("Large Drive", int2(1, 0), Reversed), + ("Large Drive", int2(3, 0), Reversed), + ("Talaria", int2(2, 14), CounterClockwise), + ("Talaria", int2(3, 14), Clockwise), + ("Core Power", int2(2, 4), None), ("GT 3K", int2(0, 5), None), ("GT 3K", int2(5, 5), None), ("FastBlast+-", int2(1, 8), None), ("FastBlast+-", int2(4, 8), None), - ("Iapyx", int2(1, 2), CounterClockwise), - ("Iapyx", int2(4, 2), Clockwise), + ("Iapyx", int2(2, 2), CounterClockwise), + ("Iapyx", int2(3, 2), Clockwise), ("not if i see you first", int2(3, 10), None), ("PotaT+-", int2(1, 5), None), - ("Fire Control Array", int2(2, 5), None), + ("Fire Control Array", int2(1, 6), None), ("Store-All Plus", int2(2, 8), None)); stage.Player(fighter, float2(0, 0)); diff --git a/Assets/Scripts/ServerShared/Scenarios/LauncherAngles.cs b/Assets/Scripts/ServerShared/Scenarios/LauncherAngles.cs index a734be6e1..e5439d3cc 100644 --- a/Assets/Scripts/ServerShared/Scenarios/LauncherAngles.cs +++ b/Assets/Scripts/ServerShared/Scenarios/LauncherAngles.cs @@ -4,7 +4,7 @@ using static ItemRotation; public sealed class LauncherAngles : Scenario { public override string Name => "Launcher Angles"; - public override string Brief => "A GT 3K, a pswarm and two FastBlasts; a hostile LonginusX broadside on, a bare Longinus beside it. " + + public override string Brief => "A GT 3K, a pswarm and two FastBlasts; a hostile bare Longinus broadside on, another beside it. " + "Verify: hits pulse only the facing edge; a pswarm round whose target dies fades unexploded."; public override uint Seed => 105; public override bool Ambient => false; @@ -13,21 +13,24 @@ public sealed class LauncherAngles : Scenario public override void Stage(ScenarioStage stage) { - var launcher = stage.Fit("LonginusX", + var launcher = stage.Fit("Longinus", ("Cockpit 2x2", int2(2, 6), None), - ("Traction", int2(2, 3), None), - ("Core Power", int2(2, 1), None), + ("Large Drive", int2(1, 0), Reversed), + ("Large Drive", int2(3, 0), Reversed), + ("Talaria", int2(2, 14), CounterClockwise), + ("Talaria", int2(3, 14), Clockwise), + ("Core Power", int2(2, 4), None), ("GT 3K", int2(0, 5), None), ("pswarm", int2(5, 5), None), ("FastBlast+-", int2(1, 8), None), ("FastBlast+-", int2(4, 8), None), - ("Iapyx", int2(1, 2), CounterClockwise), - ("Iapyx", int2(4, 2), Clockwise), + ("Iapyx", int2(2, 2), CounterClockwise), + ("Iapyx", int2(3, 2), Clockwise), ("not if i see you first", int2(3, 10), None), - ("Fire Control Array", int2(2, 5), None)); + ("Fire Control Array", int2(1, 6), None)); stage.Player(launcher, float2(0, 0)); - stage.Place(stage.Bare("LonginusX"), float2(0, 900), facing: float2(1, 0), stance: ScenarioStance.Hostile); + stage.Place(stage.Bare("Longinus"), float2(0, 900), facing: float2(1, 0), stance: ScenarioStance.Hostile); stage.Place(stage.Bare("Longinus"), float2(300, 700)); } } diff --git a/Assets/Scripts/ServerShared/Scenarios/StarvedReactor.cs b/Assets/Scripts/ServerShared/Scenarios/StarvedReactor.cs index ccbf9baa8..4c57c3b36 100644 --- a/Assets/Scripts/ServerShared/Scenarios/StarvedReactor.cs +++ b/Assets/Scripts/ServerShared/Scenarios/StarvedReactor.cs @@ -13,17 +13,20 @@ public sealed class StarvedReactor : Scenario public override void Stage(ScenarioStage stage) { - var starved = stage.Fit("LonginusX", + var starved = stage.Fit("Longinus", ("Cockpit 2x2", int2(2, 6), None), - ("Traction", int2(2, 3), None), - ("MoveOnPro", int2(2, 1), None), + ("Large Drive", int2(1, 0), Reversed), + ("Large Drive", int2(3, 0), Reversed), + ("Talaria", int2(2, 14), CounterClockwise), + ("Talaria", int2(3, 14), Clockwise), + ("MoveOnPro", int2(2, 4), None), ("Spectra", int2(1, 8), None), ("Spectra", int2(4, 8), None), - ("Iapyx", int2(1, 2), CounterClockwise), - ("Iapyx", int2(4, 2), Clockwise), + ("Iapyx", int2(2, 2), CounterClockwise), + ("Iapyx", int2(3, 2), Clockwise), ("not if i see you first", int2(3, 10), None), - ("Fire Control Array", int2(2, 5), None)); + ("Fire Control Array", int2(1, 6), None)); stage.Player(starved, float2(0, 0)); - stage.Place(stage.Bare("LonginusX"), float2(0, 400)); + stage.Place(stage.Bare("Longinus"), float2(0, 400)); } } diff --git a/docs/merge-to-master-checklist.md b/docs/merge-to-master-checklist.md index 292ad913c..d5d70e17b 100644 --- a/docs/merge-to-master-checklist.md +++ b/docs/merge-to-master-checklist.md @@ -33,7 +33,7 @@ Compiled 2026-09-29 by Eyes from the maps on the branch. Pointers are `doc:line` ### 2b. Fresh launch (no `run.cc`, no `player.cc`) 3. **Launch to main menu.** Expect no Addressables errors, and the Groups window shows `Assets/Content` under `Default Local Group` (`addressables-cut.md:301`). `player.cc` appears and Continue is disabled (`cultcache-migration-cut.md:2746`). Proves: Addressables Cut 1, CultCache cutover. The 09-17 smoke covered the last two on the old tree; this is a cheap recheck on the new schema. -4. **New Game (tutorial).** Expect the galaxy generates and the start ship is LonginusX (`settings-globals-cut.md:498`, an expectation, not a recorded check for this stack). Expect a Zenith station with a docking bay in the entrance zone every time (`locomotion-cut.md:375`). Proves: locomotion Cut 1 station rule. +4. **New Game (tutorial).** Expect the galaxy generates and the start ship is the Longinus (`settings-globals-cut.md:498`, an expectation, not a recorded check for this stack). Expect a Zenith station with a docking bay in the entrance zone every time (`locomotion-cut.md:375`). Proves: locomotion Cut 1 station rule. 5. **Addressables, "Use Asset Database".** Ships, turret and Zenith spawn; thruster and aether-drive particles show; fire an instant weapon (Autocannon) and a charged one (ChargeBlast SG); action bar icons show (`addressables-cut.md:362-366`). Proves: Addressables Cut 2. ### 2c. Flight, power and economy @@ -44,19 +44,19 @@ Compiled 2026-09-29 by Eyes from the maps on the branch. Pointers are `doc:line` 9. **Brownout and refill (O6, O4).** Brownout reads as degradation, not breakage (`:774`, `:990`). A weapon refilling under brownout stutters instead of firing full rate and cooking the ship (`:729-730`). Proves: power-supply term, brownout, input capacitors. 10. **Memory across zones (O1).** Play across several zones with kills, open the Profiler or Task Manager, and confirm memory does not climb (`:672-673`, `:980-982`). Proves: stat resolver, the entity-leak fix (map Cut 2). Combine with steps 11 and 12. -### 2d. Combat (against a LonginusX-class AI), then save and Studio +### 2d. Combat (against a Longinus-class AI), then save and Studio 11. **Fire-control Cut 1 arcs.** Side-mounted weapons still fire at targets abeam; a turret tracks all the way round; nothing fires through the hull (`fire-control-cut.md:397-398`). 12. **Fire-control Cut 3 and shots.** Shots show rolled impacts and misses; a miss reads as a near-miss, not a bug; damage matches the HUD; aiming at a revealed subsystem concentrates damage; kill time is in the same order as before (`:659-661`). Reveal and selection work, kills drop loot, pickup stores it, the tractor beam still pulls (`headless-playground-cut.md:789-794`). No crash or frozen hot ship when the target vanishes (the 2026-09-20 failure, `fire-control-cut.md:1464-1470`). Recheck after the fixes: unrecorded. 13. **12.2, bow then beam.** The HUD `hull` factor reads higher from the beam. Turn your armoured face into a slow missile and watch where it lands (`:2380-2383`). 14. **12.3, launcher into a bow, then a flank.** Use a GT 3K or pswarm launcher. The schematic display pulses the facing edge only (`:2519-2520`). -15. **12.4, scratch catalog.** Watch a proximity airburst beside a LonginusX and a delayed penetrator into its nose. Damage must land where the model shows the blast; this confirms the centre-of-mass anchor. Bow is already confirmed +y (`:3095-3097`, `:2028-2031`). Proves: 12.4(b) `Detonate`. +15. **12.4, scratch catalog.** Watch a proximity airburst beside a Longinus and a delayed penetrator into its nose. Damage must land where the model shows the blast; this confirms the centre-of-mass anchor. Bow is already confirmed +y (`:3095-3097`, `:2028-2031`). Proves: 12.4(b) `Detonate`. 16. **Wormhole, save, Continue.** Take a wormhole, quit, Continue: same items, same tier and brand (`item-provenance-cut.md:536-537`). 17. **Die.** Continue is disabled and `run.cc` holds no ledger (`:540-541`). 18. **Shield panel, `FieldShieldTest`.** Follow the numbered look in `shield-panel-cut.md:683-700`. Nothing visible on load. Click the nose: a patch grows and the field ripple runs too. Click the flank: the panel lies flat, not tilted. Click the same spot twice: it strikes the same panel. Spam far-apart clicks: at most 12 panels, oldest replaced. Let them fade: none left. Then the Cut 5 payoff: strong hits dice the patch, and repeated hits on one spot break through on a later hit (`:737-739`). Proves: shield-panel Cuts 3, 4, 5. 19. **Studio click-through.** - Open `Aetheria.cc`. The fire-control behaviour union renders (`fire-control-cut.md:503-506`). - - `LonginusX` schematic still underlays the shape drawer (`addressables-cut.md:370-371`). + - `Longinus` schematic still underlays the shape drawer (`addressables-cut.md:370-371`). - The restored hull records open (`locomotion-cut.md:501`; the map gives no checklist beyond "the Studio click-through"). - The scratch catalog's new `Fuse` and `BlastRadius` fields edit (from 2a; no map line). - Open `GameData/run.cc` after step 16. `aetheria.provenanceledger` renders `Lots` with `Attributed` origins and per-role lists. If Studio cannot draw an int-keyed dictionary of unions, that is a Studio defect to raise, not a schema change (`item-provenance-cut.md:541-544`). diff --git a/tests/Aetheria.Shared.Tests/BrownoutTests.cs b/tests/Aetheria.Shared.Tests/BrownoutTests.cs index 660b4106b..b4a691574 100644 --- a/tests/Aetheria.Shared.Tests/BrownoutTests.cs +++ b/tests/Aetheria.Shared.Tests/BrownoutTests.cs @@ -10,7 +10,7 @@ using static CultMath.math; // Cut 7 (docs/stats-and-power-target.md): "Continuous consumers brown out through a power supply curve on // their performance stats. An exponent is enough." Cut 6 wired the curve itself (PowerCurveTests.cs) and the // bus already writes a real fractional grant (PowerBusTests.cs, PowerTierTests.cs), but every continuous -// consumer still gated its own effect on Item.PowerSupply >= 1f (Thruster.Execute, AetherDrive.Execute, +// consumer still gated its own effect on Item.PowerSupply >= 1f (Thruster.Execute, // Radiator.Execute, ConstantWeapon.Execute, EnergyDraw.Execute) -- so a partial grant read exactly like none, // the flicking-off bug the ruling names. This file pins each behaviour's own gate removal: a partial grant now // produces a reduced effect, a full grant is unchanged, a zero grant still produces nothing, the degradation @@ -18,12 +18,11 @@ using static CultMath.math; // rule, Cut 4) is untouched by any of it. Each rule has a matching mutation in // tests/mutation_tests_stats_power_cut7.py. // -// F6 (docs/stats-and-power-cut.md, Soul pass 2026-09-19) surfaced a real conflict this file's Radiator and -// AetherDrive fixtures happened to sit on top of, unnoticed because the pre-F6 registry only named each +// F6 (docs/stats-and-power-cut.md, Soul pass 2026-09-19) surfaced a real conflict this file's Radiator +// fixture happened to sit on top of, unnoticed because the pre-F6 registry only named each // consumer's own top-level request field. Radiator.PowerRequest's own early-out reads PumpedHeat AND WasteHeat -// (to decide whether the pump can keep up this tick); AetherDrive.PowerRequest's spin-up arithmetic reads -// Torque, LambdaMultiplier, MaximumRpm and PassiveCoupling. Cut 7 asks for the brownout curve to live on exactly -// the stats these two behaviours actually curve (PumpedHeat, Torque), but F6 forbade a PowerSupply term on any +// (to decide whether the pump can keep up this tick). Cut 7 asks for the brownout curve to live on exactly +// the stats the behaviour actually curves (PumpedHeat), but F6 forbade a PowerSupply term on any // stat a request reads at all, on pain of the exact oscillation Soul measured against the shipped catalog's own // "OK Disperser" radiator (SOUL_RadiatorRequestDependsOnItsOwnGrantAndOscillates, docs/stats-and-power-cut.md). // The six tests below that authored one were marked Skip rather than deleted or silently reworked to hide the @@ -32,8 +31,8 @@ using static CultMath.math; // // Nominal-request ruling (docs/stats-and-power-cut.md, operator ruling 2026-09-19) is that future pass: a power // request is evaluated nominally -- what the item wants at full supply, not what it is currently managing -// (EquippedItem.EvaluateNominalPower, Entity.cs). Radiator.PowerRequest and AetherDrive.PowerRequest now read -// PumpedHeat/Torque (and every other field in StatValidation.PowerRequestFields) through that nominal read, so +// (EquippedItem.EvaluateNominalPower, Entity.cs). Radiator.PowerRequest now reads +// PumpedHeat (and every other field in StatValidation.PowerRequestFields) through that nominal read, so // the same stat can carry a PowerSupply term (curved for Execute's real effect) AND be a request field, without // the request depending on its own answer -- there is no more oscillation to reproduce, because the request no // longer reads the live grant at all. StatValidation.ValidateNoPowerSupplyOnRequest (the static check F6 added @@ -64,14 +63,6 @@ public sealed class BrownoutTests : IDisposable private static PerformanceStat Curved(float max, float exponent) => new PerformanceStat { Min = 0, Max = max, Terms = { new StatTerm { Source = StatSource.PowerSupply, Exponent = exponent } } }; - // A flat Bezier (two keys, equal value, zero tangents): AetherDrive's TorqueProfile multiplies potential - // torque by this curve's value at the current rpm ratio -- flat at 1 keeps that multiplier out of the way of - // the PowerSupply curve under test. - private static BezierCurve FlatCurve(float value) => new BezierCurve - { - Keys = new[] { new float4(0, value, 0, 0), new float4(1, value, 0, 0) } - }; - private static EquippableItem Mint(CultCache cache, ItemManager items, ItemData design) => new EquippableItem { Data = cache.RefOf(design), @@ -180,55 +171,6 @@ public sealed class BrownoutTests : IDisposable Assert.Equal(.025f, half, 3); } - // --- AetherDrive: PassiveCoupling = 1 engages the rotor regardless of axis input, so the drive can be - // --- exercised without threading Ship's own movement/torque classification -- Axis stays at Ship.Update's - // --- own default (0,0,Turn), and Turn is 0 by default. Rpm starts at 0, - // --- so decay(0, ...) contributes nothing and the whole tick's Rpm gain is exactly this behaviour's own - // --- spin-up under test. --- - private (float rpmGain, AetherDrive drive) RunAetherDrive(float reactorCharge, float exponent = 1) - { - using var cache = OpenCatalog(new AetherDriveData - { - RotorDiameter = float3(1, 1, 1), RotorMass = float3(1, 1, 1), - MaximumRpm = Constant(100), CouplingLambda = float3(1, 1, 1), LambdaMultiplier = Constant(1), - CouplingEfficiency = Constant(1), Torque = Curved(30, exponent), TorqueProfile = FlatCurve(1), - EnergyDraw = Constant(50), PassiveCoupling = Constant(1) - }); - var ship = BuildShip(cache, reactorCharge); - ship.Update(1f); - var drive = ship.GetBehavior(); - return (drive.Rpm.x, drive); - } - - // Nominal-request ruling (docs/stats-and-power-cut.md, operator ruling 2026-09-19, see file header): Torque - // is a registered AetherDrive request field (PowerRequest's spin-up arithmetic reads it), but PowerRequest now - // reads it through EvaluateNominalPower (full-supply, undegraded), so Curved(...) on Torque is a legal - // catalog shape again -- Execute's own real, curved Evaluate is what actually degrades the spin-up below. - [Fact] - public void AetherDriveAtHalfGrantProducesReducedNotZeroNotFullSpinUp() - { - var (full, _) = RunAetherDrive(reactorCharge: 1000); // demand ~50, generation far exceeds it -> ratio 1 - var (half, _) = RunAetherDrive(reactorCharge: 25); // demand ~50, generation 25 -> ratio .5 - - Assert.True(half > 0f && half < full, $"half={half} should sit strictly between 0 and full={full}"); - } - - [Fact] - public void AetherDriveAtFullGrantIsUnchangedFromToday() - { - var (full, drive) = RunAetherDrive(reactorCharge: 1000); - Assert.Equal(1f, drive.Item.PowerSupply, 3); - Assert.True(full > 0f); - } - - [Fact] - public void AetherDriveAtZeroGrantProducesNothing() - { - var (zero, drive) = RunAetherDrive(reactorCharge: 0); - Assert.Equal(0f, drive.Item.PowerSupply, 3); - Assert.Equal(0f, zero, 4); - } - // --- Radiator: no explicit power gate survives Cut 7 at all -- PumpedHeat above is a plain Evaluate() read, // --- so a curved PumpedHeat already pumps less under a partial grant, and waste heat is unaffected, so a // --- starved radiator falls behind (the reduced-performance failure the ruling asks for) instead of the @@ -383,10 +325,10 @@ public sealed class BrownoutTests : IDisposable // --- Nominal-request ruling (docs/stats-and-power-cut.md, operator ruling 2026-09-19): a direct probe of // --- EquippedItem.EvaluateNominalPower itself, decoupled from any behaviour's own physical state (Radiator's - // --- temperature, AetherDrive's Rpm) so the only thing that can make it diverge from the ordinary Evaluate + // --- temperature) so the only thing that can make it diverge from the ordinary Evaluate // --- is the item's own PowerSupply grant. Thruster's EnergyUsage carries the term here purely as a probe -- // --- not a shape BrownoutTests' other fixtures author -- because Thruster has no other tick-to-tick state - // --- that could confound the comparison the way Radiator's RadiatorTemperature or AetherDrive's Rpm would. + // --- that could confound the comparison the way Radiator's RadiatorTemperature would. // --- Backs tests/mutation_tests_stats_power_nominal_request.py's own mutation of this exact method. --- [Fact] public void EvaluateNominalPowerIgnoresTheItemsCurrentPowerSupplyGrant() @@ -444,16 +386,4 @@ public sealed class BrownoutTests : IDisposable $"expected tick 2's gate to stay open on the nominal PumpedHeat/WasteHeat ratio, got demand {secondDemand}"); } - // --- The same wiring check attempted on AetherDrive.PowerRequest's own Torque read does not carry over - // --- cleanly: torqueRatio (actualRpmDelta/potentialRpmDelta) self-cancels Torque's own magnitude whenever - // --- the rotor is not yet rpm-capped (both numerator and denominator scale with Torque, so the ratio -- and - // --- so the final draw -- comes out identical whether Torque is read nominally or through the curved - // --- Evaluate; verified empirically while building this fixture: reverting AetherDrive.PowerRequest to - // --- Evaluate produced byte-identical TotalDemand across two ticks, including once the rotor was pushed - // --- into its rpm cap). A black-box demand comparison cannot discriminate this call site the way it can - // --- Radiator's binary gate above. Its correctness rests instead on the identical pattern to Radiator's - // --- (same EvaluateNominalPower call, same EquippedItem mechanism EvaluateNominalPowerIgnoresTheItemsCurrent - // --- PowerSupplyGrant already pins) and on AetherDriveAtHalfGrantProducesReducedNotZeroNotFullSpinUp/ - // --- AtFullGrantIsUnchangedFromToday/AtZeroGrantProducesNothing above, which exercise the real end-to-end - // --- Execute path this ruling exists to keep legal. Not part of the mutation harness for that reason. } diff --git a/tests/Aetheria.Shared.Tests/ConditionRatioTests.cs b/tests/Aetheria.Shared.Tests/ConditionRatioTests.cs index 9781f0ec9..13b5f281e 100644 --- a/tests/Aetheria.Shared.Tests/ConditionRatioTests.cs +++ b/tests/Aetheria.Shared.Tests/ConditionRatioTests.cs @@ -12,7 +12,7 @@ using static CultMath.math; // Should multiply that by actual performance." EquippedItem.ConditionRatio (Entity.cs) is the one number that // answers "how healthy does this item actually look": the item's resolved value for a stat against what the // same item, same lot, same modifiers would produce with Heat/Durability/PowerSupply pinned to their identity. -// Thruster.Condition and AetherDrive.Condition (Thruster.cs/AetherDrive.cs) are that ratio read against each +// Thruster.Condition (Thruster.cs) is that ratio read against each // behaviour's own governing stat (Thrust, Torque); ShipInstance.cs (Unity-side, not covered here) multiplies // presentation intent by it instead of recomputing any durability/heat/power arithmetic of its own. // Each rule here has a matching mutation in tests/mutation_tests_condition_ratio_cut8.py. diff --git a/tests/Aetheria.Shared.Tests/FireControlCut124Tests.cs b/tests/Aetheria.Shared.Tests/FireControlCut124Tests.cs index c6b1bb00c..c515d8469 100644 --- a/tests/Aetheria.Shared.Tests/FireControlCut124Tests.cs +++ b/tests/Aetheria.Shared.Tests/FireControlCut124Tests.cs @@ -469,10 +469,9 @@ public sealed class FireControlCut124Tests : IDisposable } // The same rule on the shipped hulls: a blast just grazing the corner farthest from the centre of mass. The - // corners of the shipped Longinus and LonginusX reach past the old cull's box-half-diagonal bound. + // corner of the shipped Longinus reaches past the old cull's box-half-diagonal bound. [Theory] [InlineData("Longinus")] - [InlineData("LonginusX")] public void AGrazingBlastAtAShippedHullsFarthestCornerLands(string hullName) { Shape shape; diff --git a/tests/Aetheria.Shared.Tests/FireControlCut7Tests.cs b/tests/Aetheria.Shared.Tests/FireControlCut7Tests.cs index 63578287d..aca0f24ba 100644 --- a/tests/Aetheria.Shared.Tests/FireControlCut7Tests.cs +++ b/tests/Aetheria.Shared.Tests/FireControlCut7Tests.cs @@ -212,7 +212,7 @@ public sealed class FireControlCut7Tests : IDisposable Assert.NotEmpty(equippable); Assert.Contains(equippable, e => e is WeaponItemData); - var hull = cache.GetByName("LonginusX"); + var hull = cache.GetByName("Longinus"); Assert.NotNull(hull); var settings = new GameplaySettings diff --git a/tests/Aetheria.Shared.Tests/RunStartTests.cs b/tests/Aetheria.Shared.Tests/RunStartTests.cs index 755dd91bb..3cb507f4e 100644 --- a/tests/Aetheria.Shared.Tests/RunStartTests.cs +++ b/tests/Aetheria.Shared.Tests/RunStartTests.cs @@ -670,7 +670,7 @@ public sealed partial class RunStartTests : IDisposable public void TheRealHullsMaterializeBareFromABrandedProduct() { var available = new LoadoutGenerator(ref _items.Random, _items, _galaxy, _galaxy.Entrance, null, 2); - foreach (var hull in new[] { Hull("Djinni"), Hull("LonginusX"), TurretHull() }) + foreach (var hull in new[] { Hull("Djinni"), Hull("Longinus"), TurretHull() }) { var failures = new List(); var entity = Loadouts.Materialize(_items, null, BareLoadout(hull), available.IsAvailable, failures); diff --git a/tests/Aetheria.Shared.Tests/ScenarioTests.cs b/tests/Aetheria.Shared.Tests/ScenarioTests.cs index cfd5cc174..2ee1f96be 100644 --- a/tests/Aetheria.Shared.Tests/ScenarioTests.cs +++ b/tests/Aetheria.Shared.Tests/ScenarioTests.cs @@ -129,7 +129,7 @@ public sealed partial class RunStartTests [Fact] public void ModesMatchOldNewGame() { - Assert.Equal("LonginusX", _startingHull); + Assert.Equal("Longinus", _startingHull); var tutorial = Launch(new TutorialGalaxy(), Inputs(() => GalaxySeed)); Assert.Empty(tutorial.failures); @@ -285,8 +285,8 @@ public sealed partial class RunStartTests var failures = Refused(Arena(true), new Scripted(true, stage => { stage.Player(stage.Bare("Djinni"), float2(0, 0)); - stage.Place(stage.Generated("LonginusX"), float2(300, 0), stance: ScenarioStance.Hostile, piloted: true); - stage.Place(stage.Fit("LonginusX", ("No Such Gun", int2(1, 8), ItemRotation.None)), float2(-300, 0)); + stage.Place(stage.Generated("Longinus"), float2(300, 0), stance: ScenarioStance.Hostile, piloted: true); + stage.Place(stage.Fit("Longinus", ("No Such Gun", int2(1, 8), ItemRotation.None)), float2(-300, 0)); })); Assert.Contains("No Such Gun", Assert.Single(failures)); } @@ -301,8 +301,8 @@ public sealed partial class RunStartTests var staged = Stage(zone, new Scripted(true, stage => { stage.Player(stage.Bare("Djinni"), float2(0, 0)); - stage.Place(stage.Generated("LonginusX"), float2(300, 0), stance: ScenarioStance.Hostile, piloted: true); - stage.Place(stage.Bare("LonginusX"), float2(-300, 0), stance: ScenarioStance.Neutral, piloted: false); + stage.Place(stage.Generated("Longinus"), float2(300, 0), stance: ScenarioStance.Hostile, piloted: true); + stage.Place(stage.Bare("Longinus"), float2(-300, 0), stance: ScenarioStance.Neutral, piloted: false); })); var player = staged.Player; var hostile = staged.Entities[0]; @@ -336,8 +336,8 @@ public sealed partial class RunStartTests var staged = Stage(zone, new Scripted(true, stage => { stage.Player(stage.Bare("Djinni"), float2(0, 0)); - stage.Place(stage.Bare("LonginusX"), float2(300, 0), stance: ScenarioStance.Hostile); - stage.Place(stage.Bare("LonginusX"), float2(-300, 0), stance: ScenarioStance.Hostile); + stage.Place(stage.Bare("Longinus"), float2(300, 0), stance: ScenarioStance.Hostile); + stage.Place(stage.Bare("Longinus"), float2(-300, 0), stance: ScenarioStance.Hostile); })); var player = staged.Player; var hostile = staged.Entities[0]; diff --git a/tests/Aetheria.Shared.Tests/ShipAuthoringTests.cs b/tests/Aetheria.Shared.Tests/ShipAuthoringTests.cs index f969ff187..98eeaf5f7 100644 --- a/tests/Aetheria.Shared.Tests/ShipAuthoringTests.cs +++ b/tests/Aetheria.Shared.Tests/ShipAuthoringTests.cs @@ -251,7 +251,7 @@ public sealed class ShipAuthoringTests [Theory] [InlineData(HardpointType.Hull), InlineData(HardpointType.Tool), InlineData(HardpointType.Thermal), InlineData(HardpointType.WarpDrive), InlineData(HardpointType.Reactor), InlineData(HardpointType.Shield), InlineData(HardpointType.Sensors), - InlineData(HardpointType.ControlModule), InlineData(HardpointType.AetherDrive)] + InlineData(HardpointType.ControlModule)] public void AnInternalHardpointCarriesNoAnchor(HardpointType type) { WithOnly(type).Validate(); diff --git a/tests/Aetheria.Shared.Tests/ShipValidationTests.cs b/tests/Aetheria.Shared.Tests/ShipValidationTests.cs index c67f3e6d3..804ff723f 100644 --- a/tests/Aetheria.Shared.Tests/ShipValidationTests.cs +++ b/tests/Aetheria.Shared.Tests/ShipValidationTests.cs @@ -33,7 +33,7 @@ public sealed class ShipValidationTests var ship = ShipAuthoringTests.Fixture(); ship.Hull.Hardpoints[0].Armor = 5; ship.Hull.Hardpoints[0].FiringArc = 360; - ship.Hull.Hardpoints[0].Type = HardpointType.AetherDrive; + ship.Hull.Hardpoints[0].Type = HardpointType.ControlModule; ship.Visual.Anchors.RemoveAll(anchor => anchor.Id == "thruster.port"); ship.Hull.Hardpoints[0].Rotation = ItemRotation.Clockwise; ship.Validate(); diff --git a/tests/Aetheria.Shared.Tests/SteeringTests.cs b/tests/Aetheria.Shared.Tests/SteeringTests.cs index b87e91f71..e6c8604a6 100644 --- a/tests/Aetheria.Shared.Tests/SteeringTests.cs +++ b/tests/Aetheria.Shared.Tests/SteeringTests.cs @@ -241,7 +241,7 @@ public sealed class SteeringTests } // The turn bound has one owner, the actuators: Ship.Update hands Turn to them unclamped, Thruster.Axis saturates to - // 0..1 and AetherDrive.Axis clamps to -1..1. A Turn of 3 therefore rotates the hull as a Turn of 1. + // 0..1. A Turn of 3 therefore rotates the hull as a Turn of 1. [Fact] public void TheActuatorsBoundATurnBeyondOne() { @@ -276,13 +276,6 @@ public sealed class SteeringTests thruster.Axis = -3f; Assert.Equal(0f, thruster.Axis); } - - // Axis is a pure property; no catalog hull carries a drive, so the instance is made without its item. - var drive = (AetherDrive)System.Runtime.CompilerServices.RuntimeHelpers.GetUninitializedObject(typeof(AetherDrive)); - drive.Axis = float3(3f, -3f, 3f); - Assert.Equal(float3(1f, -1f, 1f), drive.Axis); - drive.Axis = float3(0f, 0f, -3f); - Assert.Equal(-1f, drive.Axis.z); } // The size of a turn, computed from the hull and not read back from the code under test: each rotation thruster diff --git a/tests/mutation_tests_addressables_cut.py b/tests/mutation_tests_addressables_cut.py index 3194b47cb..953121448 100644 --- a/tests/mutation_tests_addressables_cut.py +++ b/tests/mutation_tests_addressables_cut.py @@ -33,7 +33,7 @@ UNITY_EXE = r"C:\Program Files\Unity\Hub\Editor\6000.3.24f1\Editor\Unity.exe" ITEM_DATA_CS = REPO_ROOT / "Assets" / "Scripts" / "ServerShared" / "ItemData.cs" DIRECTORY_BUILD_PROPS = REPO_ROOT / "Directory.Build.props" -# A known-good record to mutate: HullData "LonginusX", whose Prefab already resolves (see the Cut 2 +# A known-good record to mutate: HullData "Longinus", whose Prefab already resolves (see the Cut 2 # migration report). Texture2D guid is its own Schematic (schema_Longinus.png), still a real, # addressable asset, just the wrong type for Prefab (which wants a GameObject+EntityInstance). TEXTURE_GUID = "51702555e534a7a4fb39eb105b80bbaf" @@ -109,7 +109,7 @@ using System.Linq; using System.Reflection; using GameCult.Caching; -// Scratch-only (not landed): sets HullData "LonginusX".Prefab to an arbitrary string in a copy of +// Scratch-only (not landed): sets HullData "Longinus".Prefab to an arbitrary string in a copy of // the catalog, for tests/mutation_tests.py to feed to EngineAssetCheck and prove it fails. internal static class Program { @@ -122,12 +122,12 @@ internal static class Program { var stored = cache.AllStoredDocuments.First(s => s.Descriptor.DocumentType.Name == "HullData" && - (string)s.Descriptor.DocumentType.GetField("Name").GetValue(s.Document) == "LonginusX"); + (string)s.Descriptor.DocumentType.GetField("Name").GetValue(s.Document) == "Longinus"); var field = stored.Descriptor.DocumentType.GetField("Prefab", BindingFlags.Public | BindingFlags.Instance); field.SetValue(stored.Document, newValue); cache.UpsertAsync(stored.Descriptor.DocumentType, stored.Document, stored.Key).Wait(); cache.FlushAllBackingStores(); - Console.WriteLine($"Set HullData \"LonginusX\".Prefab = \"{newValue}\""); + Console.WriteLine($"Set HullData \"Longinus\".Prefab = \"{newValue}\""); return 0; } finally @@ -217,11 +217,11 @@ def main(): if code == 0: failures.append(f"{name}: EngineAssetCheck exited 0; the mutation was not caught") continue - if "LonginusX" not in text: - failures.append(f"{name}: failure did not name the record (LonginusX)") + if "Longinus" not in text: + failures.append(f"{name}: failure did not name the record (Longinus)") continue assert_contains(text, expect, name) - print(f"OK: caught, exit {code}, named LonginusX, mentioned {expect!r}") + print(f"OK: caught, exit {code}, named Longinus, mentioned {expect!r}") except SystemExit as exc: failures.append(f"{name}: {exc}") finally: diff --git a/tools/blender/aetheria_ships/ship_cc.py b/tools/blender/aetheria_ships/ship_cc.py index f16fe72a5..6018d6a96 100644 --- a/tools/blender/aetheria_ships/ship_cc.py +++ b/tools/blender/aetheria_ships/ship_cc.py @@ -33,7 +33,7 @@ HARDPOINT_MEMBERS = ("Type", "Position", "Shape", "Transform", "Rotation", "Armo ANCHOR_MEMBERS = ("Id", "Role", "ModelNodeId", "ParentId", "Order") # Enum member names in value order (HardpointType, ItemRotation). HARDPOINT_TYPE_NAMES = ("Hull", "Tool", "Thermal", "Thruster", "WarpDrive", "Reactor", "Radiator", - "Shield", "Sensors", "Energy", "Ballistic", "Launcher", "ControlModule", "AetherDrive") + "Shield", "Sensors", "Energy", "Ballistic", "Launcher", "ControlModule") ROTATION_NAMES = ("None", "CounterClockwise", "Reversed", "Clockwise")