determinism-ledger: Scenario.Id, a stable kebab identity for matches and replays

Every scenario, and the test Scripted scenario, names an Id; Name is
display only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
soul 2026-10-21 19:01:43 +02:00
commit 9379712c78
19 changed files with 22 additions and 0 deletions

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

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