AI draws from its own dice: the range-sample offset and the patrol circuit leave ItemManager.Random
Combat.SampleDps and Zone.CreateAgent's patrol circuit drew from the item stream, so minting items moved how a pilot fights and where it patrols. Both now draw SimulationDice.For(CombatSeed, stream, Agent.Id); Agent.Id is the zone-scoped ordinal Zone.NextAgentId gives a pilot at admission (ruling ai-sample-offset-off-item-stream). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
2891b9a807
commit
72d7e25df0
5 changed files with 40 additions and 3 deletions
|
|
@ -23,6 +23,9 @@ public class Agent
|
|||
private VelocityLimit _velocityLimit;
|
||||
|
||||
public AgentTask Task { get; set; }
|
||||
// Zone-scoped, from 1: the ordinal of this pilot's own dice, set by Zone.CreateAgent as it admits the pilot (0 for
|
||||
// an agent built outside a zone's admission).
|
||||
public uint Id { get; init; }
|
||||
public Ship Ship { get; }
|
||||
public ItemManager ItemManager { get; }
|
||||
public GameplaySettings Settings { get; }
|
||||
|
|
|
|||
|
|
@ -173,7 +173,10 @@ public class CombatState : BaseState
|
|||
maxRange = weapon.Range;
|
||||
}
|
||||
|
||||
var offset = _agent.ItemManager.Random.NextFloat((maxRange - minRange) / DPS_SAMPLE_COUNT);
|
||||
// The pilot's own dice, a pure function of the zone seed and its id: the same offset at every resample, and
|
||||
// nothing that mints an item can move it.
|
||||
var offset = SimulationDice.For(_agent.Ship.Zone.CombatSeed, SimulationDice.CombatSampleStream, _agent.Id)
|
||||
.NextFloat((maxRange - minRange) / DPS_SAMPLE_COUNT);
|
||||
var optimumDPS = 0f;
|
||||
for (int i = 0; i < DPS_SAMPLE_COUNT; i++)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -10,6 +10,10 @@ public static class SimulationDice
|
|||
{
|
||||
public const uint LootStream = 1;
|
||||
public const uint MineStream = 2;
|
||||
// A pilot's own decisions, ordinal Agent.Id: its preferred engagement range and its patrol circuit. AI behaviour
|
||||
// never rides ItemManager.Random, so minting items cannot change how a pilot fights.
|
||||
public const uint CombatSampleStream = 3;
|
||||
public const uint PatrolStream = 4;
|
||||
|
||||
// 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,
|
||||
|
|
|
|||
|
|
@ -133,6 +133,10 @@ public class Zone
|
|||
// The death ordinal of the loot dice: the nth death in this zone rolls on SimulationDice.For(CombatSeed, LootStream, n).
|
||||
private uint _deaths;
|
||||
|
||||
// The zone-scoped agent ordinal, from 1, never reused: the key of a pilot's own dice.
|
||||
private uint _lastAgentId;
|
||||
public uint NextAgentId() => ++_lastAgentId;
|
||||
|
||||
// One death subscription per member entity: Admit creates it together with the membership, Remove disposes it
|
||||
// together with the membership, and nothing else touches either.
|
||||
private readonly Dictionary<Entity, IDisposable> _deathWatches = new Dictionary<Entity, IDisposable>();
|
||||
|
|
@ -301,9 +305,11 @@ public class Zone
|
|||
var key = ship.Faction ?? Unaffiliated;
|
||||
if (!_flights.TryGetValue(key, out var flight))
|
||||
_flights[key] = flight = new Flight(ship.Faction?.Doctrine, _itemManager.GameplaySettings);
|
||||
var agent = new Minion(ship, flight.Combatant);
|
||||
var id = NextAgentId();
|
||||
var agent = new Minion(ship, flight.Combatant) { Id = id };
|
||||
var task = new PatrolOrbitsTask();
|
||||
task.Circuit = Orbits.OrderBy(_ => _itemManager.Random.NextFloat()).Take(4).Select(x => x.Key).ToArray();
|
||||
var dice = SimulationDice.For(CombatSeed, SimulationDice.PatrolStream, id);
|
||||
task.Circuit = Orbits.OrderBy(_ => dice.NextFloat()).Take(4).Select(x => x.Key).ToArray();
|
||||
flight.Join(agent, task);
|
||||
return agent;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue