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:
soul 2026-10-22 00:44:50 +02:00
commit 95903ab00e
118 changed files with 1157 additions and 297 deletions

View file

@ -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)
{

View file

@ -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());

View file

@ -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.

View file

@ -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
{

View file

@ -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);
}

View file

@ -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 };

View file

@ -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 },

View file

@ -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());

View file

@ -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;

View file

@ -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

View file

@ -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;

View file

@ -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;

View file

@ -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)));

View file

@ -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;
}
}

View file

@ -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; }

View file

@ -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.";

View file

@ -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 " +

View file

@ -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 " +

View file

@ -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 " +

View file

@ -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.";

View file

@ -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 " +

View file

@ -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;

View file

@ -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 " +

View file

@ -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;

View file

@ -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.";

View file

@ -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;

View file

@ -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();

View file

@ -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.";

View file

@ -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 " +

View file

@ -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;

View file

@ -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();

View file

@ -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.";

View file

@ -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,

View file

@ -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

View 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`.

View file

@ -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`).

View file

@ -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

View file

@ -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()

View file

@ -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());

View file

@ -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)
{

View file

@ -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());

View file

@ -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));

View file

@ -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++)

View file

@ -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);

View file

@ -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) } },

View file

@ -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);

View 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}");
}
}

View file

@ -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) } },

View file

@ -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"));

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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);
}

View file

@ -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"));

View file

@ -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"));

View file

@ -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");

View file

@ -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 --

View file

@ -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);

View file

@ -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"));

View file

@ -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"));

View file

@ -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

View file

@ -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);

View file

@ -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);

View file

@ -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()

View file

@ -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()

View file

@ -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.

View file

@ -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);
}

View file

@ -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());

View file

@ -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 }));

View file

@ -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()

View file

@ -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);

View file

@ -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);
}

View file

@ -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;

View file

@ -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) } },

View file

@ -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);

View file

@ -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);

View file

@ -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;

View file

@ -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 };

View file

@ -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,

View file

@ -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)

View file

@ -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);

View file

@ -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) } },

View file

@ -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)

View file

@ -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) } },

View file

@ -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()

View file

@ -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());

View file

@ -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());

View file

@ -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());

View file

@ -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()

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);

View file

@ -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

View file

@ -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);

View file

@ -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;

View file

@ -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);

View file

@ -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

View file

@ -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;

View file

@ -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