Merge origin/master (arena cut 1: ItemManager takes a seed) into eureka/aetheria-gravity-laned-belts
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
95903ab00e
118 changed files with 1157 additions and 297 deletions
|
|
@ -122,7 +122,7 @@ namespace Aetheria.EditorTools
|
|||
var cache = AetheriaStores.Open(Path.Combine(_work, "belts.cc"), Path.Combine(_work, "belts-run.cc"), catalogWritable: true);
|
||||
cache.Upsert(new VerseGrammar { Revision = 1 });
|
||||
cache.FlushAsync().Wait();
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), settings, _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), settings, _ => { });
|
||||
|
||||
CultRecordKey Orbit(CultRecordKey parent, float distance, float phase, float2 fixedPosition = default)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ public static class CapabilityPresentationSmoke
|
|||
cache.FlushAsync().Wait();
|
||||
var ledger = new ProvenanceLedger();
|
||||
for (var i = 1; i <= 8; i++) ledger.Lots[i] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
var items = new ItemManager(cache, ledger, settings, _ => { });
|
||||
var items = new ItemManager(1u, cache, ledger, settings, _ => { });
|
||||
var zone = new Zone(items, new PlanetSettings(), new ZonePack(), new GalaxyZone { Name = "Smoke", Owner = null }, null);
|
||||
var hullRef = items.ItemData.RefOf<ItemData>(items.ItemData.GetByName<HullData>("Smoke Hull"));
|
||||
var ship = new Ship(items, zone, new EquippableItem { Data = hullRef, Durability = 1000, Lot = 1 }, new EntitySettings());
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ namespace Aetheria.EditorTools
|
|||
cache.Upsert(new SimpleCommodityData { Name = "Wiring Ore", Shape = oneCell, MaxStack = 100 });
|
||||
cache.FlushAsync().Wait();
|
||||
var ledger = new ProvenanceLedger();
|
||||
var items = new ItemManager(cache, ledger, settings, _ => { });
|
||||
var items = new ItemManager(1u, cache, ledger, settings, _ => { });
|
||||
|
||||
// A sun and a planet on a moving rail round it, both near the bodies below: the field under a floater changes with the
|
||||
// drawn time, so a presenter that samples it at the step's time, drops it, or reads lead 0 for the height draws a different y.
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ public static class FloatingItemsDrawnSmoke
|
|||
cache.FlushAsync().Wait();
|
||||
var ledger = new ProvenanceLedger();
|
||||
for (var i = 1; i <= 8; i++) ledger.Lots[i] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
var items = new ItemManager(cache, ledger, settings, _ => { });
|
||||
var items = new ItemManager(1u, cache, ledger, settings, _ => { });
|
||||
var zone = new Zone(items, new PlanetSettings(), new ZonePack(), null, null);
|
||||
EquippableItem Make(string name, int lot) => new EquippableItem
|
||||
{
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ namespace Aetheria.EditorTools
|
|||
var cache = AetheriaStores.Open(Path.Combine(work, Guid.NewGuid().ToString("N") + ".cc"), catalogWritable: true);
|
||||
cache.Upsert(new VerseGrammar { Revision = 1 });
|
||||
cache.FlushAsync().Wait();
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), settings.GameplaySettings, _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), settings.GameplaySettings, _ => { });
|
||||
return new Zone(items, new PlanetSettings { Flow = flow }, new ZonePack { Time = time }, new GalaxyZone { Name = "Parity", Owner = null }, null);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ namespace Aetheria.EditorTools
|
|||
var cache = AetheriaStores.Open(Path.Combine(work, "catalog.cc"), Path.Combine(work, "run.cc"), catalogWritable: true);
|
||||
cache.Upsert(new VerseGrammar { Revision = 1 });
|
||||
cache.FlushAsync().Wait();
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), settings.GameplaySettings, _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), settings.GameplaySettings, _ => { });
|
||||
var positions = new List<Vector2> { new Vector2(-130f, 85f), new Vector2(610f, -240f), new Vector2(670f, -270f), new Vector2(1700f, 300f) };
|
||||
var masses = new List<float> { 14152f, 273.9f, 29.2f, 0f };
|
||||
var parents = new List<int> { -1, 0, 1, -1 };
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ public static class MapBeltPerf
|
|||
Planets = { new CultRecordRef<BodyData>(sunKey), new CultRecordRef<BodyData>(beltKey) },
|
||||
Radius = 5000f, Mass = 10000f, Time = 0
|
||||
};
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), settings.GameplaySettings, _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), settings.GameplaySettings, _ => { });
|
||||
var zone = new Zone(items, new PlanetSettings
|
||||
{
|
||||
BodyRadius = new ExponentialCurve(), WellStrength = new ExponentialCurve { Multiplier = 1f, Exponent = 1f },
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ public static class MapPresenterSmoke
|
|||
};
|
||||
var ledger = new ProvenanceLedger();
|
||||
for (var i = 1; i <= 8; i++) ledger.Lots[i] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
var items = new ItemManager(cache, ledger, settings.GameplaySettings, _ => { });
|
||||
var items = new ItemManager(1u, cache, ledger, settings.GameplaySettings, _ => { });
|
||||
var zone = new Zone(items, planetSettings, pack, new GalaxyZone { Name = "Smoke", Owner = null }, null);
|
||||
var hullRef = items.ItemData.RefOf<ItemData>(items.ItemData.GetByName<HullData>("Smoke Hull"));
|
||||
var ship = new Ship(items, zone, new EquippableItem { Data = hullRef, Durability = 1000, Lot = 1 }, new EntitySettings());
|
||||
|
|
|
|||
|
|
@ -294,7 +294,11 @@ public class ActionGameManager : MonoBehaviour
|
|||
ConsoleController.MessageReceiver = this;
|
||||
|
||||
// A new run mints into an empty ledger; the saved run's stays in the store until the new run has started.
|
||||
ItemManager = new ItemManager(CultCache, PendingScenario != null ? new ProvenanceLedger() : RunSave.Lots(CultCache),
|
||||
// A new run is reseeded by RunStart; Continue's item draws come from the saved run's seed.
|
||||
ItemManager = new ItemManager(
|
||||
CurrentGalaxy == null ? RunStart.NoRunSeed
|
||||
: RunStart.ItemSeed(CurrentGalaxy, PendingScenario != null ? RunStart.ArenaStep : RunStart.ContinueStep),
|
||||
CultCache, PendingScenario != null ? new ProvenanceLedger() : RunSave.Lots(CultCache),
|
||||
Settings.GameplaySettings, Debug.Log);
|
||||
ZoneRenderer.ItemManager = ItemManager;
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,12 @@ public class FactionProductData
|
|||
// no Assembly; the mint derives both from the line.
|
||||
[Inspectable, JsonProperty("bin"), Key(7)]
|
||||
public ProductBin Bin;
|
||||
|
||||
// Off the market (ruling off-market-is-unoffered-product): the product keeps its maker, brand, quality and price, but no
|
||||
// station stocks it, no generation or chooser draws it, and its hull gives no gear a home. It reaches a ship only through a
|
||||
// Loadout that pins it. LoadoutGenerator.IsOffered is its one reader.
|
||||
[Inspectable, JsonProperty("notOffered"), Key(8)]
|
||||
public bool NotOffered;
|
||||
}
|
||||
|
||||
// How good this product's part is in one of its design's roles: a pseudo-gaussian distribution whose mean is
|
||||
|
|
|
|||
|
|
@ -17,7 +17,9 @@ using float4 = CultMath.float4;
|
|||
|
||||
public class ItemManager
|
||||
{
|
||||
public Random Random = new Random((uint) (DateTime.Now.Ticks%uint.MaxValue));
|
||||
// The item stream: each lot's quality and each generated fit. Seeded by whoever constructs the manager and reseeded
|
||||
// by RunStart at each step of a new run, never by the clock.
|
||||
public Random Random;
|
||||
|
||||
private Action<string> _logger;
|
||||
|
||||
|
|
@ -25,8 +27,10 @@ public class ItemManager
|
|||
public ProvenanceLedger Lots { get; }
|
||||
public GameplaySettings GameplaySettings { get; }
|
||||
|
||||
public ItemManager(CultCache itemData, ProvenanceLedger lots, GameplaySettings settings, Action<string> logger)
|
||||
public ItemManager(uint seed, CultCache itemData, ProvenanceLedger lots, GameplaySettings settings, Action<string> logger)
|
||||
{
|
||||
if (seed == 0) throw new ArgumentOutOfRangeException(nameof(seed), "an item stream needs a nonzero seed");
|
||||
Random = new Random(seed);
|
||||
ItemData = itemData;
|
||||
Lots = lots;
|
||||
GameplaySettings = settings;
|
||||
|
|
|
|||
|
|
@ -127,6 +127,9 @@ public static class Loadouts
|
|||
// The quality a design no manufacturer makes is built at: the console give's.
|
||||
public const float UnbrandedQuality = .95f;
|
||||
|
||||
// The seed of the scratch item manager a fit is tried on. It draws nothing, so any nonzero constant does.
|
||||
private const uint ScratchSeed = 1u;
|
||||
|
||||
// All-or-nothing: returns an entity only when every design resolved and fitted, the loadout has at most
|
||||
// WeaponGroupCount weapon groups, and every group index names a weapon slot. Every failure of a stage is listed; on any
|
||||
// failure nothing is returned and the zone and cache are unchanged. The stages run in the order that puts refusal
|
||||
|
|
@ -166,7 +169,7 @@ public static class Loadouts
|
|||
|
||||
// The cells fit or they do not whatever brand a design-only slot takes: the shape is the design's. Tried on its own
|
||||
// item manager, so nothing is minted into the live ledger and nothing is drawn.
|
||||
var scratch = new ItemManager(cache, new ProvenanceLedger(), itemManager.GameplaySettings, _ => { });
|
||||
var scratch = new ItemManager(ScratchSeed, cache, new ProvenanceLedger(), itemManager.GameplaySettings, _ => { });
|
||||
Assemble(scratch, null, hullData, loadout, Unbranded(scratch, hullSettled), slotsSettled.Select(settled => Unbranded(scratch, settled)).ToArray(), failures, out _);
|
||||
if (failures.Count > reported) return null;
|
||||
|
||||
|
|
|
|||
|
|
@ -197,6 +197,8 @@ public class LoadoutGenerator
|
|||
entry.design != null &&
|
||||
entry.design.Price > 0 &&
|
||||
entry.product.Manufacturer.IsSet() &&
|
||||
// A not-offered product is never a candidate, so the any-manufacturer fallback below cannot reach it either.
|
||||
IsOffered(ItemManager.ItemData, entry.product) &&
|
||||
(!(entry.design is EquippableItemData equippable) || HasHome(equippable, hulls)) &&
|
||||
(filter?.Invoke(entry.design) ?? true))
|
||||
.ToArray();
|
||||
|
|
@ -216,9 +218,10 @@ public class LoadoutGenerator
|
|||
// bunch more hulls before all the gear variety in the game has a home"): generation never offers it, so no station
|
||||
// stocks it. It gains one the moment a hull with a hardpoint that takes it exists. Tool gear goes in any interior.
|
||||
// Only a hull some product sells gives gear a home: a hull no faction builds (the Smoke Test Hull) is a test fixture, so its
|
||||
// hardpoints never put gear on a shelf. The one definition of the hulls HasHome considers.
|
||||
// hardpoints never put gear on a shelf. An off-market hull gives no gear a home either: only its pinned Loadout flies it.
|
||||
// The one definition of the hulls HasHome considers.
|
||||
public static HullData[] SoldHulls(CultCache items) =>
|
||||
items.GetAll<FactionProductData>().Select(product => items.Get(product.Design)).OfType<HullData>().Distinct().ToArray();
|
||||
items.GetAll<FactionProductData>().Where(product => IsOffered(items, product)).Select(product => items.Get(product.Design)).OfType<HullData>().Distinct().ToArray();
|
||||
|
||||
public static bool HasHome(EquippableItemData design, HullData[] hulls) =>
|
||||
design.HardpointType == HardpointType.Tool || design.HardpointType == HardpointType.Hull ||
|
||||
|
|
@ -231,9 +234,16 @@ public class LoadoutGenerator
|
|||
// always reaches its own manufacturer's gear, and otherwise gear made by a manufacturer its allegiance names
|
||||
// (operator, 2026-09-30: allegiance lists only other factions). Whether that manufacturer is present in the galaxy
|
||||
// does not matter (operator, 2026-10-06: a faction need not be present for its gear to be available).
|
||||
// A not-offered product is available to nobody, a stage with no faction included.
|
||||
public bool IsAvailable(FactionProductData product) =>
|
||||
Galaxy == null || Faction == null ||
|
||||
ItemManager.ItemData.Get(product.Manufacturer) == Faction || Faction.Allegiance.ContainsKey(product.Manufacturer);
|
||||
IsOffered(ItemManager.ItemData, product) && (
|
||||
Galaxy == null || Faction == null ||
|
||||
ItemManager.ItemData.Get(product.Manufacturer) == Faction || Faction.Allegiance.ContainsKey(product.Manufacturer));
|
||||
|
||||
// Whether a product is on the market at all (ruling off-market-is-unoffered-product). The one reader of NotOffered. A bin is
|
||||
// offered only if both it and its line are (ruling bin-inherits-line-offered); the catalog read guarantees a bin's line exists.
|
||||
public static bool IsOffered(CultCache items, FactionProductData product) =>
|
||||
!product.NotOffered && (product.Bin == null || !items.Get(product.Bin.Line).NotOffered);
|
||||
|
||||
// How much this outfitter favors a product: its maker's preference times the product's share of it. The one weight of
|
||||
// generation and of ChooseProduct. A bin takes the share of its maker's stock that would pass its floor and its line takes
|
||||
|
|
@ -248,9 +258,10 @@ public class LoadoutGenerator
|
|||
// 1 - Phi(floor): the fraction of a normal draw that lies at least floor deviations above its mean.
|
||||
private static float PassFraction(float floor) => .5f * (1 - erf(floor / sqrt(2f)));
|
||||
|
||||
// Each line's key to the sum of its bins' pass fractions: read from the catalog once per weighting pass, never kept.
|
||||
// Each line's key to the sum of its offered bins' pass fractions: read from the catalog once per weighting pass, never kept. An
|
||||
// off-market bin takes no share, so its line keeps the whole weight.
|
||||
private Dictionary<string, float> BinShares() => ItemManager.ItemData.GetAll<FactionProductData>()
|
||||
.Where(product => product.Bin != null)
|
||||
.Where(product => product.Bin != null && IsOffered(ItemManager.ItemData, product))
|
||||
.GroupBy(product => product.Bin.Line.Key.Value)
|
||||
.ToDictionary(group => group.Key, group => group.Sum(bin => PassFraction(bin.Bin.Floor)));
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ public static class RunStart
|
|||
|
||||
public static ZonePack GenerateArena(ItemManager itemManager, ZoneGenerationSettings zoneSettings, PlanetSettings planetSettings, Galaxy galaxy, Scenario scenario)
|
||||
{
|
||||
SeedItems(itemManager, galaxy, 1);
|
||||
SeedItems(itemManager, galaxy, ArenaStep);
|
||||
return galaxy.Entrance.PackedContents = ZoneGenerator.GenerateZone(
|
||||
itemManager, zoneSettings, planetSettings, galaxy, galaxy.Entrance, galaxy.IsPrelude, scenario.Ambient);
|
||||
}
|
||||
|
|
@ -36,7 +36,7 @@ public static class RunStart
|
|||
public static Staged Stage(ItemManager itemManager, Zone arena, Scenario scenario, string startingHull,
|
||||
TutorialGenerationSettings tutorialGenerationSettings, List<string> failures)
|
||||
{
|
||||
SeedItems(itemManager, arena.Galaxy, 2);
|
||||
SeedItems(itemManager, arena.Galaxy, StageStep);
|
||||
var stage = new ScenarioStage(itemManager, arena, startingHull, tutorialGenerationSettings);
|
||||
scenario.Stage(stage);
|
||||
var reported = failures.Count;
|
||||
|
|
@ -62,5 +62,22 @@ public static class RunStart
|
|||
// so a fixed seed fixes the arena's gear and the player's fit. Each step draws its own stream, so whatever runs
|
||||
// between them (populating the level) cannot shift it.
|
||||
private static void SeedItems(ItemManager itemManager, Galaxy galaxy, uint step) =>
|
||||
itemManager.Random = new Random(galaxy.Seed * 0x9E3779B1u + step);
|
||||
itemManager.Random = new Random(ItemSeed(galaxy, step));
|
||||
|
||||
// The steps of a run that draw items: generating its arena, staging its scenario, and continuing a saved run.
|
||||
public const uint ArenaStep = 1;
|
||||
public const uint StageStep = 2;
|
||||
public const uint ContinueStep = 3;
|
||||
|
||||
// The item seed of an ActionGameManager scene opened with no galaxy. No run is loaded, so nothing a run decides
|
||||
// draws from it.
|
||||
public const uint NoRunSeed = 0x5EED0001u;
|
||||
|
||||
// The item stream's seed for one step of a run: the run's seed and the step. Never zero, which no stream accepts.
|
||||
public static uint ItemSeed(Galaxy galaxy, uint step)
|
||||
{
|
||||
var seed = galaxy.Seed * 0x9E3779B1u + step;
|
||||
if (seed == 0) throw new InvalidOperationException($"Item seed is zero for galaxy seed {galaxy.Seed} at step {step}.");
|
||||
return seed;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,10 @@ public abstract class Scenario
|
|||
{
|
||||
public abstract string Name { get; }
|
||||
|
||||
// The scenario's stable identity: kebab-case, unique across Scenarios, never shown to the player, recorded by
|
||||
// matches and replays. Name is display only.
|
||||
public abstract string Id { get; }
|
||||
|
||||
// The conditions and what to verify in them, shown under the scenario's button.
|
||||
public abstract string Brief { get; }
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using static ItemRotation;
|
|||
public sealed class AiFusedDiscipline : Scenario
|
||||
{
|
||||
public override string Name => "AI Fused Discipline";
|
||||
public override string Id => "ai-fused-discipline";
|
||||
public override string Brief => "A Djinni; a piloted neutral Longinus carrying a Smoke Proximity and a piloted hostile Longinus whose only weapon is " +
|
||||
"Smoke Refused. Verify: the hostile closes on you and never fires its refused weapon; the neutral never fires its " +
|
||||
"fused one.";
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using static ItemRotation;
|
|||
public sealed class Arcs : Scenario
|
||||
{
|
||||
public override string Name => "Arcs";
|
||||
public override string Id => "arcs";
|
||||
public override string Brief => "The Smoke Test Hull with forward mounts and a side mount on each flank; a bare hull off the bow, one off " +
|
||||
"the stern, one abeam to port, one abeam to starboard, and a hostile turret. Verify: the hull keeps its " +
|
||||
"heading while you aim at the stern hull, and the forward guns hold. Designate the bow hull and fire with the " +
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using static ItemRotation;
|
|||
public sealed class Belt : Scenario
|
||||
{
|
||||
public override string Name => "Belt";
|
||||
public override string Id => "belt";
|
||||
public override string Brief => "The biggest asteroid belt the generator made, with the Longinus parked 100 m outside its ring, facing along it, " +
|
||||
"and nothing else in the arena. Verify: next and previous target step through the rocks and the reticle sweeps " +
|
||||
"across the belt without a hitch; Previous with nothing targeted takes the farthest rock; the target indicator " +
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using static ItemRotation;
|
|||
public sealed class Crossing : Scenario
|
||||
{
|
||||
public override string Name => "Crossing";
|
||||
public override string Id => "crossing";
|
||||
public override string Brief => "A Lightning Projector at the starboard gun and a Cell Blaster at the port gun, an unpiloted Djinni coasting across the bow " +
|
||||
"close ahead and an unpiloted one waiting at range. The crosser is launched to port at 50 m/s and only coasts: nothing steers it, " +
|
||||
"its hull drag slows it to about 25 m/s at the bow line, and it does not shoot back. Make the pass coasting: give no throttle until the Djinni has crossed the bow, then fly freely " +
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ public sealed class DemoTerminus : Scenario
|
|||
};
|
||||
|
||||
public override string Name => "Terminus (Demo)";
|
||||
public override string Id => "demo-terminus";
|
||||
public override string Brief => "One gate, one region, one boss. Allied: the Pirates. Opposed: Zhestokost. " +
|
||||
"Neutral: Lucent Media and Aeronautics Unlimited.";
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ using float2 = CultMath.float2;
|
|||
public sealed class DjinniShakedown : Scenario
|
||||
{
|
||||
public override string Name => "Djinni Shakedown";
|
||||
public override string Id => "djinni-shakedown";
|
||||
public override string Brief => "A fitted Djinni in empty space, facing +z, with a bare hull dead ahead and one abeam. Verify: its thrusters " +
|
||||
"move and turn it; with the hull offline and thrusters firing, its speed does not run away. Its " +
|
||||
"articulated ballistic barrel points at the hull ahead when it bears, and rests at the arc edge while you " +
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using static ItemRotation;
|
|||
public sealed class Duel : Scenario
|
||||
{
|
||||
public override string Name => "Duel";
|
||||
public override string Id => "duel";
|
||||
public override string Brief => "One piloted, hostile Longinus with slow guided missiles and a Light Laser 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;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using static ItemRotation;
|
|||
public sealed class FusedRounds : Scenario
|
||||
{
|
||||
public override string Name => "Fused Rounds";
|
||||
public override string Id => "fused-rounds";
|
||||
public override string Brief => "The Smoke Test Hull with Smoke Proximity, Smoke Delayed and a Cluster Cannon; a hostile bare Longinus at mid range, a bare " +
|
||||
"hull dead ahead, a fragile one to starboard, a close one inside the proximity blast radius, and one astern. " +
|
||||
"Verify with nothing selected: aim past every hull and the round bursts at max range along the aim; aim at the hull " +
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using static ItemRotation;
|
|||
public sealed class LauncherAngles : Scenario
|
||||
{
|
||||
public override string Name => "Launcher Angles";
|
||||
public override string Id => "launcher-angles";
|
||||
public override string Brief => "A Guided Missile Launcher, a Swarm Rocket Pod and two Cell Blasters; a hostile bare Longinus broadside on, another beside it. " +
|
||||
"Verify: hits pulse only the facing edge; a Swarm Rocket Pod round whose target dies fades unexploded.";
|
||||
public override uint Seed => 105;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using static ItemRotation;
|
|||
public sealed class LockOn : Scenario
|
||||
{
|
||||
public override string Name => "Lock On";
|
||||
public override string Id => "lock-on";
|
||||
public override string Brief => "A hostile closes from out of sight, finds you and locks. Verify: no warning at first; once it locks, one " +
|
||||
"indicator appears toward it and tightens as the lock grows; break line of sight or range and it vanishes; " +
|
||||
"your own ping shows no incoming paint on its side, only the lock does.";
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ using float2 = CultMath.float2;
|
|||
public sealed class LongHaul : Scenario
|
||||
{
|
||||
public override string Name => "Long Haul";
|
||||
public override string Id => "long-haul";
|
||||
public override string Brief => "A standard ship in a populated galaxy. Verify: memory stays flat across zones with kills; " +
|
||||
"warp, quit and Continue keep items, tier and brand; death disables Continue.";
|
||||
public override uint Seed => 106;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using float2 = CultMath.float2;
|
|||
public sealed class MainGalaxy : Scenario
|
||||
{
|
||||
public override string Name => "Main Galaxy";
|
||||
public override string Id => "main-galaxy";
|
||||
public override string Brief => "The full galaxy: factions drawn at random, bosses guarding the road to the exit.";
|
||||
|
||||
public override Galaxy Generate(GalaxyStage stage) => stage.Main();
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ public sealed class Pursuit : Scenario
|
|||
private const float Astern = 300;
|
||||
|
||||
public override string Name => "Pursuit";
|
||||
public override string Id => "pursuit";
|
||||
public override string Brief => "A hostile Longinus on your tail; a Mine Launcher fitted, three Coolant Vents and two Thruster " +
|
||||
"Overdrives aboard. Verify: lay mines in its path, the mine pulses slowly after 2 s, fast when " +
|
||||
"the hostile is near, explodes 2 s later; vent and coast to lose it; overdrive away.";
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using static ItemRotation;
|
|||
public sealed class RefusedRounds : Scenario
|
||||
{
|
||||
public override string Name => "Refused Rounds";
|
||||
public override string Id => "refused-rounds";
|
||||
public override string Brief => "The Smoke Test Hull with Smoke Refused, Smoke Refused Beam, Smoke Refused Charge and an Autocannon; a bare Longinus " +
|
||||
"dead ahead. Each smoke weapon's blast radius is wider than its range, so it can never be fired. Verify: pull each " +
|
||||
"trigger and a refused shot costs no ammo, energy, heat, sound, visibility or cooldown, the beam never starts and " +
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using static ItemRotation;
|
|||
public sealed class StarvedReactor : Scenario
|
||||
{
|
||||
public override string Name => "Starved Reactor";
|
||||
public override string Id => "starved-reactor";
|
||||
public override string Brief => "Two Light Lasers on a Muon Fusion Reactor, which can fall to a quarter of the Compact Reactor's charge, and a bare hull " +
|
||||
"to shoot. Verify: starvation reads as starvation, brownout degrades, refills stutter.";
|
||||
public override uint Seed => 102;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using float2 = CultMath.float2;
|
|||
public sealed class TutorialGalaxy : Scenario
|
||||
{
|
||||
public override string Name => "Tutorial Galaxy";
|
||||
public override string Id => "tutorial-galaxy";
|
||||
public override string Brief => "A small galaxy of fixed factions, entered in a ship of the protagonist faction.";
|
||||
|
||||
public override Galaxy Generate(GalaxyStage stage) => stage.Prelude();
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using static ItemRotation;
|
|||
public sealed class WeaponFeel : Scenario
|
||||
{
|
||||
public override string Name => "Weapon Feel";
|
||||
public override string Id => "weapon-feel";
|
||||
public override string Brief => "The Smoke Test Hull with a Charged Scatter Blaster, an Autocannon and a Cell Blaster burst weapon; a bare Longinus dead ahead. " +
|
||||
"Verify: Charged Scatter Blaster and Autocannon fire with effects, particles and action-bar icons; the burst weapon's " +
|
||||
"cadence is as before; a fast release-and-press on the Charged Scatter Blaster starts no second charge.";
|
||||
|
|
|
|||
|
|
@ -14,6 +14,10 @@ public static class SimulationDice
|
|||
// never rides ItemManager.Random, so minting items cannot change how a pilot fights.
|
||||
public const uint CombatSampleStream = 3;
|
||||
public const uint PatrolStream = 4;
|
||||
// Generation placement of an unpacked ship packed at the origin, ordinal its 1-based index in the pack, on the zone's
|
||||
// seed (CombatSeed): the run seed feeds drops and mines only (ruling run-seed-drops-only), and placement never rides
|
||||
// ItemManager.Random.
|
||||
public const uint ScatterStream = 5;
|
||||
|
||||
// 9.1 (docs/fire-control-cut.md): fmix32, MurmurHash3's 32-bit finalizer -- xor-shift, multiply,
|
||||
// xor-shift, multiply, xor-shift. Diffuses a structured seed (a zone seed XORed with a small,
|
||||
|
|
|
|||
|
|
@ -266,14 +266,18 @@ public class Zone
|
|||
foreach (var wear in pack.ChunkWear)
|
||||
_wear[new ChunkId(wear.Field, wear.Index)] = new ChunkWear { Damage = wear.Damage, BrokenUntil = wear.BrokenUntil };
|
||||
|
||||
var ordinal = 0u;
|
||||
foreach (var entityPack in pack.Entities)
|
||||
{
|
||||
ordinal++;
|
||||
var entity = EntitySerializer.Unpack(_itemManager, this, entityPack);
|
||||
var ship = entity as Ship;
|
||||
Admit(entity, piloted: ship is {IsPlayerShip: false});
|
||||
// Generation placement, not admission: a ship packed at the origin is scattered across the zone.
|
||||
// Generation placement, not admission: a ship packed at the origin is scattered across the zone, on the
|
||||
// zone's own dice, so nothing drawn from the item stream can move it.
|
||||
if (ship is {IsPlayerShip: false} && lengthsq(ship.Position) < 1)
|
||||
ship.Position = _itemManager.Random.NextFloat3(float3(-pack.Radius * .5f), float3(pack.Radius * .5f));
|
||||
ship.Position = SimulationDice.For(CombatSeed, SimulationDice.ScatterStream, ordinal)
|
||||
.NextFloat3(float3(-pack.Radius * .5f), float3(pack.Radius * .5f));
|
||||
}
|
||||
|
||||
// TODO: Associate planets with stored entities for planetary colonies
|
||||
|
|
|
|||
178
docs/aetheria-arena-target.md
Normal file
178
docs/aetheria-arena-target.md
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
# Aetheria arena: deterministic headless matches between swappable brains
|
||||
|
||||
Date: 2026-10-21. Status: target, drafted by Imagination for Self to commit and admit.
|
||||
Body pinned at `GameCult/Aetheria` origin/master `d52b50f9`. Probes ran on that tree.
|
||||
|
||||
## 1. Why
|
||||
|
||||
The operator ruled that arenas are a design instrument (`aetheria-release:ruling:arenas-explore-the-metagame`). Agents
|
||||
explore the metagame through virtual play, and the design iterates on what they find. Her words: "We use agents to explore the
|
||||
metagame space so that we can iterate on the game design through virtual play." The combat Colosseum develops NPC
|
||||
behaviour by implementing many strategies over shared primitives, and the arena selects among them
|
||||
(`colosseum-is-the-npc-behaviour-lab`). Colosseum statistics fit the composition model that settles fights off screen
|
||||
(`colosseum-feeds-matchup-model`, `matchup-composition-model`). The strategic arena trains on generated galaxies varied
|
||||
across seeds (`strategic-start-r2-generator-trains`). The demo waits on how well that sim performs
|
||||
(`demo-start-gated-on-sim-quality`).
|
||||
|
||||
All of that rests on one foundation. That foundation is the campaign:
|
||||
|
||||
- A match is a pure function of its inputs.
|
||||
- A brain reaches the world only through a typed seam.
|
||||
- A match can be recorded, replayed and checked.
|
||||
- Matches run headless on Yggdrasil fast enough to beat rating noise.
|
||||
|
||||
The strategic state, its rules, the matchup model and the strategic arena are later campaigns (section 6). They consume this
|
||||
foundation and do not reshape it.
|
||||
|
||||
## 2. Body facts (probed at d52b50f9)
|
||||
|
||||
Randomness:
|
||||
- **Combat dice.** Combat dice are per shot: `FireControl.Commit` seeds a local generator from `Zone.CombatSeed` and the
|
||||
shot id (`FireControl.cs:931`). AI dice are per pilot: `SimulationDice.For(CombatSeed, CombatSampleStream|PatrolStream,
|
||||
Agent.Id)` (`SimulationDice.cs:9-38`, `Combat.cs:178`, `Zone.cs:320-323`). Drop dice use `Zone.DropSeed` and the
|
||||
death ordinal (`Zone.cs:131,159,460`).
|
||||
- **CombatSeed ignores the run seed.** `Zone.CombatSeed` is `GalaxyZone.Name.StableHash()` (`Zone.cs:158`), with no run
|
||||
seed in it. This is ruled: `aetheria-release:ruling:run-seed-drops-only` (2026-10-18, operator). It means one scenario
|
||||
replayed at one galaxy seed rolls identical dice every round.
|
||||
- **The item stream at run start.** `ItemManager.Random` is still clock-seeded at construction (`ItemManager.cs:20`).
|
||||
`RunStart.SeedItems` reseeds it from the galaxy seed before arena generation (step 1) and before staging (step 2)
|
||||
(`RunStart.cs:62-65`). After staging, the stream runs on shared. Its draws are lot quality on every mint
|
||||
(`ItemManager.cs:147,165`), the scatter of an unpacked ship packed at the origin (`Zone.cs:276`), the wormhole exit and
|
||||
console spawns (`ActionGameManager.cs:589-593,710`), and staged and generated fits (`ScenarioStage.cs:31,92`).
|
||||
- **Zone generation.** It seeds a local generator from the zone's name hash and its position (`ZoneGenerator.cs:52-53`). A
|
||||
zone reached by warp is generated in `ActionGameManager.PopulateLevel` (`:716-731`), a Unity owner, and not in
|
||||
ServerShared.
|
||||
- **MIConvexHull is gone.** Commit `e56af288` replaced it with a double-precision Delaunay (`Delaunay.cs`). The
|
||||
`colosseum-npc-lab` follow-up's "unseeded MIConvexHull randoms" item is stale.
|
||||
- **Trig is the host's libm.** CultMath trig and pow forward to `MathF` (`CultLib packages/cultmath/src/CultMath/math.cs:62-70`).
|
||||
Cross-host float identity is therefore unproven: Windows UCRT and glibc may differ in the last ulp.
|
||||
|
||||
Determinism probe:
|
||||
- **What ran.** A scratch partial of `RunStartTests` on Yggdrasil, Linux, .NET 10.0.12. It ran `LaunchDuel`: the Duel
|
||||
scenario, seed 104, the item stream reseeded. The player is scripted to fire every 90 steps and the rival is an AI. It
|
||||
stepped 36,000 steps at 1/60 s and hashed `Snapshot` every step.
|
||||
- **Result.** Three separate processes gave the same hash, `082F7400...`. The first death came at step 1445. Generation
|
||||
took about 325 ms per launch, and stepping ran about 9,700 steps/s. That is about 160x real time, but mostly with one
|
||||
ship left after the first death, so it is not yet a two-ship throughput figure.
|
||||
- **Existing pin.** `SimClockTests.OneHistoryTwice` already pins in-process repeatability of the Duel.
|
||||
|
||||
How brains reach the world:
|
||||
- **The pilot writes ship fields directly.** `Agent.Update` writes `Ship.Turn`, `Ship.MovementDirection` and
|
||||
`TryActivateConsumable` (`Agent.cs:53-110`). `Combat` writes `Turn` and calls fire control (`Combat.cs:149` and
|
||||
others). `MoveTo` writes too.
|
||||
- **The flight decides targets.** `Flight` picks `Ship.Target` (`Flight.cs:178`) and `Agent.Task` from the union of its
|
||||
members' `VisibleEnemies`, so target choice is sensor-gated. Flights are keyed by faction, one per zone
|
||||
(`Zone.cs:75,312`), and tick in `Zone.Update` (`:363`).
|
||||
- **Aiming reads ground truth.** `Combat` reads the target's true `Position` (`Combat.cs:52`). Its picture of the
|
||||
target's weapons comes from the EW book (`Combat.cs:55-60`).
|
||||
- **The player has its own writes.** The player path is `ActionGameManager`, with 12 direct writes of the same fields.
|
||||
- **Agents hold live references.** An agent holds live `Ship`, `Zone` (through `Ship.Zone`) and `ItemManager`
|
||||
references (`Agent.cs:27-31`). Nothing narrows what a brain can read or write.
|
||||
- **Defined but unused tasks.** `HaulingTask`, `Mining`, `StationTowing` and `Survey` are defined, but nothing
|
||||
constructs them.
|
||||
|
||||
Harness:
|
||||
- **The run owner.** `RunStart` owns new runs and every run is a `Scenario`, compiled C#
|
||||
(`Scenario.cs`, `Scenarios/*.cs`: 17 scenarios, Duel and Pursuit among them).
|
||||
- **Headless today.** `Zone.Update` steps headless. Fights resolve headless, with commit and resolution in `FireControl`.
|
||||
`SimClock` is the fixed-step owner.
|
||||
- **No runner.** There is no headless runner command: `tools/AetherDb/Program.cs:25-56` has catalog commands only. The
|
||||
scenario tests are the only headless driver.
|
||||
- **Off screen.** Only the player's zone ticks (`ActionGameManager.cs:1357-1361`). Zone ownership is written only at
|
||||
generation (`Galaxy.cs:346`) and on load (`:77`). There is no job or mission generator (`Galaxy.cs:578`).
|
||||
|
||||
## 3. Invariants
|
||||
|
||||
- `A-pure`: A match is a pure function of (code revision, catalog revision, scenario id, match seed, brain ids per
|
||||
side, host determinism class). The same inputs give the same per-tick state digest, in any process, run alone or
|
||||
in a batch.
|
||||
- `A-no-clock`: No input to a match comes from the wall clock, process identity, hash-code ordering of reference
|
||||
types, or a stream another subsystem advances. Every draw a match reaches comes from `SimulationDice` (or a successor
|
||||
owner) keyed by a structured seed. A missing seed is refused, never defaulted to the clock.
|
||||
- `A-seam`: A brain reads only its side's `PilotView` and writes only `PilotCommand`s. One applier in the zone step
|
||||
writes ship control fields from commands. Brains, the player's input and the playground use that applier and nothing
|
||||
else. A brain holds no live `Ship`, `Zone` or `ItemManager` reference.
|
||||
- `A-truthful-view`: A view holds what that side's sensors and book give, the player's information. It never holds
|
||||
ground truth about another side. (Depends on question `observation-fidelity`.)
|
||||
- `A-replay`: A replay holds the match inputs, the command stream and periodic digests. Replaying it reproduces every
|
||||
digest, or it names the first tick that diverges. A replay records its host determinism class. Under
|
||||
`cross-host-determinism`, it is trusted only on that class.
|
||||
- `A-compiled`: Brains and arena scenarios ship as compiled C# content in the Aetheria repo, under stable ids.
|
||||
No model call, interpreter or DSL runs inside a match (`arenas-explore-the-metagame`, Aetheria's compiled-mods rule).
|
||||
- `A-one-sim`: The arena runs the game's own simulation. There is no arena-only physics, combat or rules fork. An exploit
|
||||
the arena finds is a game bug.
|
||||
- `A-headless`: A match runs with no Unity type loaded, from a command that runs in Yggdrasil's dotnet container.
|
||||
- `A-records`: Match results, replays and tournaments are CultCache documents in a directory the runner is given. They
|
||||
never land in `GameData/run.cc`, `player.cc` or the catalog.
|
||||
|
||||
## 4. Not in scope
|
||||
|
||||
- Strategic state: holdings, stockpiles, faction relations, strength or fronts. Also any off-screen zone tick, ownership
|
||||
change, job board or generator starting-infrastructure rule. These belong to campaign 2.
|
||||
- The matchup composition model and its fitting or residual retraining (campaign 2 or 3).
|
||||
- Character or fun scoring, the model judge and the operator's pairwise calibration
|
||||
(`strategic-scoring-success-and-character`; strategic arena).
|
||||
- A large strategy library. This campaign lands the seam and two brains, enough to prove a tournament. The vocabulary
|
||||
grows as content afterwards.
|
||||
- An Eve/CultMesh operator surface for tournaments. Results are CultCache documents, and a surface is a follow-up once
|
||||
there is a consumer.
|
||||
- Moving warp-time zone generation out of `ActionGameManager`. It becomes in scope only if a match crosses a wormhole,
|
||||
and arena matches are one zone.
|
||||
- Cross-host deterministic trig in CultMath. It is a CultLib follow-up if `cross-host-determinism` rules that way.
|
||||
- The Unity presentation of arena matches, and replay viewing in the client.
|
||||
|
||||
## 5. Identity, lifecycle, authority
|
||||
|
||||
| Kind | Identity | Lifecycle | Authority (owner; forbidden writers) |
|
||||
|---|---|---|---|
|
||||
| Brain (compiled strategy) | A stable string id declared by the class, such as `doctrine.v1`. Ids are unique across the brain registry, which refuses a duplicate. Behaviour is versioned by code revision, not by the id | Added as code. Revised by commits: a behaviour change that should not merge ratings bumps the id's version. Retired brains stay compiled, become products or faction behaviour (`colosseum-is-the-npc-behaviour-lab`), or are deleted | Owner: the brain registry, compiled. Forbidden: any runtime-loaded or model-written brain, and a brain writing ship fields directly |
|
||||
| Arena scenario | A stable id string on `Scenario`. Today the identity is `Name`, a display string, so a stable `Id` is a finding to add | Same as brains: code, revised by commits. A match records its id and the code revision | Owner: the `Scenarios` registry. Forbidden: a match staging entities outside `RunStart.Stage` |
|
||||
| Match seed | A uint chosen by the tournament, nonzero. It feeds the galaxy seed, the item-stream steps and the start variance (per question `match-variance`) | Fixed per match, never rerolled. Replay reuses it | Owner: the runner, from the tournament spec. Forbidden: the clock, and `Scenario.Seed`'s clock fallback inside a match |
|
||||
| Combat, drop and pilot dice | `SimulationDice.For(zoneSeed, stream, ordinal)`. `CombatSeed` is the zone name hash (ruled) and `DropSeed` mixes in the run seed | Derived each draw, never stored | Owner: `SimulationDice`. Forbidden: any draw on `ItemManager.Random` from behaviour or combat (`ai-sample-offset-off-item-stream`) |
|
||||
| Item stream (`ItemManager.Random`) | One stream per `ItemManager` | Reseeded by `RunStart` steps 1 and 2. After that it advances on every mint | Owner: `RunStart` seeds it and `ItemManager` draws. Forbidden: its clock-seeded default reaching a match (`A-no-clock`) |
|
||||
| PilotCommand | (match, tick, side, ship ordinal `Agent.Id`) | Produced by a brain each tick and consumed by the applier the same tick. Recorded in the replay, never re-derived | Owner: the side's brain, or player input. Forbidden: any writer of ship control fields other than the applier |
|
||||
| PilotView | (match, tick, side) | Derived each tick from the side's sensors and book. Never stored, and never in the replay except as a digest if wanted | Owner: the view builder, one function. Forbidden: a brain reading `Zone`, `Ship` or `ItemManager` |
|
||||
| Replay | A CultCache document keyed by match id = (tournament id, ordinal) | Written once when the match ends and never edited. Superseded only by a new match. A divergence on replay is a finding, not an edit | Owner: the runner's recorder. Forbidden: a replay written by the game client, until a cut says so |
|
||||
| Match result | Keyed by match id, like the replay | Written with the replay. Read by the tournament and, later, by the matchup model | Owner: the runner. Forbidden: hand edits, and results with no replay |
|
||||
| Tournament | A CultCache document keyed by a tournament id (date plus label). It names its brains, scenarios, seed range, code and catalog revisions | Created by the runner. Ratings are derived from results and recomputable. A rerun is a new tournament | Owner: the runner. Forbidden: ratings stored as truth without the results they derive from |
|
||||
| Host determinism class | (OS, architecture, runtime) | Recorded on every replay and result | Owner: the runner. Forbidden: comparing digests across classes unless `cross-host-determinism` rules them one class |
|
||||
|
||||
No cell is empty. The Scenario row exposes a gap: `Name` is today's only identity, and it is a display string. Cut 1 adds `Id`.
|
||||
|
||||
## 6. Campaign split
|
||||
|
||||
1. `aetheria-arena` (this one): determinism, the seam, replay, the headless runner and the first tournament.
|
||||
2. `aetheria-living-world` (later):
|
||||
- strategic state owned in ServerShared;
|
||||
- the galaxy generator plus faction starting-infrastructure rules;
|
||||
- the off-screen strategic tick at reduced detail;
|
||||
- ownership changing hands;
|
||||
- the lore institutions as damping rules (`snowball-damping-lore-institutions`);
|
||||
- the job board from needs;
|
||||
- the strategic arena on generated seeds, scored on success and character.
|
||||
|
||||
It owns the demo gate (`demo-start-gated-on-sim-quality`) and absorbs follow-up `release-target-living-demo`'s map.
|
||||
3. `aetheria-matchups` (later, after 1 and enough brains): Colosseum statistics fitting the composition model and the
|
||||
on-screen residual loop. It is split from 2 because its foundation, a fitted statistical model, is a different
|
||||
pipeline. Campaign 2 consumes its output through one typed predictor.
|
||||
|
||||
## 7. Cut sequence (campaign 1)
|
||||
|
||||
1. **Determinism ledger.**
|
||||
- Add `Scenario.Id`.
|
||||
- Refuse an unseeded `ItemManager` in a match (delete the clock default and pass the seed in).
|
||||
- Move the `Zone.cs:276` scatter onto `SimulationDice`.
|
||||
- Pin a two-ship AI-versus-AI digest test.
|
||||
- Run the same digest on Starfire and on Yggdrasil, and record the cross-host result.
|
||||
2. **Command seam.** Add `PilotCommand` and one applier in `Zone.Update`. Route Agent, Combat, MoveTo and
|
||||
ActionGameManager's direct writes through it, and delete their direct writes.
|
||||
3. **View seam.** Add a `PilotView` built from sensors and book. Brains lose their live `Ship`, `Zone` and `ItemManager`
|
||||
references. `Flight` and `Minion` become `DoctrineBrain` behind an `IBrain` registry. Aiming moves to the view if
|
||||
`observation-fidelity` rules for it.
|
||||
4. **Replay.** The recorder writes inputs, commands and digests every N ticks as CultCache. The replayer reproduces
|
||||
them and names the first divergent tick. Replay tests read an existing match.
|
||||
5. **Runner.** `AetherDb arena`: a scenario, two brain ids and a seed range produce results and replays in a given
|
||||
directory. It runs in Yggdrasil's dotnet container, with two-ship throughput measured and recorded.
|
||||
6. **First tournament.** Round-robin over two or more brains (`DoctrineBrain` plus one variant) and the combat
|
||||
scenarios, with enough seeds per pairing. Ratings are derived with confidence intervals, and the results document
|
||||
is ready to feed `aetheria-matchups`.
|
||||
|
|
@ -131,11 +131,11 @@ Cells not filled, plainly: whether a per-body multiplier on c or the core is eve
|
|||
| **Rock** (`Asteroid`) | (belt key, index) is the `ChunkId`. The index order is fixed at generation and never re-sorted, because wear (`ChunkWear`) is keyed by it. | Persisted (Distance, Phase, Size, RotationSpeed) about the belt's parent; wear and respawn live on Zone. | The generator draws a rock. Zone owns its wear and its pose (`AsteroidBelt.Pose`). |
|
||||
| **Period rule** | A property of the belt, not of the rock: the persisted flag `AsteroidBeltData.RidesRail` (MessagePack key 11, JSON `ridesRail`). False: each rock reads its own distance (`PeriodAround(parent, rock.Distance)`), an annulus. True: every rock takes its rail's period (`Orbits[rail].Period`), so a Trojan swarm keeps its shape (ruling `trojans-ride-host-period-with-stations`; on own-distance rails it shears within an hour, B60). | Persisted at generation. A belt saved before key 11 reads false, the annulus it was (CultCache defaults a missing slot, B61). | Zone's constructor alone writes `AsteroidBelt.Periods`, through PeriodAround or the rail's own Period; the flag only says which. Forbidden: a period formula in Pose, BeltTargets, DrawAhead, the renderer or the generator, and a second flag or shape enum read anywhere else. |
|
||||
| **Extent rule** | What a belt's rail adds to its parent's L_F: `AsteroidBelt.Extent(railDistance)`, the outermost rock for an annulus, the rail's own Distance for a swarm, whose rocks' periods never read their own distance. So a swarm moves no well (B61). An edge (Oort-like) belt must add nothing beyond the window, or the family's fade band narrows (B60). | Derived at zone build. | `AsteroidBelt.Extent`, read by Zone (the only WellNode builder in play) and by the station tests' rebuilt tree. The generator's clearance tree never sees a swarm, which is not a GeneratorPlanet. |
|
||||
| **Belt host** | A generator-time relation only. A Trojan host is a planet or gas giant (PlanetMass < mass <= SunMass) whose parent is a body, at a distance > 0. A ring member is never a host: its rail's pull adds its partners' (WellTree.PullAround), so a rail copied beside it would take another period and shear (B61). The swarm's rail copies the host's (Parent, Distance, Phase); that copy is all Zone needs, and nothing persists the host. A lane's host waits on laned-belts. | Lives for one GenerateZone call. | ZoneGenerator.GenerateZone's swarm pass. |
|
||||
| **Belt host** | A generator-time relation only. A Trojan host is a planet or gas giant (PlanetMass < mass <= SunMass) whose parent is a body, at a distance > 0. A ring member is never a host: its rail's pull adds its partners' (WellTree.PullAround), so a rail copied beside it would take another period and shear (B61). The swarm's rail copies the host's (Parent, Distance, Phase); that copy is all Zone needs, and nothing persists the host. A lane's host is a body that keeps its slot with room on both sides of the lane (cut laned-belts: half the eligible bodies, never a Trojan host, one shape per parent). | Lives for one GenerateZone call. | ZoneGenerator.GenerateZone's swarm pass. |
|
||||
| **Lagrange station orbit** | A bodyless OrbitData on a host's rail, 1/6 turn ahead of or behind it (CreateLagrangeOrbit). On a swarm host it sits inside the swarm, a mining outpost among the rocks (ruling `trojans-ride-host-period-with-stations`); rocks are on the floater layer below it, so nothing collides. | Created at generation and persisted. | ZoneGenerator's station placement, unchanged: candidacy reads the GeneratorPlanet tree, where no swarm exists, so no swarm can bar its host. |
|
||||
| **Belt generation settings** | `ZoneGenerationSettings` in Settings.asset (an LFS object): BeltProbability 0.25, BeltMassCeiling 500, AsteroidBeltWidth (a ceiling that never binds, B56), AsteroidCount = sqrt(mass x D) + 11. Trojans: TrojanProbability 0.15 per host, TrojanArc 0.07 turn per swarm, TrojanHalfWidth 0.06 D, TrojanCount = 0.04 D + 8 rocks round each of L4 and L5 (tuning defaults for a flight, B61). The code default of TrojanProbability is 0, so settings built in code draw no swarm. | Authored. | Settings.asset. Forbidden: a tunable shape's numbers as constants in code. The swarm's random stream salt is a ZoneGenerator constant, not a tunable. |
|
||||
|
||||
Cells not filled, plainly: the lane's host set and half-width (laned-belts' map proposes the visual margin, ruling `lanes-two-sided-visual-margin`); where an edge cloud's rocks sit between a root's window and the zone boundary (edge-material's map, rulings `edge-material-root-only` and `edge-material-in-the-dark`). The edge material's place in the potential is not a question: it stays out of the extent, because the law breaks otherwise (B60).
|
||||
Cells not filled, plainly: where an edge cloud's rocks sit between a root's window and the zone boundary (edge-material's map, rulings `edge-material-root-only` and `edge-material-in-the-dark`). The edge material's place in the potential is not a question: it stays out of the extent, because the law breaks otherwise (B60).
|
||||
|
||||
## Rationale
|
||||
|
||||
|
|
@ -180,5 +180,5 @@ Mapped and admitted (2026-10-14, base 11b2d7ad): `aetheria-gravity:cut_spec:cut-
|
|||
- **grid-noise-presentation**: the Sector Waves noise (after grid-lowering on layer 10, drawn only into the nebula patch height) and fine-grid detail move to non-slope presentation (grid-gravity-only-ruled); it also owns the Main Menu's own Surface Displacement brushes (B29, follow-up `menu-grid-brushes`). Waits on grid-lowering.
|
||||
- **top-speed-field**: top speed becomes a hull field; no drag below it, drag on the excess above it; the VelocityLimit clamp (Behaviors/VelocityLimit.cs) and always-on hull drag (Ship.cs:127-129) go; AI pilots brake actively; open: drag shape above the cap, consumables as modifiers, whether loot and wreckage come to rest (aetheria-gravity:ruling:no-drag-below-top-speed, target invariant orbits-do-not-decay). Waits on potential-owner and orbits-from-field.
|
||||
- **belt-shapes** (follow-up `belt-shapes`, ruling `belt-shapes-direction`; B60, B61; order by ruling `trojans-first`). One cut per shape. trojan-swarms is mapped and admitted (2026-10-18, base 67517f40): `aetheria-gravity:cut_spec:cut-trojan-swarms.r1`.
|
||||
- **laned-belts**: only a body with room on both sides of its lane keeps a laned belt, and the lane's half-width is its own visual margin, not the placement Roche margin (ruling `lanes-two-sided-visual-margin`; 468 eligible at the Roche width, B60). Folds in `belt-after-belt-edge-pin`, in the same Place loop.
|
||||
- **laned-belts** (mapped and admitted 2026-10-21, base d52b50f9: `aetheria-gravity:cut_spec:cut-laned-belts.r1`; lane half-width 5 body radii, a flight default): only a body with room on both sides of its lane keeps a laned belt, and the lane's half-width is its own visual margin, not the placement Roche margin (ruling `lanes-two-sided-visual-margin`; 468 eligible at the Roche width, B60). Folds in `belt-after-belt-edge-pin`, in the same Place loop.
|
||||
- **edge-material**: one sparse Oort-like cloud per root (per subzone root in a multi-root zone), from its family's window out toward the zone boundary, nothing at planets' rims (ruling `edge-material-root-only`); kept out of the extent (B60) and skipped by the populated radius, so its rocks sit in the dark fade (ruling `edge-material-in-the-dark`).
|
||||
|
|
|
|||
|
|
@ -11034,7 +11034,9 @@ presence for the design does not change when it bins a line.
|
|||
- Lot (unchanged schema): a binned lot records the binned product, and its fills and assembly sit at or above the
|
||||
floor of the line's distributions.
|
||||
- ProductWeight: the maker's preference times a share. A bin's share is its pass fraction `1 - Phi(Bin.Floor)`. A line's
|
||||
share is 1 minus the sum of the pass fractions of its bins, floored at 0. Any other product's share is 1. Two
|
||||
share is 1 minus the sum of the pass fractions of its offered bins, floored at 0, so an off-market bin takes nothing
|
||||
from its line (cut product-not-offered; a bin of an off-market line is itself off-market, ruling
|
||||
bin-inherits-line-offered). Any other product's share is 1. Two
|
||||
unbinned lines of one design and maker each weigh the full preference, as before. ProductWeight is still the one
|
||||
weight of RandomProducts and ChooseProduct.
|
||||
|
||||
|
|
@ -11238,7 +11240,7 @@ PIDs are in `S\out\pids.log`, the first 3404 and the last 20348. It then writes
|
|||
- **AH-P1. Checks.** All pass (`S\out\<id>\checks.json`).
|
||||
- Tender: 3468 triangles. The bbox is 10.5 x 23.4 x 5.9 m, inside its 18-28 m size class. Body h/w is 0.42 and w/l
|
||||
is 0.61, inside Zhestokost's ranges [0.25, 0.6] and [0.35, 0.75]. Bilateral symmetry has a worst mirror miss of
|
||||
0.0000 m. It has five mount anchors, and every weapon has a muzzle nose-ward of its mount.
|
||||
0.0000 m. It has five mount anchors, and every weapon mount has its muzzle (gun facing is HullData's, ruling gun-facing-is-hulldata).
|
||||
- Fighter: 1466 triangles and 15 m long. The donor body's h/w is 0.20 and w/l 0.69, inside the R&D ranges. The
|
||||
asymmetry rule holds.
|
||||
- The vertex-based asymmetry figure (0.91 unmirrored) is too lenient, because tessellation alone breaks vertex
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ public sealed class AssemblyFromMakerTests : IDisposable
|
|||
Roles = { new ItemRole { Name = "lens" } }
|
||||
});
|
||||
_lens = new CultRecordRef<CraftedItemData>(gear.Key);
|
||||
_items = new ItemManager(_cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
_items = new ItemManager(1u, _cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ public sealed class BrownoutTests : IDisposable
|
|||
|
||||
private Ship BuildShip(CultCache cache, float reactorCharge)
|
||||
{
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), Settings(), _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), Settings(), _ => { });
|
||||
var zone = new Zone(items, new PlanetSettings(), new ZonePack(), new GalaxyZone { Name = "Test Zone", Owner = null }, null);
|
||||
var hull = Mint(cache, items, cache.GetByName<HullData>("Skiff"));
|
||||
var ship = new Ship(items, zone, hull, new EntitySettings());
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ public sealed class ChildrenWithinReachTests : IClassFixture<ChildrenWithinReach
|
|||
authored.Read<SectorBackgroundSettings>("TutorialBackgroundSettings"),
|
||||
cache, new PlayerSettings(),
|
||||
Directory.CreateDirectory(Path.Combine(dir, "Narrative")), _ => { }, null, seed);
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), authored.Read<GameplaySettings>("GameplaySettings"), _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), authored.Read<GameplaySettings>("GameplaySettings"), _ => { });
|
||||
var planetSettings = authored.Read<PlanetSettings>("PlanetSettings");
|
||||
foreach (var galaxyZone in galaxy.Zones)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ public sealed class ConditionRatioTests : IDisposable
|
|||
|
||||
private Ship BuildShip(CultCache cache, float reactorCharge, out EquippedItem consumer)
|
||||
{
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), Settings(), _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), Settings(), _ => { });
|
||||
var zone = new Zone(items, new PlanetSettings(), new ZonePack(), new GalaxyZone { Name = "Test Zone", Owner = null }, null);
|
||||
var hull = Mint(cache, items, cache.GetByName<HullData>("Skiff"));
|
||||
var ship = new Ship(items, zone, hull, new EntitySettings());
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ public sealed class ConsumableAiTests : IDisposable
|
|||
settings.UnaidedPrecision = 2f;
|
||||
settings.AgentMinHitProbability = 0f;
|
||||
settings.BeamResolveInterval = .1f;
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), settings, _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), settings, _ => { });
|
||||
var maker = cache.RefOf(cache.GetByName<Faction>("Maker"));
|
||||
EquippableItem Make<T>(string name) where T : CraftedItemData =>
|
||||
(EquippableItem) items.CreateInstance(items.CreateLot(cache.GetByName<T>(name), maker, .5f));
|
||||
|
|
|
|||
|
|
@ -153,7 +153,7 @@ public sealed class ConsumableSupplyTests : IDisposable
|
|||
return new Rig
|
||||
{
|
||||
Cache = cache,
|
||||
Items = new ItemManager(cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { }),
|
||||
Items = new ItemManager(1u, cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { }),
|
||||
Galaxy = new Galaxy(cache, cache.GetGlobal<SavedGame>(), _ => { }),
|
||||
Maker = cache.GetByName<Faction>("Maker")
|
||||
};
|
||||
|
|
@ -281,7 +281,7 @@ public sealed class ConsumableSupplyTests : IDisposable
|
|||
var authored = AuthoredSettings.Load(repo);
|
||||
var galaxy = new Galaxy(authored.Read<TutorialGenerationSettings>("TutorialGenerationSettings"),
|
||||
authored.Read<SectorBackgroundSettings>("TutorialBackgroundSettings"), cache, new PlayerSettings(), Directory.CreateDirectory(Path.Combine(_root, "Narrative")), _ => { }, null, 1);
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), authored.Read<GameplaySettings>("GameplaySettings"), _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), authored.Read<GameplaySettings>("GameplaySettings"), _ => { });
|
||||
|
||||
int stations = 0, ships = 0;
|
||||
for (var i = 0; i < Seeds; i++)
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ public sealed class ConsumableTests : IDisposable
|
|||
var otherRef = cache.Upsert(new ConsumableItemData { Name = "Flare", Duration = 1f, Shape = new Shape() });
|
||||
cache.FlushAsync().Wait();
|
||||
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var makerRef = cache.RefOf(cache.GetByName<Faction>("Maker"));
|
||||
EquippableItem Gear(EquippableItemData design) => (EquippableItem) items.CreateInstance(items.CreateLot(design, makerRef, .5f));
|
||||
var zone = new Zone(items, new PlanetSettings(), new ZonePack(), new GalaxyZone { Name = "Test Zone", Owner = null }, null);
|
||||
|
|
|
|||
|
|
@ -173,7 +173,7 @@ public sealed partial class ContactRevealTests : IDisposable
|
|||
_openCache = AetheriaStores.Open(Catalog, catalogWritable: true);
|
||||
var ledger = new ProvenanceLedger();
|
||||
for (var lot = 1000; lot < 2000; lot++) ledger.Lots[lot] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
_items = new ItemManager(_openCache, ledger, new GameplaySettings
|
||||
_items = new ItemManager(1u, _openCache, ledger, new GameplaySettings
|
||||
{
|
||||
DefaultEntitySettings = new EntitySettings(),
|
||||
Tiers = new[] { new RarityTier { Name = "Common", Quality = .5f, Color = new float3(1, 1, 1) } },
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ public sealed class ContactTests : IDisposable
|
|||
|
||||
var ledger = new ProvenanceLedger();
|
||||
for (var i = 1; i <= 50; i++) ledger.Lots[i] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
var items = new ItemManager(cache, ledger, settings, _ => { });
|
||||
var items = new ItemManager(1u, cache, ledger, settings, _ => { });
|
||||
var zone = new Zone(items, new PlanetSettings(), new ZonePack { Radius = 0 }, new GalaxyZone { Name = "Contacts", Owner = null }, null);
|
||||
var lab = new Lab { Items = items, Zone = zone };
|
||||
zone.Contacted.Subscribe(lab.Events.Add);
|
||||
|
|
|
|||
244
tests/Aetheria.Shared.Tests/DeterminismLedgerTests.cs
Normal file
244
tests/Aetheria.Shared.Tests/DeterminismLedgerTests.cs
Normal file
|
|
@ -0,0 +1,244 @@
|
|||
/* 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 System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using CultMath;
|
||||
using UniRx;
|
||||
using Xunit;
|
||||
using static CultMath.math;
|
||||
using float2 = CultMath.float2;
|
||||
|
||||
// The determinism ledger (aetheria-arena, cut determinism-ledger): every seed a match reaches is stated, never the
|
||||
// clock's. The item stream is seeded by whoever builds the ItemManager and by RunStart at each step of a run; the unpack
|
||||
// scatter rides the zone's own dice (ruling run-seed-drops-only); a scenario has a stable Id; and one fight between two
|
||||
// piloted ships is one history, pinned for the host class that records replays.
|
||||
public sealed partial class RunStartTests
|
||||
{
|
||||
// The two-AI fight's digest, captured on Yggdrasil. Replays are trusted only on the host class that recorded them
|
||||
// (ruling replays-per-host-class), and arenas run on Linux.
|
||||
private const string TwoAiPinnedHostClass = "linux-x64";
|
||||
private const string TwoAiPinnedDigest = "8ba2116ed0ecd574703846f493c408936d536e3b7c9e4652556103ec1b75098c";
|
||||
private const int TwoAiSteps = 3600;
|
||||
|
||||
[Fact]
|
||||
public void ItemManagerRefusesSeedZero()
|
||||
{
|
||||
var refused = Assert.Throws<ArgumentOutOfRangeException>(() =>
|
||||
new ItemManager(0u, _cache, new ProvenanceLedger(), _items.GameplaySettings, _ => { }));
|
||||
Assert.Equal("seed", refused.ParamName);
|
||||
|
||||
var seeded = new ItemManager(0xBEEFu, _cache, new ProvenanceLedger(), _items.GameplaySettings, _ => { });
|
||||
Assert.Equal(new CultMath.Random(0xBEEFu).state, seeded.Random.state);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ItemSeedIsTheRunSeedAndStep()
|
||||
{
|
||||
var galaxy = Launch(new Scripted(false, stage => stage.Player(stage.Bare("Djinni"), float2(0, 0)), seed: 4242)).galaxy;
|
||||
Assert.Equal(4242u, galaxy.Seed);
|
||||
Assert.Equal(unchecked(4242u * 0x9E3779B1u + 1), RunStart.ItemSeed(galaxy, RunStart.ArenaStep));
|
||||
Assert.Equal(unchecked(4242u * 0x9E3779B1u + 2), RunStart.ItemSeed(galaxy, RunStart.StageStep));
|
||||
var arena = RunStart.ItemSeed(galaxy, RunStart.ArenaStep);
|
||||
var stage = RunStart.ItemSeed(galaxy, RunStart.StageStep);
|
||||
var resumed = RunStart.ItemSeed(galaxy, RunStart.ContinueStep);
|
||||
Assert.NotEqual(arena, resumed);
|
||||
Assert.NotEqual(stage, resumed);
|
||||
Assert.NotEqual(0u, RunStart.NoRunSeed);
|
||||
|
||||
// The stream a new run's arena generates on is the one ItemSeed names.
|
||||
var items = new ItemManager(7u, _cache, new ProvenanceLedger(), _items.GameplaySettings, _ => { });
|
||||
RunStart.GenerateArena(items, _zoneSettings, _planetSettings, galaxy, new Scripted(false, _ => { }, seed: 4242));
|
||||
var expected = new ItemManager(arena, _cache, new ProvenanceLedger(), _items.GameplaySettings, _ => { });
|
||||
RunStart.GenerateArena(expected, _zoneSettings, _planetSettings, galaxy, new Scripted(false, _ => { }, seed: 4242));
|
||||
Assert.Equal(expected.Random.state, items.Random.state);
|
||||
}
|
||||
|
||||
private const int OriginDrifters = 3;
|
||||
|
||||
// The galaxy seed whose ContinueStep item seed wraps to zero: -ContinueStep times the inverse of 0x9E3779B1, mod 2^32.
|
||||
private const uint ZeroAtContinueSeed = 0xD45E720Du;
|
||||
|
||||
[Fact]
|
||||
public void ItemSeedRefusesAZeroSeed()
|
||||
{
|
||||
var galaxy = Launch(new Scripted(false, stage => stage.Player(stage.Bare("Djinni"), float2(0, 0)),
|
||||
seed: ZeroAtContinueSeed)).galaxy;
|
||||
Assert.Equal(ZeroAtContinueSeed, galaxy.Seed);
|
||||
Assert.NotEqual(0u, RunStart.ItemSeed(galaxy, RunStart.ArenaStep));
|
||||
var refused = Assert.Throws<InvalidOperationException>(() => RunStart.ItemSeed(galaxy, RunStart.ContinueStep));
|
||||
Assert.Contains("zero", refused.Message);
|
||||
}
|
||||
|
||||
// A pack holding three unpiloted ships at the origin besides the player, from a staged arena. More than one, so
|
||||
// each ship's own ordinal is visible: ships sharing a draw would stack on one point.
|
||||
private (ZonePack pack, Galaxy galaxy) OriginPack()
|
||||
{
|
||||
var drifters = new List<Ship>();
|
||||
var (galaxy, arena, staged, failures) = Launch(new Scripted(false, stage =>
|
||||
{
|
||||
stage.Player(stage.Bare("Djinni"), float2(-50000, -50000));
|
||||
for (var i = 0; i < OriginDrifters; i++)
|
||||
drifters.Add(stage.Place(stage.Bare("Longinus"), float2(0, 0)) as Ship);
|
||||
}));
|
||||
Assert.True(failures.Count == 0, string.Join("; ", failures));
|
||||
Assert.All(drifters, drifter =>
|
||||
{
|
||||
Assert.NotNull(drifter);
|
||||
Assert.False(drifter.IsPlayerShip);
|
||||
Assert.True(lengthsq(drifter.Position) < 1, "the fixture's drifters are packed at the origin");
|
||||
});
|
||||
return (arena.PackZone(), galaxy);
|
||||
}
|
||||
|
||||
private List<CultMath.float3> Scattered(ZonePack pack, GalaxyZone galaxyZone, Galaxy galaxy)
|
||||
{
|
||||
var zone = new Zone(_items, _planetSettings, pack, galaxyZone, galaxy);
|
||||
return zone.Entities.OfType<Ship>().Where(ship => !ship.IsPlayerShip).Select(ship => ship.Position).ToList();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScatterIgnoresTheItemStream()
|
||||
{
|
||||
var (pack, galaxy) = OriginPack();
|
||||
_items.Random = new CultMath.Random(11);
|
||||
var first = Scattered(pack, galaxy.Entrance, galaxy);
|
||||
_items.Random = new CultMath.Random(12);
|
||||
for (var i = 0; i < 37; i++) _items.Random.NextUInt();
|
||||
var second = Scattered(pack, galaxy.Entrance, galaxy);
|
||||
|
||||
Assert.NotEmpty(first);
|
||||
Assert.All(first, position => Assert.True(lengthsq(position) >= 1, "the drifter was scattered"));
|
||||
Assert.Equal(first, second);
|
||||
}
|
||||
|
||||
// Each origin ship is placed by its own draw: the 1-based ordinal of its entry in the pack, on the zone's
|
||||
// ScatterStream, within half the zone's radius of the centre on every axis.
|
||||
[Fact]
|
||||
public void ScatterDrawsEachShipsOrdinalWithinHalfTheRadius()
|
||||
{
|
||||
var (pack, galaxy) = OriginPack();
|
||||
var zone = new Zone(_items, _planetSettings, pack, galaxy.Entrance, galaxy);
|
||||
Assert.Equal(pack.Entities.Count, zone.Entities.Count);
|
||||
|
||||
var half = pack.Radius * .5f;
|
||||
var expected = new List<CultMath.float3>();
|
||||
var actual = new List<CultMath.float3>();
|
||||
for (var i = 0; i < zone.Entities.Count; i++)
|
||||
{
|
||||
if (zone.Entities[i] is not Ship {IsPlayerShip: false} ship) continue;
|
||||
expected.Add(SimulationDice.For(zone.CombatSeed, SimulationDice.ScatterStream, (uint) i + 1)
|
||||
.NextFloat3(float3(-half), float3(half)));
|
||||
actual.Add(ship.Position);
|
||||
}
|
||||
|
||||
Assert.Equal(OriginDrifters, actual.Count);
|
||||
Assert.Equal(expected, actual);
|
||||
Assert.Equal(OriginDrifters, actual.Distinct().Count());
|
||||
Assert.All(actual, position => Assert.True(all(abs(position) <= half), $"{position} is within half the radius {half}"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScatterIsPerZone()
|
||||
{
|
||||
var (pack, galaxy) = OriginPack();
|
||||
var other = galaxy.Zones.First(zone => zone != galaxy.Entrance && zone.Name != galaxy.Entrance.Name);
|
||||
var here = Scattered(pack, galaxy.Entrance, galaxy);
|
||||
var there = Scattered(pack, other, galaxy);
|
||||
Assert.NotEmpty(here);
|
||||
Assert.Equal(here.Count, there.Count);
|
||||
Assert.NotEqual(here, there);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SimulationDiceStreamsAreDistinct()
|
||||
{
|
||||
var streams = new[]
|
||||
{
|
||||
SimulationDice.LootStream, SimulationDice.MineStream, SimulationDice.CombatSampleStream,
|
||||
SimulationDice.PatrolStream, SimulationDice.ScatterStream
|
||||
};
|
||||
Assert.Equal(streams.Length, streams.Distinct().Count());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScenarioIdsAreUniqueKebabAndStable()
|
||||
{
|
||||
var kebab = new Regex("^[a-z0-9]+(-[a-z0-9]+)*$");
|
||||
var every = EveryScenario.ToList();
|
||||
Assert.All(every, scenario => Assert.True(scenario.Id != null && kebab.IsMatch(scenario.Id),
|
||||
$"{scenario.GetType().Name}: Id is not lowercase kebab"));
|
||||
Assert.Equal(every.Count, every.Select(scenario => scenario.Id).Distinct().Count());
|
||||
Assert.Equal("duel", new Duel().Id);
|
||||
Assert.Equal("demo-terminus", new DemoTerminus().Id);
|
||||
}
|
||||
|
||||
// Two piloted fighters about 900 apart, each approaching the other, hostile both ways; the player bare, unpiloted
|
||||
// and far from the fight. Steps 3600 times at 1/60 s and returns the SHA-256 of the joined snapshots.
|
||||
private (string digest, bool bothPiloted, int hits, bool hullLost) TwoAiFight()
|
||||
{
|
||||
Ship east = null, west = null;
|
||||
var (_, arena, _, failures) = Launch(new Scripted(false, stage =>
|
||||
{
|
||||
stage.Player(stage.Bare("Djinni"), float2(-50000, -50000));
|
||||
east = stage.Place(EvLonginus(stage), float2(450, 0), facing: float2(-1, 0), piloted: true) as Ship;
|
||||
west = stage.Place(EvLonginus(stage), float2(-450, 0), facing: float2(1, 0), piloted: true) as Ship;
|
||||
stage.Approach(east, west);
|
||||
stage.Approach(west, east);
|
||||
}));
|
||||
Assert.True(failures.Count == 0, string.Join("; ", failures));
|
||||
east.SetIff(west, true);
|
||||
west.SetIff(east, true);
|
||||
var hullAtStart = east.Hull.Durability + west.Hull.Durability;
|
||||
var bothPiloted = arena.Agents.Any(agent => agent.Ship == east) && arena.Agents.Any(agent => agent.Ship == west);
|
||||
|
||||
var shots = new List<string>();
|
||||
var hits = 0;
|
||||
using var _ = arena.ShotResolved.Subscribe(outcome =>
|
||||
{
|
||||
shots.Add($"{outcome.ShotId}:{outcome.Result}");
|
||||
if (outcome.Hit) hits++;
|
||||
});
|
||||
var history = new StringBuilder();
|
||||
for (var step = 0; step < TwoAiSteps; step++)
|
||||
{
|
||||
arena.Update(1f / 60f);
|
||||
history.Append(Snapshot(arena, shots)).Append('\n');
|
||||
}
|
||||
var digest = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(history.ToString()))).ToLowerInvariant();
|
||||
return (digest, bothPiloted, hits, east.Hull.Durability + west.Hull.Durability < hullAtStart);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TwoAiFightIsOneHistory()
|
||||
{
|
||||
var first = TwoAiFight();
|
||||
var second = TwoAiFight();
|
||||
Assert.True(first.bothPiloted, "both fighters are piloted");
|
||||
Assert.True(first.hits > 0, "at least one shot resolved as a hit");
|
||||
Assert.True(first.hullLost, "at least one fighter lost hull");
|
||||
Assert.Equal(first.digest, second.digest);
|
||||
}
|
||||
|
||||
private static string HostClass() =>
|
||||
(RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "linux"
|
||||
: RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "windows"
|
||||
: RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "osx" : "other")
|
||||
+ "-" + RuntimeInformation.ProcessArchitecture.ToString().ToLowerInvariant();
|
||||
|
||||
[Fact]
|
||||
public void TwoAiDigestIsPinned()
|
||||
{
|
||||
var host = HostClass();
|
||||
var digest = TwoAiFight().digest;
|
||||
Assert.True(host == TwoAiPinnedHostClass,
|
||||
$"TWO-AI-DIGEST host class {host} has no pin (pinned: {TwoAiPinnedHostClass}); measured digest {digest}");
|
||||
Assert.True(digest == TwoAiPinnedDigest, $"TWO-AI-DIGEST {host} measured {digest}, pinned {TwoAiPinnedDigest}");
|
||||
}
|
||||
}
|
||||
|
|
@ -84,7 +84,7 @@ public sealed class DrawnPoseTests : IDisposable
|
|||
_caches.Add(cache);
|
||||
var ledger = new ProvenanceLedger();
|
||||
ledger.Lots[1] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
var items = new ItemManager(cache, ledger, new GameplaySettings
|
||||
var items = new ItemManager(1u, cache, ledger, new GameplaySettings
|
||||
{
|
||||
DefaultEntitySettings = new EntitySettings(),
|
||||
Tiers = new[] { new RarityTier { Name = "Common", Quality = .5f, Color = new float3(1, 1, 1) } },
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@ public sealed class FireAuthorityTests : IDisposable
|
|||
ledger.Lots[3] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
ledger.Lots[4] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
for (var i = 5; i <= 12; i++) ledger.Lots[i] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
var items = new ItemManager(cache, ledger, settings, _ => { });
|
||||
var items = new ItemManager(1u, cache, ledger, settings, _ => { });
|
||||
var zone = new Zone(items, new PlanetSettings(), new ZonePack(), new GalaxyZone { Name = "Test", Owner = null }, null);
|
||||
|
||||
var hullRef = items.ItemData.RefOf<ItemData>(items.ItemData.GetByName<HullData>("Hull"));
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ public sealed class FireControlCut11Tests : IDisposable
|
|||
|
||||
var ledger = new ProvenanceLedger();
|
||||
for (var i = 1; i <= 16; i++) ledger.Lots[i] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
var items = new ItemManager(cache, ledger, TestSettings(schematicCellSize), _ => { });
|
||||
var items = new ItemManager(1u, cache, ledger, TestSettings(schematicCellSize), _ => { });
|
||||
var zone = new Zone(items, new PlanetSettings(), new ZonePack(), new GalaxyZone { Name = zoneName, Owner = null }, null);
|
||||
|
||||
EquippableItem Make(string name, int lot, float durability) => new EquippableItem
|
||||
|
|
|
|||
|
|
@ -160,7 +160,7 @@ public sealed class FireControlCut123Tests : IDisposable
|
|||
|
||||
var ledger = new ProvenanceLedger();
|
||||
for (var i = 1; i <= 500; i++) ledger.Lots[i] = new Lot { Origin = new Attributed(), Quality = 1 }; // F8: shared-item fixtures equip more items (bars) than earlier cuts needed
|
||||
var items = new ItemManager(cache, ledger, settings, _ => { });
|
||||
var items = new ItemManager(1u, cache, ledger, settings, _ => { });
|
||||
var zone = new Zone(items, new PlanetSettings(), new ZonePack(), new GalaxyZone { Name = "Cut123", Owner = null }, null);
|
||||
|
||||
EquippableItem Make(string name, int lot, float durability) => new EquippableItem
|
||||
|
|
|
|||
|
|
@ -188,7 +188,7 @@ public sealed partial class FireControlCut124Tests : IDisposable
|
|||
|
||||
var ledger = new ProvenanceLedger();
|
||||
for (var i = 1; i <= 500; i++) ledger.Lots[i] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
var items = new ItemManager(cache, ledger, settings, _ => { });
|
||||
var items = new ItemManager(1u, cache, ledger, settings, _ => { });
|
||||
var zone = new Zone(items, new PlanetSettings(), new ZonePack(), new GalaxyZone { Name = "Cut124", Owner = null }, null);
|
||||
|
||||
EquippableItem Make(string name, int lot, float durability) => new EquippableItem
|
||||
|
|
|
|||
|
|
@ -192,7 +192,7 @@ public sealed class FireControlCut12Tests : IDisposable
|
|||
|
||||
var ledger = new ProvenanceLedger();
|
||||
for (var i = 1; i <= 24; i++) ledger.Lots[i] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
var items = new ItemManager(cache, ledger, settings, _ => { });
|
||||
var items = new ItemManager(1u, cache, ledger, settings, _ => { });
|
||||
// Cut 6.1's own convention (FireControlCut6Tests.cs:62-63): every engagement shares one fixed
|
||||
// GalaxyZone name, so every Zone this fixture builds shares one CombatSeed and every statistical test
|
||||
// is replayable byte-for-byte across runs. A GUID-suffixed name here (this file's own earlier defect)
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ public sealed class FireControlCut4Tests : IDisposable
|
|||
ledger.Lots[4] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
ledger.Lots[5] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
ledger.Lots[9] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
var items = new ItemManager(cache, ledger, settings, _ => { });
|
||||
var items = new ItemManager(1u, cache, ledger, settings, _ => { });
|
||||
var zone = new Zone(items, new PlanetSettings(), new ZonePack(), new GalaxyZone { Name = "Test", Owner = null }, null);
|
||||
|
||||
var hullRef = items.ItemData.RefOf<ItemData>(items.ItemData.GetByName<HullData>("Hull"));
|
||||
|
|
@ -218,7 +218,7 @@ public sealed class FireControlCut4Tests : IDisposable
|
|||
cache.FlushAsync().Wait();
|
||||
|
||||
var ledger = new ProvenanceLedger { Lots = { [1] = new Lot { Origin = new Attributed(), Quality = 1 } } };
|
||||
var items = new ItemManager(cache, ledger, TestSettings(), _ => { });
|
||||
var items = new ItemManager(1u, cache, ledger, TestSettings(), _ => { });
|
||||
var zone = new Zone(items, new PlanetSettings(), new ZonePack(), new GalaxyZone { Name = "Test", Owner = null }, null);
|
||||
|
||||
var hullRef = items.ItemData.RefOf<ItemData>(items.ItemData.GetByName<HullData>("Hull"));
|
||||
|
|
@ -258,7 +258,7 @@ public sealed class FireControlCut4Tests : IDisposable
|
|||
cache.FlushAsync().Wait();
|
||||
|
||||
_splashLedger = new ProvenanceLedger();
|
||||
var items = new ItemManager(cache, _splashLedger, settings, _ => { });
|
||||
var items = new ItemManager(1u, cache, _splashLedger, settings, _ => { });
|
||||
var zone = new Zone(items, new PlanetSettings(), new ZonePack(), new GalaxyZone { Name = "Test", Owner = null }, null);
|
||||
return (items, zone);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ public sealed class FireControlCut5Tests : IDisposable
|
|||
ledger.Lots[3] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
ledger.Lots[4] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
for (var i = 5; i <= 12; i++) ledger.Lots[i] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
var items = new ItemManager(cache, ledger, settings, _ => { });
|
||||
var items = new ItemManager(1u, cache, ledger, settings, _ => { });
|
||||
var zone = new Zone(items, new PlanetSettings(), new ZonePack(), new GalaxyZone { Name = "Test", Owner = null }, null);
|
||||
|
||||
var hullRef = items.ItemData.RefOf<ItemData>(items.ItemData.GetByName<HullData>("Hull"));
|
||||
|
|
@ -310,7 +310,7 @@ public sealed class FireControlCut5Tests : IDisposable
|
|||
ledger.Lots[3] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
ledger.Lots[4] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
ledger.Lots[5] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
var items = new ItemManager(cache, ledger, settings, _ => { });
|
||||
var items = new ItemManager(1u, cache, ledger, settings, _ => { });
|
||||
var zone = new Zone(items, new PlanetSettings(), new ZonePack(), new GalaxyZone { Name = "Test", Owner = null }, null);
|
||||
|
||||
var hullRef = items.ItemData.RefOf<ItemData>(items.ItemData.GetByName<HullData>("Hull"));
|
||||
|
|
@ -406,7 +406,7 @@ public sealed class FireControlCut5Tests : IDisposable
|
|||
ledger.Lots[3] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
ledger.Lots[4] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
ledger.Lots[5] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
var items = new ItemManager(cache, ledger, settings, _ => { });
|
||||
var items = new ItemManager(1u, cache, ledger, settings, _ => { });
|
||||
var zone = new Zone(items, new PlanetSettings(), new ZonePack(), new GalaxyZone { Name = "Test", Owner = null }, null);
|
||||
|
||||
var hullRef = items.ItemData.RefOf<ItemData>(items.ItemData.GetByName<HullData>("Hull"));
|
||||
|
|
@ -549,7 +549,7 @@ public sealed class FireControlCut5Tests : IDisposable
|
|||
ledger.Lots[1] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
ledger.Lots[2] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
ledger.Lots[3] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
var items = new ItemManager(cache, ledger, settings, _ => { });
|
||||
var items = new ItemManager(1u, cache, ledger, settings, _ => { });
|
||||
var zone = new Zone(items, new PlanetSettings(), new ZonePack(), new GalaxyZone { Name = "Test", Owner = null }, null);
|
||||
|
||||
var hullRef = items.ItemData.RefOf<ItemData>(items.ItemData.GetByName<HullData>("Hull"));
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ public sealed class FireControlCut6Tests : IDisposable
|
|||
ledger.Lots[3] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
ledger.Lots[4] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
ledger.Lots[5] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
var items = new ItemManager(cache, ledger, settings, _ => { });
|
||||
var items = new ItemManager(1u, cache, ledger, settings, _ => { });
|
||||
var zone = new Zone(items, new PlanetSettings(), new ZonePack(), new GalaxyZone { Name = "Test", Owner = null }, null);
|
||||
|
||||
var hullRef = items.ItemData.RefOf<ItemData>(items.ItemData.GetByName<HullData>("Hull"));
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ public sealed class FireControlCut6cTests : IDisposable
|
|||
ledger.Lots[3] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
ledger.Lots[4] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
for (var i = 5; i <= 12; i++) ledger.Lots[i] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
var items = new ItemManager(cache, ledger, settings, _ => { });
|
||||
var items = new ItemManager(1u, cache, ledger, settings, _ => { });
|
||||
var zone = new Zone(items, new PlanetSettings(), new ZonePack(), new GalaxyZone { Name = "Test", Owner = null }, null);
|
||||
|
||||
var hullRef = items.ItemData.RefOf<ItemData>(items.ItemData.GetByName<HullData>("Hull"));
|
||||
|
|
@ -254,7 +254,7 @@ public sealed class FireControlCut6cTests : IDisposable
|
|||
var random = new Random(1u);
|
||||
var settings = TestSettings();
|
||||
var log = new List<string>();
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), settings, log.Add);
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), settings, log.Add);
|
||||
var generator = new LoadoutGenerator(ref random, items, galaxy, null, makerFaction, .5f);
|
||||
|
||||
var pack = generator.GenerateShipLoadout(candidate => candidate.Name == "Armed");
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ public sealed class FireControlCut6dTests : IDisposable
|
|||
|
||||
var ledger = new ProvenanceLedger();
|
||||
for (var i = 1; i <= 20; i++) ledger.Lots[i] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
var items = new ItemManager(cache, ledger, settings, _ => { });
|
||||
var items = new ItemManager(1u, cache, ledger, settings, _ => { });
|
||||
var zone = new Zone(items, new PlanetSettings(), new ZonePack(), new GalaxyZone { Name = "Test", Owner = null }, null);
|
||||
|
||||
// Durability high enough to survive thousands of direct 10-damage hits over a test's whole sample --
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ public sealed class FireControlCut7Tests : IDisposable
|
|||
|
||||
var ledger = new ProvenanceLedger();
|
||||
for (var i = 1; i <= 10; i++) ledger.Lots[i] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
var items = new ItemManager(cache, ledger, settings, _ => { });
|
||||
var items = new ItemManager(1u, cache, ledger, settings, _ => { });
|
||||
var zone = new Zone(items, new PlanetSettings(), new ZonePack(), new GalaxyZone { Name = "Test", Owner = null }, null);
|
||||
|
||||
var hullRef = items.ItemData.RefOf<ItemData>(items.ItemData.GetByName<HullData>("Hull"));
|
||||
|
|
@ -214,7 +214,7 @@ public sealed class FireControlCut7Tests : IDisposable
|
|||
QualityPriceModifier = new ExponentialLerp()
|
||||
};
|
||||
var log = new List<string>();
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), settings, log.Add);
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), settings, log.Add);
|
||||
var random = new Random(1u);
|
||||
var generator = new LoadoutGenerator(ref random, items, null, null, null, .5f);
|
||||
var pack = generator.GenerateShipLoadout(candidate => candidate == hull);
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ public sealed class FireControlCut8Tests : IDisposable
|
|||
ledger.Lots[3] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
ledger.Lots[4] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
ledger.Lots[5] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
var items = new ItemManager(cache, ledger, settings, _ => { });
|
||||
var items = new ItemManager(1u, cache, ledger, settings, _ => { });
|
||||
var zone = new Zone(items, new PlanetSettings(), new ZonePack(), new GalaxyZone { Name = "Test", Owner = null }, null);
|
||||
|
||||
var hullRef = items.ItemData.RefOf<ItemData>(items.ItemData.GetByName<HullData>("Hull"));
|
||||
|
|
@ -195,7 +195,7 @@ public sealed class FireControlCut8Tests : IDisposable
|
|||
ledger.Lots[2] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
ledger.Lots[3] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
ledger.Lots[4] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
var items = new ItemManager(cache, ledger, settings, _ => { });
|
||||
var items = new ItemManager(1u, cache, ledger, settings, _ => { });
|
||||
var zone = new Zone(items, new PlanetSettings(), new ZonePack(), new GalaxyZone { Name = "Test", Owner = null }, null);
|
||||
|
||||
var hullRef = items.ItemData.RefOf<ItemData>(items.ItemData.GetByName<HullData>("Hull"));
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ public sealed class FireControlCut9Tests : IDisposable
|
|||
ledger.Lots[2] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
ledger.Lots[3] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
ledger.Lots[4] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
var items = new ItemManager(cache, ledger, settings, _ => { });
|
||||
var items = new ItemManager(1u, cache, ledger, settings, _ => { });
|
||||
var zone = new Zone(items, new PlanetSettings(), new ZonePack(), new GalaxyZone { Name = zoneName, Owner = null }, null);
|
||||
|
||||
var hullRef = items.ItemData.RefOf<ItemData>(items.ItemData.GetByName<HullData>("Hull"));
|
||||
|
|
@ -312,7 +312,7 @@ public sealed class FireControlCut9Tests : IDisposable
|
|||
|
||||
var ledger = new ProvenanceLedger();
|
||||
for (var i = 1; i <= 5; i++) ledger.Lots[i] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
var items = new ItemManager(cache, ledger, settings, _ => { });
|
||||
var items = new ItemManager(1u, cache, ledger, settings, _ => { });
|
||||
var zone = new Zone(items, new PlanetSettings(), new ZonePack(), new GalaxyZone { Name = "Cut9-Kernel", Owner = null }, null);
|
||||
|
||||
var hullRef = items.ItemData.RefOf<ItemData>(items.ItemData.GetByName<HullData>("Hull"));
|
||||
|
|
@ -543,7 +543,7 @@ public sealed class FireControlCut9Tests : IDisposable
|
|||
ledger.Lots[2] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
ledger.Lots[3] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
ledger.Lots[4] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
var items = new ItemManager(cache, ledger, settings, _ => { });
|
||||
var items = new ItemManager(1u, cache, ledger, settings, _ => { });
|
||||
var zone = new Zone(items, new PlanetSettings(), new ZonePack(), new GalaxyZone { Name = "Cut9-Lock", Owner = null }, null);
|
||||
|
||||
var hullRef = items.ItemData.RefOf<ItemData>(items.ItemData.GetByName<HullData>("Hull"));
|
||||
|
|
|
|||
|
|
@ -193,7 +193,7 @@ public sealed class FireControlPerWeaponTests : IDisposable
|
|||
|
||||
var ledger = new ProvenanceLedger();
|
||||
for (var i = 1; i <= 100; i++) ledger.Lots[i] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
var items = new ItemManager(cache, ledger, TestSettings(minHit, targetLayerHeight), _ => { });
|
||||
var items = new ItemManager(1u, cache, ledger, TestSettings(minHit, targetLayerHeight), _ => { });
|
||||
var zone = new Zone(items, new PlanetSettings(), new ZonePack(), new GalaxyZone { Name = "PerWeapon", Owner = null }, null);
|
||||
|
||||
EquippableItem Make(string name, int lot, float durability) => new EquippableItem
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ public sealed class FireControlTests : IDisposable
|
|||
ledger.Lots[1] = new Lot { Origin = new Attributed(), Quality = 1 }; // hull
|
||||
for (var i = 0; i < hardpoints.Length; i++)
|
||||
ledger.Lots[2 + i] = new Lot { Origin = new Attributed(), Quality = 1 }; // guns
|
||||
var items = new ItemManager(_openCache, ledger, TestSettings(shipLayerHeight), _ => { });
|
||||
var items = new ItemManager(1u, _openCache, ledger, TestSettings(shipLayerHeight), _ => { });
|
||||
var planets = new PlanetSettings();
|
||||
var pack = new ZonePack();
|
||||
if (well) WellFixture.Dig(_openCache, pack, planets, 300f, 100f);
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ public sealed class FloatingBodyTests : IDisposable
|
|||
Tiers = new[] { new RarityTier { Name = "Common", Quality = .5f, Color = new float3(1, 1, 1) } },
|
||||
QualityPriceModifier = new ExponentialLerp()
|
||||
};
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), settings, _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), settings, _ => { });
|
||||
return new Zone(items, new PlanetSettings(), new ZonePack(), new GalaxyZone { Name = "Test Zone", Owner = null }, null);
|
||||
}
|
||||
|
||||
|
|
@ -206,7 +206,7 @@ public sealed class FloatingBodyTests : IDisposable
|
|||
Tiers = new[] { new RarityTier { Name = "Common", Quality = .5f, Color = new float3(1, 1, 1) } },
|
||||
QualityPriceModifier = new ExponentialLerp()
|
||||
};
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), settings, _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), settings, _ => { });
|
||||
var planets = new PlanetSettings { HeightExaggeration = 10f }; // the tilt reads force / K, which K = 1 would hide
|
||||
var pack = new ZonePack { Radius = 1000 };
|
||||
WellFixture.Dig(cache, pack, planets, 300, 100);
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ public sealed class FlowFieldTests : IDisposable
|
|||
TargetDetectionInfoThreshold = .1f, TargetGearInfoThreshold = .8f,
|
||||
FiringArc = 170, CommitHorizon = .5f, SchematicCellSize = 2f
|
||||
};
|
||||
_items = new ItemManager(_cache, new ProvenanceLedger(), settings, _ => { });
|
||||
_items = new ItemManager(1u, _cache, new ProvenanceLedger(), settings, _ => { });
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ public sealed class GravitySubtractTests : IDisposable
|
|||
TargetDetectionInfoThreshold = .1f, TargetGearInfoThreshold = .8f,
|
||||
FiringArc = 170, CommitHorizon = .5f, SchematicCellSize = 2f
|
||||
};
|
||||
_items = new ItemManager(_cache, new ProvenanceLedger(), settings, _ => { });
|
||||
_items = new ItemManager(1u, _cache, new ProvenanceLedger(), settings, _ => { });
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
|
|
|||
|
|
@ -233,7 +233,7 @@ public sealed class HeatResponseTests : IDisposable
|
|||
public void HeldInsidePlateauTakesNoThermalWear()
|
||||
{
|
||||
using var cache = OpenCatalogWithThermalGear();
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), Settings(), _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), Settings(), _ => { });
|
||||
var equipped = BuildEquippedAt(cache, items, 50f, out var ship);
|
||||
Assert.Equal(1f, equipped.ThermalPerformance, 5);
|
||||
Assert.Equal(0f, equipped.Wear, 5); // no thermal term (Performance == 1) and no deltaTemp (settled)
|
||||
|
|
@ -243,7 +243,7 @@ public sealed class HeatResponseTests : IDisposable
|
|||
public void HeldOutsidePlateauTakesThermalWear()
|
||||
{
|
||||
using var cache = OpenCatalogWithThermalGear();
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), Settings(), _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), Settings(), _ => { });
|
||||
var equipped = BuildEquippedAt(cache, items, 10f, out var ship); // outside 40-60, inside 0-100
|
||||
Assert.True(equipped.ThermalPerformance < 1f);
|
||||
Assert.True(equipped.Wear > 0f);
|
||||
|
|
@ -255,7 +255,7 @@ public sealed class HeatResponseTests : IDisposable
|
|||
public void HeldOutsidePlateauWearReadsEffectiveQuality()
|
||||
{
|
||||
using var cache = OpenCatalogWithThermalGear();
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), Settings(), _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), Settings(), _ => { });
|
||||
var thermal = cache.RefOf<ItemData>(cache.GetByName<GearData>("Thermal"));
|
||||
Lot Gear(float assembly, params float[] parts) => new Lot
|
||||
{
|
||||
|
|
@ -274,7 +274,7 @@ public sealed class HeatResponseTests : IDisposable
|
|||
public void AFastSwingAcrossThePlateauStillWears()
|
||||
{
|
||||
using var cache = OpenCatalogWithThermalGear();
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), Settings(), _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), Settings(), _ => { });
|
||||
var equipped = BuildEquippedAt(cache, items, 10f, out var ship);
|
||||
// Jump straight into the plateau in one tick: ThermalPerformance ends at 1 (no thermal term), but the
|
||||
// swing itself (deltaTemp) still costs.
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ public sealed class IffAndCombatTests : IDisposable
|
|||
var ledger = new ProvenanceLedger();
|
||||
ledger.Lots[1] = new Lot { Origin = new Attributed(), Quality = 1 }; // hull
|
||||
ledger.Lots[2] = new Lot { Origin = new Attributed(), Quality = 1 }; // gun
|
||||
var items = new ItemManager(_openCache, ledger, TestSettings(), _ => { });
|
||||
var items = new ItemManager(1u, _openCache, ledger, TestSettings(), _ => { });
|
||||
var zone = new Zone(items, new PlanetSettings(), new ZonePack(), new GalaxyZone { Name = "Test Zone", Owner = null }, null);
|
||||
return (items, zone);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ public sealed class InputCapacitorTests : IDisposable
|
|||
private (Ship ship, ItemManager items, Reactor reactor) BuildShip(CultCache cache, float reactorCharge)
|
||||
{
|
||||
var ledger = new ProvenanceLedger();
|
||||
var items = new ItemManager(cache, ledger, Settings(), _ => { });
|
||||
var items = new ItemManager(1u, cache, ledger, Settings(), _ => { });
|
||||
var zone = new Zone(items, new PlanetSettings(), new ZonePack(), new GalaxyZone { Name = "Test Zone", Owner = null }, null);
|
||||
var hull = Mint(cache, items, cache.GetByName<HullData>("Skiff"));
|
||||
var ship = new Ship(items, zone, hull, new EntitySettings());
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ public sealed class InspectionScannerCatalogTests
|
|||
Origin = new Attributed(), Quality = 1f,
|
||||
Roles = product.Roles.Select(r => new RoleFill { Role = r.Role, Quality = r.Mean }).ToList(),
|
||||
};
|
||||
var items = new ItemManager(Cache, ledger, settings, _ => { });
|
||||
var items = new ItemManager(1u, Cache, ledger, settings, _ => { });
|
||||
var zone = new Zone(items, new PlanetSettings(), new ZonePack(), new GalaxyZone { Name = "Test", Owner = null }, null);
|
||||
var ship = new Ship(items, zone, new EquippableItem { Data = Cache.RefOf<ItemData>(hull), Durability = hull.Durability, Lot = 1 }, settings.DefaultEntitySettings);
|
||||
Assert.True(ship.TryEquip(new EquippableItem { Data = Cache.RefOf<ItemData>(design), Durability = design.Durability, Lot = 2 }));
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ public sealed class ItemTitleTests : IDisposable
|
|||
_cache.Commit(batch => batch.Upsert(typeof(FactionProductData), _clapBack, new CultRecordKey("array-lucent")));
|
||||
_spiceBlend = new FactionProductData { Name = "Finch Reserve", Design = _cache.RefOf<CraftedItemData>(_spice), Manufacturer = finch };
|
||||
_cache.Commit(batch => batch.Upsert(typeof(FactionProductData), _spiceBlend, new CultRecordKey("spice-finch")));
|
||||
_items = new ItemManager(_cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
_items = new ItemManager(1u, _cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ public sealed class LaunchSuppressorCatalogTests
|
|||
Origin = new Attributed(), Quality = 1f,
|
||||
Roles = new List<RoleFill> { new RoleFill { Role = "baffles", Quality = baffles }, new RoleFill { Role = "heat sink", Quality = sink } },
|
||||
};
|
||||
var items = new ItemManager(Cache, ledger, settings, _ => { });
|
||||
var items = new ItemManager(1u, Cache, ledger, settings, _ => { });
|
||||
var zone = new Zone(items, new PlanetSettings(), new ZonePack(), new GalaxyZone { Name = "Test", Owner = null }, null);
|
||||
Ship = new Ship(items, zone, new EquippableItem { Data = Cache.RefOf<ItemData>(hull), Durability = hull.Durability, Lot = 1 }, settings.DefaultEntitySettings);
|
||||
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ public sealed class LayerTests : IDisposable
|
|||
_cache.FlushAsync().Wait();
|
||||
var ledger = new ProvenanceLedger();
|
||||
for (var i = 1; i <= 3; i++) ledger.Lots[i] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
var items = new ItemManager(_cache, ledger, Settings(), _ => { });
|
||||
var items = new ItemManager(1u, _cache, ledger, Settings(), _ => { });
|
||||
var zone = new Zone(items, new PlanetSettings(), new ZonePack { Radius = 0 }, new GalaxyZone { Name = "Layers", Owner = null }, null);
|
||||
return (items, zone);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ public sealed class LoadoutTests : IDisposable
|
|||
Loadout hand;
|
||||
using (var cache = Open())
|
||||
{
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
hand = Armed(cache);
|
||||
var failures = new List<string>();
|
||||
var ship = Build(items, hand, failures);
|
||||
|
|
@ -108,7 +108,7 @@ public sealed class LoadoutTests : IDisposable
|
|||
Assert.Equal(hand.Slots.Select(Describe), loaded.Slots.Select(Describe));
|
||||
Assert.Equal(hand.WeaponGroups, loaded.WeaponGroups);
|
||||
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var failures = new List<string>();
|
||||
var ship = Build(items, loaded, failures);
|
||||
Assert.Empty(failures);
|
||||
|
|
@ -279,7 +279,7 @@ public sealed class LoadoutTests : IDisposable
|
|||
|
||||
using (var cache = Open())
|
||||
{
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var missing = HandBuilt(cache);
|
||||
missing.Slots[1].Design = new CultRecordRef<EquippableItemData>(new CultRecordKey("absent-design"));
|
||||
var failures = new List<string>();
|
||||
|
|
@ -304,7 +304,7 @@ public sealed class LoadoutTests : IDisposable
|
|||
public void FailedFitBuildsNothing()
|
||||
{
|
||||
using var cache = Open();
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var clash = HandBuilt(cache);
|
||||
clash.Slots[1].Position = HardpointCell;
|
||||
var failures = new List<string>();
|
||||
|
|
@ -319,7 +319,7 @@ public sealed class LoadoutTests : IDisposable
|
|||
public void ADesignWithNoProductBuildsUnbranded()
|
||||
{
|
||||
using var cache = Open();
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var failures = new List<string>();
|
||||
var ship = Build(items, WithDesign(HandBuilt(cache), 0, cache, "Orphan"), failures);
|
||||
Assert.Empty(failures);
|
||||
|
|
@ -335,7 +335,7 @@ public sealed class LoadoutTests : IDisposable
|
|||
public void ADesignWhoseOnlyProductIsUnavailableStillFails()
|
||||
{
|
||||
using var cache = Open();
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var failures = new List<string>();
|
||||
var soleProduct = SoleProduct(cache);
|
||||
Assert.Null(Loadouts.Materialize(items, null, HandBuilt(cache), design => design.Name == "Lamp" ? null : soleProduct(design), failures));
|
||||
|
|
@ -365,7 +365,7 @@ public sealed class LoadoutTests : IDisposable
|
|||
var gun = cache.GetAll<FactionProductData>().Single(p => p.Name == "Gun by Maker");
|
||||
var bay = cache.GetAll<FactionProductData>().Single(p => p.Name == "Bay by Maker");
|
||||
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
|
||||
EquippableItem Mint(FactionProductData product) => (EquippableItem) items.CreateInstance(product);
|
||||
|
||||
|
|
@ -477,7 +477,7 @@ public sealed class LoadoutTests : IDisposable
|
|||
cache.FlushAsync().Wait();
|
||||
|
||||
var random = new Random(1);
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var generator = new LoadoutGenerator(ref random, items, null, null, null, .5f);
|
||||
var pack = generator.GenerateShipLoadout(candidate => candidate.Name == "Twin");
|
||||
Assert.NotNull(pack);
|
||||
|
|
@ -504,7 +504,7 @@ public sealed class LoadoutTests : IDisposable
|
|||
new RarityTier { Name = "Common", Quality = .2f, Color = new float3(1, 1, 1) },
|
||||
new RarityTier { Name = "Rare", Quality = .5f, Color = new float3(1, 1, 1) }
|
||||
};
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), settings, _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), settings, _ => { });
|
||||
var lamp = cache.GetByName<GearData>("Lamp");
|
||||
var commonLot = items.Lots.Add(new Lot
|
||||
{
|
||||
|
|
@ -561,7 +561,7 @@ public sealed class LoadoutTests : IDisposable
|
|||
using var cache = Open();
|
||||
var settings = AuthoredSettings.Load(RunStartTests.FindRepoRoot()).Read<GameplaySettings>("GameplaySettings");
|
||||
var tiers = settings.Tiers;
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), settings, _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), settings, _ => { });
|
||||
var lamp = cache.GetByName<GearData>("Lamp");
|
||||
var assembly = tiers[3].Quality;
|
||||
var sleeperLot = items.Lots.Add(new Lot
|
||||
|
|
@ -598,7 +598,7 @@ public sealed class LoadoutTests : IDisposable
|
|||
var settings = AuthoredSettings.Load(RunStartTests.FindRepoRoot()).Read<GameplaySettings>("GameplaySettings");
|
||||
var tiers = settings.Tiers;
|
||||
Assert.True(tiers.Length > index, "premise: the shipped asset carries this tier");
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), settings, _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), settings, _ => { });
|
||||
var lamp = cache.GetByName<GearData>("Lamp");
|
||||
string TierAt(float quality) => items.GetTier((CraftedItemInstance) items.CreateInstance(items.Lots.Add(
|
||||
new Lot { Design = cache.RefOf<ItemData>(lamp), Origin = new Attributed(), Quality = quality, Roles = new List<RoleFill>() }))).tier.Name;
|
||||
|
|
@ -614,7 +614,7 @@ public sealed class LoadoutTests : IDisposable
|
|||
{
|
||||
using var cache = Open();
|
||||
var settings = AuthoredSettings.Load(RunStartTests.FindRepoRoot()).Read<GameplaySettings>("GameplaySettings");
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), settings, _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), settings, _ => { });
|
||||
var lamp = cache.GetByName<GearData>("Lamp");
|
||||
// Two steps and a hair above the lowest threshold: the step width decides whether that counts as two or one.
|
||||
var quality = settings.Tiers[0].Quality + 2 * .0499f + .0001f;
|
||||
|
|
@ -640,7 +640,7 @@ public sealed class LoadoutTests : IDisposable
|
|||
public void QualityTermAppliesItsOwnExponent()
|
||||
{
|
||||
using var cache = Open();
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var lamp = cache.GetByName<GearData>("Lamp");
|
||||
var lotId = items.Lots.Add(new Lot { Design = cache.RefOf<ItemData>(lamp), Origin = new Attributed(), Quality = .5f, Roles = new List<RoleFill>() });
|
||||
var instance = (EquippableItem) items.CreateInstance(lotId);
|
||||
|
|
@ -658,7 +658,7 @@ public sealed class LoadoutTests : IDisposable
|
|||
using var cache = Open();
|
||||
var settings = RunSaveTests.TestSettings();
|
||||
settings.QualityPriceModifier = new ExponentialLerp { Minimum = 0, Maximum = 1, Exponent = 1 };
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), settings, _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), settings, _ => { });
|
||||
var lamp = cache.GetByName<GearData>("Lamp");
|
||||
var lowLot = items.Lots.Add(new Lot { Design = cache.RefOf<ItemData>(lamp), Origin = new Attributed(), Quality = .2f, Roles = new List<RoleFill>() });
|
||||
var highLot = items.Lots.Add(new Lot { Design = cache.RefOf<ItemData>(lamp), Origin = new Attributed(), Quality = .8f, Roles = new List<RoleFill>() });
|
||||
|
|
@ -695,7 +695,7 @@ public sealed class LoadoutTests : IDisposable
|
|||
var durable = cache.Upsert(new GearData { Name = "Durable", Hardpoint = HardpointType.Sensors, Shape = new Shape(), Price = 10, Durability = 100 });
|
||||
cache.FlushAsync().Wait();
|
||||
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var lowLot = items.Lots.Add(new Lot { Design = new CultRecordRef<ItemData>(durable.Key), Origin = new Attributed(), Quality = .1f, Roles = new List<RoleFill>() });
|
||||
var highLot = items.Lots.Add(new Lot { Design = new CultRecordRef<ItemData>(durable.Key), Origin = new Attributed(), Quality = .9f, Roles = new List<RoleFill>() });
|
||||
var sleeperLot = items.Lots.Add(new Lot
|
||||
|
|
@ -723,7 +723,7 @@ public sealed class LoadoutTests : IDisposable
|
|||
public void EquippedItemExponentsReadEffectiveQuality()
|
||||
{
|
||||
using var cache = Open();
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var maker = cache.RefOf(cache.GetByName<Faction>("Maker"));
|
||||
var hullData = cache.GetByName<HullData>("Skiff");
|
||||
var lampData = cache.GetByName<GearData>("Lamp");
|
||||
|
|
@ -785,7 +785,7 @@ public sealed class LoadoutTests : IDisposable
|
|||
cache.FlushAsync().Wait();
|
||||
var thermalData = cache.Get(thermalGear);
|
||||
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var hullItem = (EquippableItem) items.CreateInstance(items.CreateLot(hullData, maker, .5f));
|
||||
var ship = new Ship(items, null, hullItem, new EntitySettings());
|
||||
var gearItem = (EquippableItem) items.CreateInstance(items.CreateLot(thermalData, maker, .5f));
|
||||
|
|
@ -831,7 +831,7 @@ public sealed class LoadoutTests : IDisposable
|
|||
cache.FlushAsync().Wait();
|
||||
var thermalData = cache.Get(thermalGear);
|
||||
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var hullItem = (EquippableItem) items.CreateInstance(items.CreateLot(hullData, maker, .5f));
|
||||
var ship = new Ship(items, null, hullItem, new EntitySettings());
|
||||
var gearItem = (EquippableItem) items.CreateInstance(items.CreateLot(thermalData, maker, .5f));
|
||||
|
|
@ -867,7 +867,7 @@ public sealed class LoadoutTests : IDisposable
|
|||
cache.FlushAsync().Wait();
|
||||
var consumableData = cache.Get(consumableRef);
|
||||
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var hullItem = (EquippableItem) items.CreateInstance(items.CreateLot(hullData, maker, .5f));
|
||||
var ship = new Ship(items, null, hullItem, new EntitySettings());
|
||||
var consumableItem = (ConsumableItem) items.CreateInstance(items.CreateLot(consumableData, maker, .5f));
|
||||
|
|
@ -902,7 +902,7 @@ public sealed class LoadoutTests : IDisposable
|
|||
cache.FlushAsync().Wait();
|
||||
|
||||
var product = cache.GetAll<FactionProductData>().Single(p => p.Name == "Lensed by Maker");
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var lotId = items.CreateLot(product);
|
||||
var roleFill = items.Lots[lotId].Roles.Single(r => r.Role == "lens");
|
||||
Assert.Equal(.9f, roleFill.Quality, 3);
|
||||
|
|
@ -914,7 +914,7 @@ public sealed class LoadoutTests : IDisposable
|
|||
{
|
||||
using var cache = Open();
|
||||
var maker = cache.RefOf(cache.GetByName<Faction>("Maker"));
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var lamp = cache.GetByName<GearData>("Lamp");
|
||||
var lotId = items.Lots.Add(new Lot
|
||||
{
|
||||
|
|
@ -932,7 +932,7 @@ public sealed class LoadoutTests : IDisposable
|
|||
public void OutOfRangeWeaponGroupIndexBuildsNothing()
|
||||
{
|
||||
using var cache = Open();
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var stale = Armed(cache);
|
||||
stale.WeaponGroups = new[] { new[] { 0, 2 }, new[] { -1 } };
|
||||
var failures = new List<string>();
|
||||
|
|
@ -944,7 +944,7 @@ public sealed class LoadoutTests : IDisposable
|
|||
public void UnequippingAGroupedWeaponRemovesItFromItsGroups()
|
||||
{
|
||||
using var cache = Open();
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var ship = Build(items, Armed(cache), new List<string>());
|
||||
var item = Assert.Single(ship.WeaponGroups[0].items);
|
||||
Assert.NotNull(item.GetBehavior<Weapon>());
|
||||
|
|
@ -959,7 +959,7 @@ public sealed class LoadoutTests : IDisposable
|
|||
public void NonWeaponGroupIndexBuildsNothing()
|
||||
{
|
||||
using var cache = Open();
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var lampGrouped = WithDesign(Armed(cache), 0, cache, "Lamp");
|
||||
var failures = new List<string>();
|
||||
Assert.Null(Build(items, lampGrouped, failures));
|
||||
|
|
@ -970,7 +970,7 @@ public sealed class LoadoutTests : IDisposable
|
|||
public void MoreWeaponGroupsThanTheGameHasBuildsNothing()
|
||||
{
|
||||
using var cache = Open();
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var wide = Armed(cache);
|
||||
wide.WeaponGroups = wide.WeaponGroups.Append(new[] { 0 }).ToArray();
|
||||
var failures = new List<string>();
|
||||
|
|
@ -984,7 +984,7 @@ public sealed class LoadoutTests : IDisposable
|
|||
public void FewerWeaponGroupsArePaddedToTheGameCount()
|
||||
{
|
||||
using var cache = Open();
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var armed = Armed(cache);
|
||||
armed.WeaponGroups = new[] { new[] { 0 } };
|
||||
foreach (var groups in new[] { armed.WeaponGroups, null })
|
||||
|
|
@ -1008,7 +1008,7 @@ public sealed class LoadoutTests : IDisposable
|
|||
public void EntitiesDoNotShareDefaultEntitySettings()
|
||||
{
|
||||
using var cache = Open();
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var maker = cache.RefOf(cache.GetByName<Faction>("Maker"));
|
||||
var hullData = cache.GetByName<HullData>("Skiff");
|
||||
var template = items.GameplaySettings.DefaultEntitySettings;
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ public sealed class LockWarningTests : IDisposable
|
|||
_openCache = AetheriaStores.Open(Catalog, catalogWritable: true);
|
||||
var ledger = new ProvenanceLedger();
|
||||
for (var lot = 1; lot < 12; lot++) ledger.Lots[lot] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
_items = new ItemManager(_openCache, ledger, new GameplaySettings
|
||||
_items = new ItemManager(1u, _openCache, ledger, new GameplaySettings
|
||||
{
|
||||
DefaultEntitySettings = new EntitySettings(),
|
||||
Tiers = new[] { new RarityTier { Name = "Common", Quality = .5f, Color = new float3(1, 1, 1) } },
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ public sealed class LootTests : IDisposable
|
|||
|
||||
var ledger = new ProvenanceLedger();
|
||||
for (var i = 1; i <= 9000; i++) ledger.Lots[i] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
var items = new ItemManager(cache, ledger, settings, _ => { });
|
||||
var items = new ItemManager(1u, cache, ledger, settings, _ => { });
|
||||
var planets = new PlanetSettings();
|
||||
var pack = new ZonePack { Radius = well ? 1000 : 0 };
|
||||
if (well) WellFixture.Dig(cache, pack, planets, 300, 100);
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@ public sealed class MineTests : IDisposable
|
|||
|
||||
var ledger = new ProvenanceLedger();
|
||||
for (var i = 1; i <= 500; i++) ledger.Lots[i] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
var items = new ItemManager(cache, ledger, settings, _ => { });
|
||||
var items = new ItemManager(1u, cache, ledger, settings, _ => { });
|
||||
var planets = new PlanetSettings();
|
||||
var pack = new ZonePack { Radius = well ? 1000 : 0 };
|
||||
if (well) WellFixture.Dig(cache, pack, planets, 300, 100);
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ public sealed class MiningCut1Tests : IDisposable
|
|||
// OrbitData and BodyData (AsteroidBeltData) are run types (AetheriaStores.RunTypes), not catalog types:
|
||||
// they need a run store to write into.
|
||||
var cache = AetheriaStores.Open(catalog, run, catalogWritable: true);
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), GameSettings(), _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), GameSettings(), _ => { });
|
||||
var settings = Settings();
|
||||
|
||||
var sunOrbitKey = cache.Upsert(new OrbitData()).Key;
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ public sealed class MiningCut2Tests : IDisposable
|
|||
var dir = Path.Combine(_root, $"cat{_fixtureCount++}");
|
||||
Directory.CreateDirectory(dir);
|
||||
var cache = AetheriaStores.Open(Path.Combine(dir, "Aetheria.cc"), Path.Combine(dir, "run.cc"), catalogWritable: true);
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), GameSettings(), _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), GameSettings(), _ => { });
|
||||
var settings = Settings();
|
||||
|
||||
var rootOrbit = new OrbitData { Distance = 3000f, Phase = 0.05f };
|
||||
|
|
|
|||
|
|
@ -131,7 +131,7 @@ public sealed partial class MiningCut3Tests : IDisposable
|
|||
});
|
||||
cache.Upsert(dimEye);
|
||||
foreach (var kind in kinds ?? new FieldKindData[0]) cache.Upsert(kind);
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), GameSettings(), _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), GameSettings(), _ => { });
|
||||
|
||||
var sunOrbit = cache.Upsert(new OrbitData { FixedPosition = sunAt }).Key;
|
||||
var sun = cache.Upsert(new SunData { Orbit = new CultRecordRef<OrbitData>(sunOrbit), Mass = 1f }).Key;
|
||||
|
|
@ -679,7 +679,7 @@ public sealed partial class MiningCut3Tests : IDisposable
|
|||
// Kept: a new, overwhelmingly weighted kind now wins the pick for this belt, yet loading it again leaves it.
|
||||
reopened.Upsert(Kind("Overwhelming", 9f, 1e6f));
|
||||
Assert.False(FieldKinds.Assign(beltKey, reopened).Key.Equals(assigned));
|
||||
var items = new ItemManager(reopened, new ProvenanceLedger(), GameSettings(), _ => { });
|
||||
var items = new ItemManager(1u, reopened, new ProvenanceLedger(), GameSettings(), _ => { });
|
||||
var reloaded = new Zone(items, PlanetSettings(), s.Pack, new GalaxyZone { Name = "MiningCut3Reload", Owner = null }, null);
|
||||
Assert.True(((AsteroidBeltData) reloaded.Planets[beltKey]).Kind.Key.Equals(assigned));
|
||||
}
|
||||
|
|
@ -817,7 +817,7 @@ public sealed partial class MiningCut3Tests : IDisposable
|
|||
cache, new PlayerSettings(), new DirectoryInfo(dir), _ => { }, null, seed);
|
||||
}
|
||||
catch (Exception) { continue; } // Galaxy's own seed-dependent flakiness (RestoredHullsTests)
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), GameSettings(), _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), GameSettings(), _ => { });
|
||||
foreach (var zone in galaxy.Zones.Take(4))
|
||||
{
|
||||
var pack = ZoneGenerator.GenerateZone(items, RestoredHullsTests.TutorialZoneSettings(), RestoredHullsTests.AuthoredPlanetSettings(), galaxy, zone, isTutorial: zone == galaxy.Entrance);
|
||||
|
|
@ -924,7 +924,7 @@ public sealed partial class MiningCut3Tests : IDisposable
|
|||
_caches.Add(clean);
|
||||
Assert.False(clean.IsDirty);
|
||||
Assert.False(((AsteroidBeltData) clean.Get(beltKey)).Kind.IsSet());
|
||||
var zone = new Zone(new ItemManager(clean, new ProvenanceLedger(), GameSettings(), _ => { }), PlanetSettings(), s.Pack,
|
||||
var zone = new Zone(new ItemManager(1u, clean, new ProvenanceLedger(), GameSettings(), _ => { }), PlanetSettings(), s.Pack,
|
||||
new GalaxyZone { Name = "Clean", Owner = null }, null);
|
||||
var assigned = ((AsteroidBeltData) zone.Planets[beltKey]).Kind;
|
||||
Assert.True(assigned.IsSet());
|
||||
|
|
@ -1026,7 +1026,7 @@ public sealed partial class MiningCut3Tests : IDisposable
|
|||
PingEnergy = Constant(0f), PingRange = Constant(300f), PingCooldown = Constant(4f)
|
||||
});
|
||||
cache.Upsert(eye);
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), GameSettings(), _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), GameSettings(), _ => { });
|
||||
var s = new Scene
|
||||
{
|
||||
Cache = cache, Items = items, Hull = hull, Eye = eye,
|
||||
|
|
|
|||
|
|
@ -257,7 +257,7 @@ public sealed class MunitionPartsTests : IDisposable
|
|||
var ledger = new ProvenanceLedger();
|
||||
ledger.Lots[1] = new Lot { Origin = new Attributed(), Quality = .5f };
|
||||
ledger.Lots[2] = new Lot { Origin = new Attributed(), Quality = .5f, Roles = new List<RoleFill> { new RoleFill { Role = "thruster", Quality = thrusterQuality } } };
|
||||
var items = new ItemManager(cache, ledger, settings, _ => { });
|
||||
var items = new ItemManager(1u, cache, ledger, settings, _ => { });
|
||||
var zone = new Zone(items, new PlanetSettings(), new ZonePack(), new GalaxyZone { Name = "Test", Owner = null }, null);
|
||||
ship = new Ship(items, zone, new EquippableItem { Data = cache.RefOf<ItemData>(hull), Durability = hull.Durability, Lot = 1 }, settings.DefaultEntitySettings);
|
||||
foreach (var (name, x, y) in ShipGear)
|
||||
|
|
|
|||
|
|
@ -255,7 +255,7 @@ public sealed class OrbitsFromFieldTests : IDisposable
|
|||
var galaxy = new Galaxy(tutorial, authored.Read<SectorBackgroundSettings>("TutorialBackgroundSettings"),
|
||||
cache, new PlayerSettings(),
|
||||
Directory.CreateDirectory(Path.Combine(dir, "Narrative")), _ => { }, null, seed);
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), authored.Read<GameplaySettings>("GameplaySettings"), _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), authored.Read<GameplaySettings>("GameplaySettings"), _ => { });
|
||||
var pack = ZoneGenerator.GenerateZone(items, authored.Read<ZoneGenerationSettings>("ZoneSettings"), authored.Read<PlanetSettings>("PlanetSettings"), galaxy, galaxy.Entrance,
|
||||
galaxy.IsPrelude, false);
|
||||
var zone = new Zone(items, authored.Read<PlanetSettings>("PlanetSettings"), pack, galaxy.Entrance, galaxy);
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ public sealed class PaintModeTests : IDisposable
|
|||
_openCache = AetheriaStores.Open(Catalog, catalogWritable: true);
|
||||
var ledger = new ProvenanceLedger();
|
||||
for (var lot = 1; lot < 12; lot++) ledger.Lots[lot] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
_items = new ItemManager(_openCache, ledger, new GameplaySettings
|
||||
_items = new ItemManager(1u, _openCache, ledger, new GameplaySettings
|
||||
{
|
||||
DefaultEntitySettings = new EntitySettings(),
|
||||
Tiers = new[] { new RarityTier { Name = "Common", Quality = .5f, Color = new float3(1, 1, 1) } },
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ public sealed class PickupTests : IDisposable
|
|||
|
||||
var ledger = new ProvenanceLedger();
|
||||
for (var i = 1; i <= 9000; i++) ledger.Lots[i] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
var items = new ItemManager(cache, ledger, settings, _ => { });
|
||||
var items = new ItemManager(1u, cache, ledger, settings, _ => { });
|
||||
var pack = new ZonePack { Radius = 0 };
|
||||
var curves = new PlanetSettings();
|
||||
if (wells)
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ public sealed class PopulatedRadiusTests : IDisposable
|
|||
var cache = AetheriaStores.Open(Path.Combine(dir, "Aetheria.cc"), Path.Combine(dir, "run.cc"), catalogWritable: true);
|
||||
_caches.Add(cache);
|
||||
cache.Upsert(new VerseGrammar { Revision = 1 });
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), new GameplaySettings
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), new GameplaySettings
|
||||
{
|
||||
DefaultEntitySettings = new EntitySettings(),
|
||||
Tiers = new[] { new RarityTier { Name = "Common", Quality = .5f, Color = new float3(1, 1, 1) } },
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ public sealed class PotentialOwnerTests : IDisposable
|
|||
Tiers = new[] { new RarityTier { Name = "Common", Quality = .5f, Color = new float3(1, 1, 1) } },
|
||||
QualityPriceModifier = new ExponentialLerp()
|
||||
};
|
||||
_items = new ItemManager(_cache, new ProvenanceLedger(), settings, _ => { });
|
||||
_items = new ItemManager(1u, _cache, new ProvenanceLedger(), settings, _ => { });
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ public sealed class PowerBusTests : IDisposable
|
|||
CultCache cache, float reactorCharge, float startingCapacitorCharge, bool reactorFirst)
|
||||
{
|
||||
var ledger = new ProvenanceLedger();
|
||||
var items = new ItemManager(cache, ledger, Settings(), _ => { });
|
||||
var items = new ItemManager(1u, cache, ledger, Settings(), _ => { });
|
||||
var zone = new Zone(items, new PlanetSettings(), new ZonePack(), new GalaxyZone { Name = "Test Zone", Owner = null }, null);
|
||||
var hull = Mint(cache, items, cache.GetByName<HullData>("Skiff"));
|
||||
var ship = new Ship(items, zone, hull, new EntitySettings());
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ public sealed class PowerCurveTests : IDisposable
|
|||
|
||||
private Ship BuildShip(CultCache cache, float reactorCharge)
|
||||
{
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), Settings(), _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), Settings(), _ => { });
|
||||
var zone = new Zone(items, new PlanetSettings(), new ZonePack(), new GalaxyZone { Name = "Test Zone", Owner = null }, null);
|
||||
var hull = Mint(cache, items, cache.GetByName<HullData>("Skiff"));
|
||||
var ship = new Ship(items, zone, hull, new EntitySettings());
|
||||
|
|
@ -401,7 +401,7 @@ public sealed class PowerCurveTests : IDisposable
|
|||
});
|
||||
cache.FlushAsync().Wait();
|
||||
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), Settings(), _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), Settings(), _ => { });
|
||||
var zone = new Zone(items, new PlanetSettings(), new ZonePack(), new GalaxyZone { Name = "Test Zone", Owner = null }, null);
|
||||
var hullItem = Mint(cache, items, cache.GetByName<HullData>("Skiff"));
|
||||
var ship = new Ship(items, zone, hullItem, new EntitySettings());
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ public sealed class PowerTierTests : IDisposable
|
|||
private (Ship ship, ItemManager items, Reactor reactor) BuildShip(CultCache cache, float reactorCharge)
|
||||
{
|
||||
var ledger = new ProvenanceLedger();
|
||||
var items = new ItemManager(cache, ledger, Settings(), _ => { });
|
||||
var items = new ItemManager(1u, cache, ledger, Settings(), _ => { });
|
||||
var zone = new Zone(items, new PlanetSettings(), new ZonePack(), new GalaxyZone { Name = "Test Zone", Owner = null }, null);
|
||||
var hull = Mint(cache, items, cache.GetByName<HullData>("Skiff"));
|
||||
var ship = new Ship(items, zone, hull, new EntitySettings());
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ public sealed class ProductBinTests : IDisposable
|
|||
Roles = { new ItemRole { Name = "lens" } }
|
||||
});
|
||||
_lens = new CultRecordRef<CraftedItemData>(gear.Key);
|
||||
_items = new ItemManager(_cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _log.Add) { Random = new CultMath.Random(7u) };
|
||||
_items = new ItemManager(1u, _cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _log.Add) { Random = new CultMath.Random(7u) };
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
|
|
|||
|
|
@ -65,8 +65,10 @@ public sealed class ProductChoiceTests : IDisposable
|
|||
|
||||
private Rig Build(params string[] lampOrder) => Build(new float[0], lampOrder);
|
||||
|
||||
// binFloors adds one bin of lamp-a's line per floor, named "lamp-a bin" and "lamp-a bin 2"..., by the same maker.
|
||||
private Rig Build(float[] binFloors, string[] lampOrder)
|
||||
// binFloors adds one bin of lamp-a's line per floor, named "lamp-a bin" and "lamp-a bin 2"..., by the same maker. Every rig also
|
||||
// holds a Barge, the one hull with a ballistic hardpoint, and a Gun only it mounts, each sold by Alpha. notOffered names the
|
||||
// products the rig takes off the market.
|
||||
private Rig Build(float[] binFloors, string[] lampOrder, params string[] notOffered)
|
||||
{
|
||||
var root = Path.Combine(_root, "rig" + _catalogs++);
|
||||
Directory.CreateDirectory(root);
|
||||
|
|
@ -92,20 +94,31 @@ public sealed class ProductChoiceTests : IDisposable
|
|||
Hardpoints = { new HardpointData { Type = HardpointType.Sensors, Position = HardpointCell, Shape = new Shape() } }
|
||||
});
|
||||
var lamp = seed.Upsert(new GearData { Name = "Lamp", Hardpoint = HardpointType.Sensors, Shape = new Shape(), Price = 10 });
|
||||
var barge = seed.Upsert(new HullData
|
||||
{
|
||||
Name = "Barge", HullType = HullType.Ship, Shape = shape, Price = 100,
|
||||
Hardpoints = { new HardpointData { Type = HardpointType.Ballistic, Position = HardpointCell, Shape = new Shape() } }
|
||||
});
|
||||
var gun = seed.Upsert(new GearData { Name = "Gun", Hardpoint = HardpointType.Ballistic, Shape = new Shape(), Price = 10 });
|
||||
void Sell(string name, CultRecordKey design, CultRecordRef<Faction> maker, string key) =>
|
||||
seed.Commit(batch => batch.Upsert(typeof(FactionProductData), new FactionProductData
|
||||
{ Name = name, Design = new CultRecordRef<CraftedItemData>(design), Manufacturer = maker }, new CultRecordKey(key)));
|
||||
{ Name = name, Design = new CultRecordRef<CraftedItemData>(design), Manufacturer = maker, NotOffered = notOffered.Contains(name) }, new CultRecordKey(key)));
|
||||
Sell("Skiff by Alpha", skiff.Key, alpha, "skiff-a");
|
||||
Sell("Barge by Alpha", barge.Key, alpha, "barge-a");
|
||||
Sell("Gun by Alpha", gun.Key, alpha, "gun-a");
|
||||
var makers = new Dictionary<string, CultRecordRef<Faction>> { ["lamp-a"] = alpha, ["lamp-z"] = zeta, ["lamp-m"] = mu };
|
||||
// lampOrder holds product names; the record key of the i-th inserted is "k" + i, so keys and insertion order
|
||||
// both follow the permutation.
|
||||
for (var i = 0; i < lampOrder.Length; i++) Sell(lampOrder[i], lamp.Key, makers[lampOrder[i]], "k" + i);
|
||||
for (var i = 0; i < binFloors.Length; i++)
|
||||
{
|
||||
var name = i == 0 ? "lamp-a bin" : "lamp-a bin " + (i + 1);
|
||||
seed.Commit(batch => batch.Upsert(typeof(FactionProductData), new FactionProductData
|
||||
{
|
||||
Name = i == 0 ? "lamp-a bin" : "lamp-a bin " + (i + 1), Design = new CultRecordRef<CraftedItemData>(lamp.Key), Manufacturer = alpha,
|
||||
Name = name, Design = new CultRecordRef<CraftedItemData>(lamp.Key), Manufacturer = alpha, NotOffered = notOffered.Contains(name),
|
||||
Bin = new ProductBin { Line = new CultRecordRef<FactionProductData>(new CultRecordKey("k" + Array.IndexOf(lampOrder, "lamp-a"))), Floor = binFloors[i] }
|
||||
}, new CultRecordKey("bin" + i)));
|
||||
}
|
||||
seed.FlushAsync().Wait();
|
||||
}
|
||||
|
||||
|
|
@ -133,7 +146,7 @@ public sealed class ProductChoiceTests : IDisposable
|
|||
return new Rig
|
||||
{
|
||||
Cache = cache, OutA = outA, OutZ = outZ, OutBoth = cache.GetByName<Faction>("OutBoth"), OutEven = cache.GetByName<Faction>("OutEven"), OutNone = cache.GetByName<Faction>("OutNone"),
|
||||
Items = new ItemManager(cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { }),
|
||||
Items = new ItemManager(1u, cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { }),
|
||||
Galaxy = new Galaxy(cache, cache.GetGlobal<SavedGame>(), _ => { })
|
||||
};
|
||||
}
|
||||
|
|
@ -325,7 +338,7 @@ public sealed class ProductChoiceTests : IDisposable
|
|||
int AlphaDraws(Rig rig)
|
||||
{
|
||||
var log = new List<string>();
|
||||
var items = new ItemManager(rig.Cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), log.Add);
|
||||
var items = new ItemManager(1u, rig.Cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), log.Add);
|
||||
var lamp = rig.Cache.GetByName<GearData>("Lamp");
|
||||
var alpha = Enumerable.Range(1, 1000).Count(i =>
|
||||
{
|
||||
|
|
@ -367,5 +380,129 @@ public sealed class ProductChoiceTests : IDisposable
|
|||
{
|
||||
Assert.Equal(4, typeof(Loadout).GetField(nameof(Loadout.HullProduct)).GetCustomAttribute<KeyAttribute>().IntKey);
|
||||
Assert.Equal(3, typeof(LoadoutSlot).GetField(nameof(LoadoutSlot.Product)).GetCustomAttribute<KeyAttribute>().IntKey);
|
||||
Assert.Equal(8, typeof(FactionProductData).GetField(nameof(FactionProductData.NotOffered)).GetCustomAttribute<KeyAttribute>().IntKey);
|
||||
}
|
||||
|
||||
// Ruling off-market-is-unoffered-product: a not-offered product is never chosen, by a faction that reaches its maker or by a
|
||||
// stage with no faction, which otherwise reaches every maker (ruling boss-hulls-exclusive).
|
||||
[Fact]
|
||||
public void ANotOfferedProductIsNeverChosen()
|
||||
{
|
||||
var rig = Build(new float[0], new[] { "lamp-z", "lamp-a" }, "lamp-a");
|
||||
var lamp = rig.Cache.GetByName<GearData>("Lamp");
|
||||
for (var i = 1; i <= 64; i++)
|
||||
{
|
||||
Assert.Equal("lamp-z", rig.Outfitter(null, Seed(i)).ChooseProduct(lamp).Name);
|
||||
Assert.Equal("lamp-z", rig.Outfitter(rig.OutEven, Seed(i), 0).ChooseProduct(lamp).Name);
|
||||
// OutA reaches only Alpha, whose lamp is off the market.
|
||||
Assert.Null(rig.Outfitter(rig.OutA, Seed(i)).ChooseProduct(lamp));
|
||||
}
|
||||
Assert.False(rig.Outfitter(null, 1).IsAvailable(rig.Product("lamp-a")));
|
||||
Assert.True(rig.Outfitter(null, 1).IsAvailable(rig.Product("lamp-z")));
|
||||
}
|
||||
|
||||
// An off-market bin leaves the share arithmetic (ruling bin-share-by-floor): its line keeps the whole of its maker's preference,
|
||||
// so taking a bin off the market never shrinks the maker's presence for the design.
|
||||
[Fact]
|
||||
public void AnOffMarketBinLeavesItsLineTheWholeWeight()
|
||||
{
|
||||
var alone = Build("lamp-z", "lamp-a");
|
||||
var off = Build(new[] { 0f }, new[] { "lamp-z", "lamp-a" }, "lamp-a bin");
|
||||
var preference = alone.Outfitter(alone.OutEven, 1, 0).ProductWeight(alone.Product("lamp-a"));
|
||||
Assert.Equal(preference, off.Outfitter(off.OutEven, 1, 0).ProductWeight(off.Product("lamp-a")), 3);
|
||||
|
||||
var lamp = off.Cache.GetByName<GearData>("Lamp");
|
||||
var draws = Enumerable.Range(1, 600).Select(i => off.Outfitter(off.OutEven, Seed(i)).ChooseProduct(lamp).Name).ToArray();
|
||||
Assert.DoesNotContain("lamp-a bin", draws);
|
||||
var aloneLamp = alone.Cache.GetByName<GearData>("Lamp");
|
||||
var aloneDraws = Enumerable.Range(1, 600).Count(i => alone.Outfitter(alone.OutEven, Seed(i)).ChooseProduct(aloneLamp).Name == "lamp-a");
|
||||
Assert.InRange(draws.Count(name => name == "lamp-a"), aloneDraws - 30, aloneDraws + 30);
|
||||
}
|
||||
|
||||
// Ruling bin-inherits-line-offered: a bin is offered only if both it and its line are. A bin of an off-market line is never chosen,
|
||||
// never drawn by generation, and never stocked, though the bin itself carries no NotOffered.
|
||||
[Fact]
|
||||
public void AnOffMarketLinesBinIsNeverDrawnOrSold()
|
||||
{
|
||||
var rig = Build(new[] { 0f }, new[] { "lamp-z", "lamp-a" }, "lamp-a");
|
||||
Assert.False(rig.Product("lamp-a bin").NotOffered);
|
||||
var lamp = rig.Cache.GetByName<GearData>("Lamp");
|
||||
for (var i = 1; i <= 64; i++)
|
||||
{
|
||||
// OutA reaches only Alpha, whose line and so its bin are off the market.
|
||||
Assert.Null(rig.Outfitter(rig.OutA, Seed(i)).ChooseProduct(lamp));
|
||||
Assert.Equal("lamp-z", rig.Outfitter(null, Seed(i)).ChooseProduct(lamp).Name);
|
||||
Assert.Equal("lamp-z", rig.Outfitter(rig.OutA, Seed(i)).RandomProduct<EquippableItemData>(0, design => design == lamp, required: true).product.Name);
|
||||
foreach (var faction in new[] { rig.OutA, rig.OutEven, rig.OutNone, null })
|
||||
Assert.DoesNotContain(rig.Outfitter(faction, Seed(i)).RandomProducts<CraftedItemData>(16, 1, null, true), entry => entry.product.Name.StartsWith("lamp-a"));
|
||||
}
|
||||
Assert.False(rig.Outfitter(null, 1).IsAvailable(rig.Product("lamp-a bin")));
|
||||
}
|
||||
|
||||
// The any-manufacturer fallback of a required draw is closed to off-market products: when every product of the design is
|
||||
// not offered the draw yields nothing, for a faction that reaches the maker, one that reaches nobody, and no faction.
|
||||
[Fact]
|
||||
public void ARequiredDrawNeverFallsBackToANotOfferedProduct()
|
||||
{
|
||||
var rig = Build(new float[0], new[] { "lamp-z", "lamp-a" }, "lamp-z", "lamp-a");
|
||||
var lamp = rig.Cache.GetByName<GearData>("Lamp");
|
||||
foreach (var faction in new[] { rig.OutA, rig.OutNone, null })
|
||||
for (var i = 1; i <= 16; i++)
|
||||
Assert.Null(rig.Outfitter(faction, Seed(i)).RandomProduct<EquippableItemData>(0, design => design == lamp, required: true).product);
|
||||
|
||||
// With one product still offered, a faction that reaches only the other maker falls back to it: the empty draw above is
|
||||
// the field's doing, not a closed fallback.
|
||||
var one = Build(new float[0], new[] { "lamp-z", "lamp-a" }, "lamp-a");
|
||||
var oneLamp = one.Cache.GetByName<GearData>("Lamp");
|
||||
for (var i = 1; i <= 16; i++)
|
||||
Assert.Equal("lamp-z", one.Outfitter(one.OutA, Seed(i)).RandomProduct<EquippableItemData>(0, design => design == oneLamp, required: true).product.Name);
|
||||
}
|
||||
|
||||
// A hull sold only off the market is no gear's home: gear only it mounts is never drawn, so no station stocks it.
|
||||
[Fact]
|
||||
public void AnOffMarketHullGivesNoGearAHome()
|
||||
{
|
||||
var sold = Build("lamp-a");
|
||||
var soldGun = sold.Cache.GetByName<GearData>("Gun");
|
||||
Assert.Contains(sold.Cache.GetByName<HullData>("Barge"), LoadoutGenerator.SoldHulls(sold.Cache));
|
||||
Assert.Equal("Gun by Alpha", sold.Outfitter(null, 1).RandomProduct<EquippableItemData>(0, design => design == soldGun, required: true).product.Name);
|
||||
|
||||
var off = Build(new float[0], new[] { "lamp-a" }, "Barge by Alpha");
|
||||
var barge = off.Cache.GetByName<HullData>("Barge");
|
||||
var gun = off.Cache.GetByName<GearData>("Gun");
|
||||
var hulls = LoadoutGenerator.SoldHulls(off.Cache);
|
||||
Assert.DoesNotContain(barge, hulls);
|
||||
Assert.Contains(off.Cache.GetByName<HullData>("Skiff"), hulls);
|
||||
Assert.False(LoadoutGenerator.HasHome(gun, hulls));
|
||||
foreach (var faction in new[] { off.OutA, null })
|
||||
for (var i = 1; i <= 16; i++)
|
||||
{
|
||||
var generator = off.Outfitter(faction, Seed(i));
|
||||
Assert.Null(generator.RandomProduct<EquippableItemData>(0, design => design == gun, required: true).product);
|
||||
Assert.Null(generator.RandomProduct<HullData>(0, design => design == barge, required: true).product);
|
||||
}
|
||||
}
|
||||
|
||||
// A Loadout that pins a not-offered hull product still builds it, under its brand: that is how a boss flies its hull.
|
||||
[Fact]
|
||||
public void APinnedNotOfferedHullStillMaterializes()
|
||||
{
|
||||
var rig = Build(new float[0], new[] { "lamp-a" }, "Barge by Alpha");
|
||||
var fit = new Loadout
|
||||
{
|
||||
Name = "Boss barge",
|
||||
Hull = rig.Cache.RefOf(rig.Cache.GetByName<HullData>("Barge")),
|
||||
HullProduct = rig.ProductRef("Barge by Alpha"),
|
||||
WeaponGroups = new int[0][]
|
||||
};
|
||||
var failures = new List<string>();
|
||||
var ship = Materialize(rig, fit, rig.Outfitter(rig.OutA, 1).ChooseProduct, failures);
|
||||
Assert.Empty(failures);
|
||||
Assert.Equal("Barge by Alpha", rig.Items.Brand(ship.Hull).Product.Name);
|
||||
|
||||
// Unpinned, the same hull has no product to be built as.
|
||||
fit.HullProduct = default;
|
||||
Assert.Null(Materialize(rig, fit, rig.Outfitter(null, 1).ChooseProduct, failures));
|
||||
Assert.NotEmpty(failures);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -140,8 +140,8 @@ public sealed class ProductLinesCatalogTests
|
|||
}
|
||||
}
|
||||
|
||||
// FactionProductData slot 7 is additive under the same schema id: a bin round-trips, and a record written before the slot
|
||||
// existed (an array one element shorter) reads no bin.
|
||||
// FactionProductData slots 7 and 8 are additive under the same schema id: a bin and NotOffered round-trip, a record written
|
||||
// before slot 8 existed (an array one element shorter) is on the market, and one written before slot 7 also reads no bin.
|
||||
[Fact]
|
||||
public void BinIsSlotSevenAndACatalogWithoutItReadsNoBin()
|
||||
{
|
||||
|
|
@ -150,12 +150,21 @@ public sealed class ProductLinesCatalogTests
|
|||
MessagePackSerializer.Serialize(new FactionProductData { Name = "Binned", Bin = new ProductBin { Floor = .67f } }, options), options);
|
||||
Assert.Equal(.67f, binned.Bin.Floor);
|
||||
|
||||
Assert.True(MessagePackSerializer.Deserialize<FactionProductData>(
|
||||
MessagePackSerializer.Serialize(new FactionProductData { Name = "Unsold", NotOffered = true }, options), options).NotOffered);
|
||||
|
||||
var bytes = MessagePackSerializer.Serialize(new FactionProductData { Name = "Old", Description = "d" }, options);
|
||||
Assert.Equal(0x98, bytes[0]);
|
||||
Assert.Equal(0xC0, bytes[^1]);
|
||||
var before = new byte[] { 0x97 }.Concat(bytes.Skip(1).Take(bytes.Length - 2)).ToArray();
|
||||
Assert.Equal(0x99, bytes[0]);
|
||||
Assert.Equal(0xC0, bytes[^2]);
|
||||
Assert.Equal(0xC2, bytes[^1]);
|
||||
var beforeNotOffered = new byte[] { 0x98 }.Concat(bytes.Skip(1).Take(bytes.Length - 2)).ToArray();
|
||||
var offered = MessagePackSerializer.Deserialize<FactionProductData>(beforeNotOffered, options);
|
||||
Assert.Equal("Old", offered.Name);
|
||||
Assert.False(offered.NotOffered);
|
||||
var before = new byte[] { 0x97 }.Concat(bytes.Skip(1).Take(bytes.Length - 3)).ToArray();
|
||||
var old = MessagePackSerializer.Deserialize<FactionProductData>(before, options);
|
||||
Assert.Equal("Old", old.Name);
|
||||
Assert.Null(old.Bin);
|
||||
Assert.False(old.NotOffered);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ public sealed class RangeFalloffTests
|
|||
public void AnUnequippedWeaponsUnauthoredStatsEvaluate(string design, bool unauthored)
|
||||
{
|
||||
using var cache = RestoredHullsTests.OpenCatalog();
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var data = cache.GetAll<WeaponItemData>().Single(x => x.Name == design);
|
||||
var lot = items.Lots.Add(new Lot { Design = cache.RefOf<ItemData>(data), Origin = new Attributed(), Quality = .5f, Roles = new System.Collections.Generic.List<RoleFill>() });
|
||||
var item = (EquippableItem) items.CreateInstance(lot);
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ public sealed class RestoredHullsTests
|
|||
|
||||
private static (uint seed, string zoneName) ScanForNoLagrangeCandidateZone(CultCache cache)
|
||||
{
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), Settings(), _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), Settings(), _ => { });
|
||||
for (var seed = 1u; seed <= 64u; seed++)
|
||||
{
|
||||
var galaxy = new Galaxy(TutorialGalaxySettings(), TutorialBackgroundSettings(), cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed);
|
||||
|
|
@ -227,7 +227,7 @@ public sealed class RestoredHullsTests
|
|||
var settings = Settings();
|
||||
var ledger = new ProvenanceLedger();
|
||||
for (var i = 1; i <= hull.Hardpoints.Count + 1; i++) ledger.Lots[i] = new Lot { Origin = new Attributed(), Quality = .5f };
|
||||
var items = new ItemManager(cache, ledger, settings, _ => { });
|
||||
var items = new ItemManager(1u, cache, ledger, settings, _ => { });
|
||||
var zone = new Zone(items, new PlanetSettings(), new ZonePack(), new GalaxyZone { Name = "Test", Owner = null }, null);
|
||||
var hullItem = new EquippableItem { Data = cache.RefOf<ItemData>(hull), Durability = hull.Durability, Lot = 1 };
|
||||
var ship = new Ship(items, zone, hullItem, settings.DefaultEntitySettings);
|
||||
|
|
@ -282,7 +282,7 @@ public sealed class RestoredHullsTests
|
|||
var settings = Settings();
|
||||
var ledger = new ProvenanceLedger();
|
||||
for (var i = 1; i <= hull.Hardpoints.Count + 1; i++) ledger.Lots[i] = new Lot { Origin = new Attributed(), Quality = .5f };
|
||||
var items = new ItemManager(cache, ledger, settings, _ => { });
|
||||
var items = new ItemManager(1u, cache, ledger, settings, _ => { });
|
||||
var zone = new Zone(items, new PlanetSettings(), new ZonePack(), new GalaxyZone { Name = "Test", Owner = null }, null);
|
||||
var hullItem = new EquippableItem { Data = cache.RefOf<ItemData>(hull), Durability = hull.Durability, Lot = 1 };
|
||||
var ship = new Ship(items, zone, hullItem, settings.DefaultEntitySettings);
|
||||
|
|
@ -372,7 +372,7 @@ public sealed class RestoredHullsTests
|
|||
using var cache = OpenReadOnlyRealCatalogWithScratchRun(gameData, scratchRun);
|
||||
var galaxy = new Galaxy(tutorialSettings, background, cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed);
|
||||
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), Settings(), _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), Settings(), _ => { });
|
||||
var pack = ZoneGenerator.GenerateZone(items, zoneSettings, AuthoredPlanetSettings(), galaxy, galaxy.Entrance, isTutorial: true);
|
||||
|
||||
var dockedStation = pack.Entities.OfType<OrbitalEntityPack>()
|
||||
|
|
@ -412,7 +412,7 @@ public sealed class RestoredHullsTests
|
|||
using var cache = OpenReadOnlyRealCatalogWithScratchRun(gameData, scratchRun);
|
||||
var galaxy = new Galaxy(tutorialSettings, background, cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed);
|
||||
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), Settings(), _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), Settings(), _ => { });
|
||||
var pack = ZoneGenerator.GenerateZone(items, zoneSettings, AuthoredPlanetSettings(), galaxy, galaxy.Entrance, isTutorial: true);
|
||||
|
||||
var planetOrbits = pack.Planets.Select(p => cache.Get(cache.Get(p).Orbit)).Where(o => o.Parent.IsSet()).ToArray();
|
||||
|
|
@ -449,7 +449,7 @@ public sealed class RestoredHullsTests
|
|||
{
|
||||
using var cache = OpenReadOnlyRealCatalogWithScratchRun(gameData, scratchRun);
|
||||
var galaxy = new Galaxy(TutorialGalaxySettings(), TutorialBackgroundSettings(), cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed: 1u);
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), Settings(), _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), Settings(), _ => { });
|
||||
|
||||
var zoneSettings = TutorialZoneSettings();
|
||||
zoneSettings.RosetteProbability = 0;
|
||||
|
|
@ -484,7 +484,7 @@ public sealed class RestoredHullsTests
|
|||
{
|
||||
using var cache = OpenReadOnlyRealCatalogWithScratchRun(gameData, scratchRun);
|
||||
var galaxy = new Galaxy(TutorialGalaxySettings(), TutorialBackgroundSettings(), cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed: 1u);
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), Settings(), _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), Settings(), _ => { });
|
||||
|
||||
var zoneSettings = TutorialZoneSettings();
|
||||
zoneSettings.RosetteProbability = 1;
|
||||
|
|
@ -535,7 +535,7 @@ public sealed class RestoredHullsTests
|
|||
using var cache = OpenReadOnlyRealCatalogWithScratchRun(gameData, scratchRun);
|
||||
var galaxy = new Galaxy(tutorialSettings, background, cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed);
|
||||
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), Settings(), _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), Settings(), _ => { });
|
||||
var pack = ZoneGenerator.GenerateZone(items, zoneSettings, AuthoredPlanetSettings(), galaxy, galaxy.Entrance, isTutorial: true);
|
||||
|
||||
var dockedStation = pack.Entities.OfType<OrbitalEntityPack>()
|
||||
|
|
@ -596,7 +596,7 @@ public sealed class RestoredHullsTests
|
|||
using var cache = OpenCatalog();
|
||||
var hull = cache.GetByName<HullData>("Longinus");
|
||||
var settings = Settings();
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), settings, _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), settings, _ => { });
|
||||
// ItemManager.Random defaults to a DateTime.Now-seeded instance; without pinning it here, the item
|
||||
// qualities LoadoutGenerator/EntitySerializer roll through it (not through the `random` below, which
|
||||
// only drives which designs get picked) would make this test's own measured speed non-deterministic.
|
||||
|
|
@ -645,7 +645,7 @@ public sealed class RestoredHullsTests
|
|||
|
||||
for (uint seed = 1; seed <= 10; seed++)
|
||||
{
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), settings, _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), settings, _ => { });
|
||||
var random = new Random(seed);
|
||||
var generator = new LoadoutGenerator(ref random, items, null, null, null, .5f);
|
||||
var pack = generator.GenerateShipLoadout(candidate => candidate == hull);
|
||||
|
|
@ -729,7 +729,7 @@ public sealed class RestoredHullsTests
|
|||
var (seed, zoneName) = NoLagrangeCandidateZone();
|
||||
var galaxy = BuildGalaxyWithForcedEntrance(cache, seed, zoneName);
|
||||
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), Settings(), _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), Settings(), _ => { });
|
||||
var pack = ZoneGenerator.GenerateZone(items, TutorialZoneSettings(), AuthoredPlanetSettings(), galaxy, galaxy.Entrance, isTutorial: true);
|
||||
|
||||
var station = pack.Entities.OfType<OrbitalEntityPack>()
|
||||
|
|
@ -780,7 +780,7 @@ public sealed class RestoredHullsTests
|
|||
{
|
||||
using var cache = OpenReadOnlyRealCatalogWithScratchRun(gameData, scratchRun);
|
||||
var galaxy = new Galaxy(TutorialGalaxySettings(), TutorialBackgroundSettings(), cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed: 1u);
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), Settings(), _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), Settings(), _ => { });
|
||||
|
||||
int StationCount(GalaxyZone zone, bool isTutorial) =>
|
||||
ZoneGenerator.GenerateZone(items, TutorialZoneSettings(), AuthoredPlanetSettings(), galaxy, zone, isTutorial)
|
||||
|
|
@ -820,7 +820,7 @@ public sealed class RestoredHullsTests
|
|||
{
|
||||
using var cache = OpenReadOnlyRealCatalogWithScratchRun(gameData, scratchRun);
|
||||
var galaxy = new Galaxy(TutorialGalaxySettings(), TutorialBackgroundSettings(), cache, new PlayerSettings(), new DirectoryInfo(Path.GetTempPath()), _ => { }, null, seed: 1u);
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), Settings(), _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), Settings(), _ => { });
|
||||
|
||||
var pack = ZoneGenerator.GenerateZone(items, TutorialZoneSettings(), AuthoredPlanetSettings(), galaxy, galaxy.Entrance, isTutorial: true);
|
||||
var stationCount = pack.Entities.OfType<OrbitalEntityPack>().Count(e => (cache.Get(e.Hull.Data) as HullData)?.HullType == HullType.Station);
|
||||
|
|
@ -904,7 +904,7 @@ public sealed class RestoredHullsTests
|
|||
var zone = galaxy.Zones.Single(z => z.Name == zoneName);
|
||||
Assert.NotSame(galaxy.Entrance, zone);
|
||||
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), Settings(), _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), Settings(), _ => { });
|
||||
var pack = ZoneGenerator.GenerateZone(items, TutorialZoneSettings(), AuthoredPlanetSettings(), galaxy, zone, isTutorial: true);
|
||||
|
||||
// Confirms this fixture still has no genuine (non-rosette) Lagrange candidate -- a regression here
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ public sealed class RoleAuthoringTests : IDisposable
|
|||
});
|
||||
cache.FlushAsync().Wait();
|
||||
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), Settings(), _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), Settings(), _ => { });
|
||||
var cheapLot = items.CreateLot(cache.GetAll<FactionProductData>().Single(p => p.Name == "Lensed Economy"));
|
||||
var premiumLot = items.CreateLot(cache.GetAll<FactionProductData>().Single(p => p.Name == "Lensed Premium"));
|
||||
var cheapInstance = (EquippableItem) items.CreateInstance(cheapLot);
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ public sealed partial class RunStartTests
|
|||
private static Faction Zhestokost(Galaxy galaxy) => galaxy.Factions.Single(faction => faction.Name == "Zhestokost");
|
||||
|
||||
private ItemManager LoggingItems(List<string> log) =>
|
||||
new ItemManager(_cache, new ProvenanceLedger(), AuthoredSettings.Load(FindRepoRoot()).Read<GameplaySettings>("GameplaySettings"), log.Add);
|
||||
new ItemManager(1u, _cache, new ProvenanceLedger(), AuthoredSettings.Load(FindRepoRoot()).Read<GameplaySettings>("GameplaySettings"), log.Add);
|
||||
|
||||
private static string HullName(ItemManager items, EntityPack pack) => items.GetData(pack.Hull).Name;
|
||||
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ public sealed class RunSaveTests : IDisposable
|
|||
var galaxy = new Galaxy(cache, cache.GetGlobal<SavedGame>(), _ => { });
|
||||
var ledger = new ProvenanceLedger();
|
||||
for (var i = 1; i <= 2; i++) ledger.Lots[i] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
var items = new ItemManager(cache, ledger, TestSettings(), _ => { });
|
||||
var items = new ItemManager(1u, cache, ledger, TestSettings(), _ => { });
|
||||
var zone = new Zone(items, new PlanetSettings(), new ZonePack(), galaxy.Zones[0], galaxy);
|
||||
galaxy.Zones[0].Contents = zone;
|
||||
Ship Make(string hull, int lot) => new Ship(items, zone, new EquippableItem
|
||||
|
|
@ -135,7 +135,7 @@ public sealed class RunSaveTests : IDisposable
|
|||
using (var cache = Open())
|
||||
{
|
||||
var galaxy = new Galaxy(cache, cache.GetGlobal<SavedGame>(), _ => { });
|
||||
var itemManager = new ItemManager(cache, new ProvenanceLedger(), TestSettings(), _ => { });
|
||||
var itemManager = new ItemManager(1u, cache, new ProvenanceLedger(), TestSettings(), _ => { });
|
||||
galaxy.Zones[0].Contents = new Zone(itemManager, new PlanetSettings(), new ZonePack(), galaxy.Zones[0], galaxy);
|
||||
var (game, zones) = RunSave.Capture(cache, galaxy, galaxy.Zones[0].Contents, null, new SavedActionBarBinding[0], 0);
|
||||
RunSave.Commit(cache, game, zones, RunSave.Lots(cache));
|
||||
|
|
@ -160,7 +160,7 @@ public sealed class RunSaveTests : IDisposable
|
|||
using (var cache = Open())
|
||||
{
|
||||
var galaxy = new Galaxy(cache, cache.GetGlobal<SavedGame>(), _ => { });
|
||||
var itemManager = new ItemManager(cache, new ProvenanceLedger(), TestSettings(), _ => { });
|
||||
var itemManager = new ItemManager(1u, cache, new ProvenanceLedger(), TestSettings(), _ => { });
|
||||
galaxy.Zones[0].Contents = new Zone(itemManager, new PlanetSettings(), new ZonePack(), galaxy.Zones[0], galaxy);
|
||||
var (game, zones) = RunSave.Capture(cache, galaxy, galaxy.Zones[0].Contents, null, new SavedActionBarBinding[0], 4242);
|
||||
RunSave.Commit(cache, game, zones, RunSave.Lots(cache));
|
||||
|
|
@ -186,7 +186,7 @@ public sealed class RunSaveTests : IDisposable
|
|||
{
|
||||
var galaxy = new Galaxy(cache, cache.GetGlobal<SavedGame>(), _ => { });
|
||||
Assert.Equal(0xC0FFEE42u, galaxy.Seed);
|
||||
var itemManager = new ItemManager(cache, new ProvenanceLedger(), TestSettings(), _ => { });
|
||||
var itemManager = new ItemManager(1u, cache, new ProvenanceLedger(), TestSettings(), _ => { });
|
||||
galaxy.Zones[0].Contents = new Zone(itemManager, new PlanetSettings(), new ZonePack(), galaxy.Zones[0], galaxy);
|
||||
// The zone's drop dice are its name's seed mixed with the run's.
|
||||
Assert.Equal("Zone 0".StableHash() ^ SimulationDice.Mix(0xC0FFEE42u), galaxy.Zones[0].Contents.DropSeed);
|
||||
|
|
@ -207,7 +207,7 @@ public sealed class RunSaveTests : IDisposable
|
|||
Assert.Equal(0u, cache.GetGlobal<SavedGame>().Seed);
|
||||
var galaxy = new Galaxy(cache, cache.GetGlobal<SavedGame>(), _ => { });
|
||||
Assert.Equal(0u, galaxy.Seed);
|
||||
var itemManager = new ItemManager(cache, new ProvenanceLedger(), TestSettings(), _ => { });
|
||||
var itemManager = new ItemManager(1u, cache, new ProvenanceLedger(), TestSettings(), _ => { });
|
||||
var zone = new Zone(itemManager, new PlanetSettings(), new ZonePack(), galaxy.Zones[0], galaxy);
|
||||
Assert.Equal("Zone 0".StableHash(), zone.CombatSeed);
|
||||
Assert.Equal(zone.CombatSeed, zone.DropSeed);
|
||||
|
|
@ -408,7 +408,7 @@ public sealed class RunSaveTests : IDisposable
|
|||
using (var cache = Open())
|
||||
{
|
||||
var random = new Random(7);
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), TestSettings(), _ => { });
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), TestSettings(), _ => { });
|
||||
var generator = new LoadoutGenerator(ref random, items, null, null, null, .5f);
|
||||
pack = generator.GenerateShipLoadout(candidate => candidate.Name == "Runabout");
|
||||
Assert.NotNull(pack);
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ public sealed partial class RunStartTests : IDisposable
|
|||
_galaxy = new Galaxy(tutorial, authored.Read<SectorBackgroundSettings>("TutorialBackgroundSettings"),
|
||||
_cache, new PlayerSettings(),
|
||||
Directory.CreateDirectory(Path.Combine(_root, "Narrative")), _ => { }, null, GalaxySeed);
|
||||
_items = new ItemManager(_cache, new ProvenanceLedger(), authored.Read<GameplaySettings>("GameplaySettings"), _ => { });
|
||||
_items = new ItemManager(1u, _cache, new ProvenanceLedger(), authored.Read<GameplaySettings>("GameplaySettings"), _ => { });
|
||||
_protagonist = _galaxy.ResolveFaction(tutorial.ProtagonistFaction);
|
||||
|
||||
// GameSettings.StartingHullName is a plain field of the Unity settings object, not a ServerShared subtree
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ public sealed partial class RunStartTests
|
|||
}
|
||||
|
||||
public override string Name => "Scripted";
|
||||
public override string Id => "test-scripted";
|
||||
public override string Brief => "A test's own scenario.";
|
||||
public override uint Seed => _seed;
|
||||
public override bool Ambient => _ambient;
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ public sealed class ShieldReserveTests : IDisposable
|
|||
private (Ship ship, Shield shield) BuildShipWithShield(CultCache cache, float reactorGeneration)
|
||||
{
|
||||
var ledger = new ProvenanceLedger();
|
||||
var items = new ItemManager(cache, ledger, Settings(), _ => { });
|
||||
var items = new ItemManager(1u, cache, ledger, Settings(), _ => { });
|
||||
var zone = new Zone(items, new PlanetSettings(), new ZonePack(), new GalaxyZone { Name = "Test Zone", Owner = null }, null);
|
||||
var hull = Mint(cache, items, cache.GetByName<HullData>("Skiff"));
|
||||
var ship = new Ship(items, zone, hull, new EntitySettings());
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue