{"TotalCount":71,"Files":[{"Ident":"notpointless.chomnr_adaptive_director","Path":"Code/SboxDirector/PopulationLedger.cs","FileName":"PopulationLedger.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":337987,"Code":"namespace SboxDirector;\n\npublic sealed class PopulationLedger\n{\n    readonly PopulationConfiguration _configuration;\n    readonly Dictionary\u003CEncounterKind, int\u003E _reserved = new();\n    readonly Dictionary\u003Clong, PendingReservation\u003E _pending = new();\n    public PopulationLedger(PopulationConfiguration configuration) { _configuration = configuration; }\n\n    public int Available(EncounterKind kind, PopulationSnapshot population)\n        =\u003E Available(kind, population, ConfiguredLimit(kind));\n    public int ConfiguredLimit(EncounterKind kind) =\u003E _configuration.Limits.TryGetValue(kind, out var configured) ? configured : 0;\n    public int Available(EncounterKind kind, PopulationSnapshot population, int limit)\n    {\n        return Math.Max(0, limit - population[kind] - (_reserved.TryGetValue(kind, out var value) ? value : 0));\n    }\n    public bool TryReserve(long requestId, EncounterKind kind, int count, PopulationSnapshot population, double expiresAt = double.PositiveInfinity)\n        =\u003E TryReserveWithLimit(requestId, kind, count, population, ConfiguredLimit(kind), expiresAt);\n    public bool TryReserveWithLimit(long requestId, EncounterKind kind, int count, PopulationSnapshot population, int limit, double expiresAt = double.PositiveInfinity)\n    {\n        if (count \u003C= 0 || limit \u003C 0 || _pending.ContainsKey(requestId) || Available(kind, population, limit) \u003C count) return false;\n        _pending[requestId] = new(requestId, kind, count, expiresAt); _reserved[kind] = (_reserved.TryGetValue(kind, out var value) ? value : 0) \u002B count; return true;\n    }\n    public void Resolve(SpawnRequestResult result)\n    {\n        if (result.Result == RequestResultKind.Deferred) return;\n        if (!_pending.Remove(result.RequestId, out var pending)) return;\n        _reserved[pending.Kind] = Math.Max(0, _reserved[pending.Kind] - pending.Count);\n    }\n    public IReadOnlyList\u003Clong\u003E CancelAll()\n    {\n        var ids = _pending.Keys.OrderBy(x =\u003E x).ToArray(); _pending.Clear(); _reserved.Clear(); return ids;\n    }\n    public IReadOnlyList\u003Clong\u003E Expire(double time)\n    {\n        var expired = _pending.Values.Where(x =\u003E x.ExpiresAt \u003C= time).Select(x =\u003E x.RequestId).ToArray();\n        foreach (var id in expired) Resolve(new SpawnRequestResult(id, RequestResultKind.Failed, 0, \u0022request timeout\u0022));\n        return expired;\n    }\n    public PopulationLedgerState Capture() =\u003E new(_pending.Values.OrderBy(x =\u003E x.RequestId).ToArray());\n    public void Restore(PopulationLedgerState state)\n    {\n        _pending.Clear(); _reserved.Clear();\n        foreach (var item in state.Pending) { _pending[item.RequestId] = item; _reserved[item.Kind] = (_reserved.TryGetValue(item.Kind, out var count) ? count : 0) \u002B item.Count; }\n    }\n    public IReadOnlyDictionary\u003CEncounterKind, int\u003E Reservations =\u003E _reserved;\n    public IReadOnlyList\u003CPendingReservation\u003E Pending =\u003E _pending.Values.OrderBy(x =\u003E x.RequestId).ToArray();\n}\n\npublic readonly record struct PendingReservation(long RequestId, EncounterKind Kind, int Count, double ExpiresAt);\npublic readonly record struct PopulationLedgerState(IReadOnlyList\u003CPendingReservation\u003E Pending);\n"},{"Ident":"notpointless.chomnr_adaptive_director","Path":"examples/PressureReportingExample.cs","FileName":"PressureReportingExample.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":337987,"Code":"using SboxDirector;\n\nnamespace AdaptiveDirectorExamples;\n\n/// \u003Csummary\u003E\n/// Centralizes the conversion from game events to the Director\u0027s five pressure\n/// severities. Call this service only from authoritative gameplay code.\n/// \u003C/summary\u003E\npublic sealed class PressureReportingExample\n{\n    readonly Action\u003CPressureEvent, double\u003E _report;\n\n    public PressureReportingExample(Action\u003CPressureEvent, double\u003E report)\n    {\n        _report = report ?? throw new ArgumentNullException(nameof(report));\n    }\n\n    public static PressureReportingExample ForDirector(DirectorCore director) =\u003E new(director.ApplyPressure);\n    public static PressureReportingExample ForHostRunner(DirectorHostRunner runner) =\u003E new(runner.ReportPressure);\n\n    /// \u003Csummary\u003EnormalizedDamage is damage divided by the participant\u0027s full health.\u003C/summary\u003E\n    public void OnDamage(string participantId, float normalizedDamage, double gameTime)\n    {\n        if (!float.IsFinite(normalizedDamage) || normalizedDamage \u003C 0) throw new ArgumentOutOfRangeException(nameof(normalizedDamage));\n        var severity = normalizedDamage switch\n        {\n            \u003C .20f =\u003E PressureSeverity.Moderate,\n            \u003C .50f =\u003E PressureSeverity.Major,\n            _ =\u003E PressureSeverity.Critical\n        };\n        Report(participantId, severity, \u0022damage\u0022, gameTime);\n    }\n\n    public void OnLedgeDanger(string participantId, double gameTime) =\u003E Report(participantId, PressureSeverity.Major, \u0022ledge_danger\u0022, gameTime);\n\n    public void OnIncapacitated(string participantId, int previousReviveCount, double gameTime)\n    {\n        if (previousReviveCount \u003C 0) throw new ArgumentOutOfRangeException(nameof(previousReviveCount));\n        Report(participantId, previousReviveCount == 0 ? PressureSeverity.Maximum : PressureSeverity.Critical, \u0022incapacitated\u0022, gameTime);\n    }\n\n    /// \u003Csummary\u003ECompatibility behavior: only cooperative Easy/Normal revival reaches maximum pressure.\u003C/summary\u003E\n    public void OnDefibrillatorRevived(string participantId, string baseMode, string difficulty, double gameTime)\n    {\n        var compatible = string.Equals(baseMode, \u0022coop\u0022, StringComparison.OrdinalIgnoreCase)\n            \u0026\u0026 (string.Equals(difficulty, \u0022easy\u0022, StringComparison.OrdinalIgnoreCase)\n                || string.Equals(difficulty, \u0022normal\u0022, StringComparison.OrdinalIgnoreCase));\n        if (compatible) Report(participantId, PressureSeverity.Maximum, \u0022defibrillator_revival\u0022, gameTime);\n    }\n\n    /// \u003Csummary\u003EMaps the recovered 150/500-unit proximity bands. Set typedThreat for special/nonzero threat types.\u003C/summary\u003E\n    public void OnThreatKilledNearby(string participantId, float distance, bool bossOrDormantHazard, bool typedThreat, float ordinaryThreatSpeed, double gameTime)\n    {\n        if (!float.IsFinite(distance) || distance \u003C 0 || !float.IsFinite(ordinaryThreatSpeed) || ordinaryThreatSpeed \u003C 0) throw new ArgumentOutOfRangeException(nameof(distance));\n        if (distance \u003E 500) return;\n\n        PressureSeverity severity;\n        if (bossOrDormantHazard) severity = distance \u003C= 150 ? PressureSeverity.Critical : PressureSeverity.Major;\n        else if (typedThreat) severity = distance \u003C= 150 ? PressureSeverity.Moderate : PressureSeverity.Minor;\n        else severity = distance \u003C= 150 \u0026\u0026 ordinaryThreatSpeed \u003E 150 ? PressureSeverity.Moderate : PressureSeverity.Minor;\n        Report(participantId, severity, \u0022nearby_threat_death\u0022, gameTime);\n    }\n\n    /// \u003Csummary\u003EUse custom mappings for game-specific events while keeping them in one auditable place.\u003C/summary\u003E\n    public void OnCustomDanger(string participantId, PressureSeverity severity, string reason, double gameTime) =\u003E Report(participantId, severity, reason, gameTime);\n\n    void Report(string participantId, PressureSeverity severity, string reason, double gameTime)\n    {\n        if (string.IsNullOrWhiteSpace(participantId)) throw new ArgumentException(\u0022A participant id is required.\u0022, nameof(participantId));\n        if (!double.IsFinite(gameTime)) throw new ArgumentOutOfRangeException(nameof(gameTime));\n        _report(new PressureEvent(participantId, severity, reason), gameTime);\n    }\n}\n\n/// \u003Csummary\u003EExample diagnostic wiring for a developer HUD or server log.\u003C/summary\u003E\npublic sealed class DirectorDiagnosticsExample\n{\n    readonly DirectorTelemetryBuffer _telemetry;\n    public DirectorDiagnosticsExample(DirectorCore director) { _telemetry = new DirectorTelemetryBuffer(director, capacity: 200); }\n    public IReadOnlyList\u003CDirectorDiagnostic\u003E ReadForDebugHud() =\u003E _telemetry.Snapshot();\n    public void Clear() =\u003E _telemetry.Clear();\n}\n"},{"Ident":"notpointless.chomnr_adaptive_director","Path":"SboxDirector/MusicModels.cs","FileName":"MusicModels.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":337987,"Code":"#nullable enable annotations\nnamespace SboxDirector;\n\npublic enum MusicDecisionKind { SetMixerLayer, Play, StopEvent, StopTrack, StopAll, General }\npublic enum MusicPriority { Low = 1, Medium = 2, High = 3, Critical = 4 }\npublic enum MusicTriggerKind { Play, StopEvent, StopTrack, StopAll, General, Reset, ScenarioEnded, ParticipantRestored, AmbientMob, MobSpawn, MobSpawnBehind, LargeAreaRevealed, LandmarkRevealed }\npublic enum MusicRuntimeActionKind { Play, StopEvent, StopTrack, StopAll, SetTrackVolume, SetMixerLayer, General }\npublic enum MusicDecisionResultKind { Accepted, Deferred, RejectedUnknownEvent, RejectedBlocked, RejectedLifecycle, RejectedPriority, FailedAdapter }\n\n[Flags]\npublic enum MusicMasterFlags\n{\n    None = 0,\n    PlayToEnd = 1,\n    PlaySplit = 2,\n    DontDisengage = 4,\n    DontEngage = 8,\n    AllowAfterDeath = 16,\n    AllowAfterScenarioEnd = 32\n}\n\npublic static class MusicMixerLayers\n{\n    public const string MobBeating = \u0022mob_beating\u0022;\n    public const string MobRules = \u0022mob_rules\u0022;\n    public const string HazardRage = \u0022hazard_rage\u0022;\n    public const string Combat = \u0022combat\u0022;\n    public const string CombatSecondary = \u0022combat_secondary\u0022;\n    public const string CombatClose = \u0022combat_close\u0022;\n    public const string Adrenaline = \u0022adrenaline\u0022;\n    public const string Checkpoint = \u0022checkpoint\u0022;\n    public const string Ambient = \u0022ambient\u0022;\n}\n\npublic sealed record SpecialMusicThreat(string ArchetypeId, float Distance, string EmitterId = \u0022\u0022, DirectorVector? Position = null);\npublic sealed record PositionalMusicCandidate(string Id, DirectorVector Position, bool Eligible = true);\npublic sealed record WanderingMusicHazard(string Id, float Anger, DirectorVector Position);\n\npublic sealed class MusicParticipantSnapshot\n{\n    public string Id { get; init; } = \u0022\u0022;\n    public bool IsEligible { get; init; } = true;\n    public bool IsAlive { get; init; } = true;\n    public bool IsIncapacitated { get; init; }\n    public bool DynamicMusicAllowed { get; init; } = true;\n    public float CumulativeDamage { get; init; }\n    public float WeaponActivity { get; init; }\n    public bool InflictedDamage { get; init; }\n    public int VisibleCommonThreats { get; init; }\n    public int ActiveCommonThreats { get; init; }\n    public int ScannedCommonThreats { get; init; }\n    public float AttackingCommonFactor { get; init; }\n    public float CloseCommonAction { get; init; }\n    public float VeryCloseCommonFactor { get; init; }\n    public bool MobPressureActive { get; init; }\n    public bool GlobalCombatActive { get; init; }\n    public bool BossPresent { get; init; }\n    public string BossArchetypeId { get; init; } = \u0022\u0022;\n    public bool HazardPresent { get; init; }\n    public bool HazardAttacking { get; init; }\n    public bool HazardBurning { get; init; }\n    public float HazardRage { get; init; }\n    public string HazardEmitterId { get; init; } = \u0022\u0022;\n    public DirectorVector? HazardPosition { get; init; }\n    public float Adrenaline { get; init; }\n    public bool InCheckpoint { get; init; }\n    public IReadOnlyList\u003CSpecialMusicThreat\u003E SpecialThreats { get; init; } = Array.Empty\u003CSpecialMusicThreat\u003E();\n    public IReadOnlyList\u003CPositionalMusicCandidate\u003E PositionalCandidates { get; init; } = Array.Empty\u003CPositionalMusicCandidate\u003E();\n    public IReadOnlyList\u003CWanderingMusicHazard\u003E WanderingHazards { get; init; } = Array.Empty\u003CWanderingMusicHazard\u003E();\n\n    public void Validate()\n    {\n        if (string.IsNullOrWhiteSpace(Id)) throw new ArgumentException(\u0022A music participant id is required.\u0022);\n        var values = new[] { CumulativeDamage, WeaponActivity, AttackingCommonFactor, CloseCommonAction, VeryCloseCommonFactor, HazardRage, Adrenaline };\n        if (values.Any(x =\u003E !float.IsFinite(x)) || CumulativeDamage \u003C 0 || VisibleCommonThreats \u003C 0 || ActiveCommonThreats \u003C 0 || ScannedCommonThreats \u003C 0) throw new ArgumentException(\u0022Music participant measurements must be finite and non-negative.\u0022);\n        if (WeaponActivity is \u003C 0 or \u003E 1 || AttackingCommonFactor is \u003C 0 or \u003E 1 || CloseCommonAction is \u003C 0 or \u003E 1 || VeryCloseCommonFactor is \u003C 0 or \u003E 1 || HazardRage is \u003C 0 or \u003E 1 || Adrenaline is \u003C 0 or \u003E 1) throw new ArgumentException(\u0022Normalized music inputs must be in [0,1].\u0022);\n        if (SpecialThreats.Any(x =\u003E string.IsNullOrWhiteSpace(x.ArchetypeId) || !float.IsFinite(x.Distance) || x.Distance \u003C 0)) throw new ArgumentException(\u0022Special music threats require an archetype and finite non-negative distance.\u0022);\n        if (PositionalCandidates.Any(x =\u003E string.IsNullOrWhiteSpace(x.Id))) throw new ArgumentException(\u0022Positional music candidates require stable ids.\u0022);\n        if (WanderingHazards.Any(x =\u003E string.IsNullOrWhiteSpace(x.Id) || !float.IsFinite(x.Anger) || x.Anger is \u003C 0 or \u003E 1)) throw new ArgumentException(\u0022Wandering music hazards require stable ids and anger in [0,1].\u0022);\n    }\n}\n\npublic sealed class MusicWorldSnapshot\n{\n    public double Time { get; init; }\n    public IReadOnlyList\u003CMusicParticipantSnapshot\u003E Participants { get; init; } = Array.Empty\u003CMusicParticipantSnapshot\u003E();\n    public bool ScenarioEnded { get; init; }\n\n    public void Validate()\n    {\n        if (!double.IsFinite(Time)) throw new ArgumentException(\u0022Music world time must be finite.\u0022);\n        foreach (var participant in Participants) participant.Validate();\n        if (Participants.Select(x =\u003E x.Id).Distinct(StringComparer.Ordinal).Count() != Participants.Count) throw new ArgumentException(\u0022Music participant ids must be unique.\u0022);\n    }\n}\n\npublic sealed record MusicGameplayEvent(long Sequence, MusicTriggerKind Kind, string ParticipantId = \u0022\u0022, string TargetId = \u0022\u0022, float FadeSeconds = 0, string EmitterId = \u0022\u0022, DirectorVector? Position = null)\n{\n    public IReadOnlyList\u003Cstring\u003E Arguments { get; init; } = Array.Empty\u003Cstring\u003E();\n}\n\npublic sealed record MusicDecision(long DecisionId, MusicDecisionKind Kind, string ParticipantId, string TargetId, string Reason)\n{\n    public string LayerId { get; init; } = \u0022\u0022;\n    public float Value { get; init; }\n    public float FadeSeconds { get; init; }\n    public float StartOffsetSeconds { get; init; }\n    public string EmitterId { get; init; } = \u0022\u0022;\n    public DirectorVector? Position { get; init; }\n    public IReadOnlyList\u003Cstring\u003E Arguments { get; init; } = Array.Empty\u003Cstring\u003E();\n}\n\npublic sealed class MusicDecisionBatch\n{\n    public double Time { get; init; }\n    public List\u003CMusicDecision\u003E Decisions { get; } = new();\n}\n\npublic sealed record MusicTimingTag(int Index, double OffsetSeconds);\n\npublic sealed class MusicEventDefinition\n{\n    public string EventId { get; init; } = \u0022\u0022;\n    public string TrackId { get; init; } = \u0022main\u0022;\n    public MusicPriority Priority { get; init; } = MusicPriority.Low;\n    public MusicMasterFlags Flags { get; init; }\n    public float DefaultFadeOutSeconds { get; init; }\n    public double DelaySeconds { get; init; }\n    public string? AutoQueueEventId { get; init; }\n    public IReadOnlyList\u003Cstring\u003E BlockTrackList { get; init; } = Array.Empty\u003Cstring\u003E();\n    public IReadOnlyList\u003Cstring\u003E DuckTrackList { get; init; } = Array.Empty\u003Cstring\u003E();\n    public IReadOnlyList\u003Cstring\u003E StopTrackList { get; init; } = Array.Empty\u003Cstring\u003E();\n    public IReadOnlyList\u003CMusicTimingTag\u003E TimingTags { get; init; } = Array.Empty\u003CMusicTimingTag\u003E();\n    public double LoopSeconds { get; init; }\n    public string? SyncTrackId { get; init; }\n    public int? SyncTagIndex { get; init; }\n    public double SyncTagDelaySeconds { get; init; }\n    public double SyncTagDelayMultiplier { get; init; } = 1;\n\n    public void Validate()\n    {\n        if (string.IsNullOrWhiteSpace(EventId) || string.IsNullOrWhiteSpace(TrackId)) throw new ArgumentException(\u0022Music events require event and track ids.\u0022);\n        if (!float.IsFinite(DefaultFadeOutSeconds) || DefaultFadeOutSeconds \u003C 0 || !double.IsFinite(DelaySeconds) || DelaySeconds \u003C 0 || !double.IsFinite(LoopSeconds) || LoopSeconds \u003C 0 || !double.IsFinite(SyncTagDelaySeconds) || !double.IsFinite(SyncTagDelayMultiplier) || SyncTagDelayMultiplier \u003C 0) throw new ArgumentException(\u0022Music timing values must be finite and non-negative where applicable.\u0022);\n        if (TimingTags.Any(x =\u003E x.Index \u003C 0 || !double.IsFinite(x.OffsetSeconds) || x.OffsetSeconds \u003C 0) || TimingTags.Select(x =\u003E x.Index).Distinct().Count() != TimingTags.Count) throw new ArgumentException(\u0022Music timing tags require unique non-negative indices and offsets.\u0022);\n        if (BlockTrackList.Concat(DuckTrackList).Concat(StopTrackList).Any(string.IsNullOrWhiteSpace)) throw new ArgumentException(\u0022Music track lists cannot contain empty ids.\u0022);\n    }\n}\n\npublic interface IMusicEventCatalog\n{\n    bool TryGet(string eventId, out MusicEventDefinition definition);\n}\n\npublic sealed class MusicEventCatalog : IMusicEventCatalog\n{\n    readonly Dictionary\u003Cstring, MusicEventDefinition\u003E _events;\n    public MusicEventCatalog(IEnumerable\u003CMusicEventDefinition\u003E events)\n    {\n        _events = new(StringComparer.Ordinal);\n        foreach (var definition in events) { definition.Validate(); if (!_events.TryAdd(definition.EventId, definition)) throw new ArgumentException($\u0022Duplicate music event \u0027{definition.EventId}\u0027.\u0022); }\n        foreach (var definition in _events.Values) if (definition.AutoQueueEventId is { Length: \u003E 0 } next \u0026\u0026 !_events.ContainsKey(next)) throw new ArgumentException($\u0022Music event \u0027{definition.EventId}\u0027 queues unknown event \u0027{next}\u0027.\u0022);\n    }\n    public bool TryGet(string eventId, out MusicEventDefinition definition) =\u003E _events.TryGetValue(eventId, out definition!);\n}\n\npublic sealed record MusicRuntimeAction(long ActionId, MusicRuntimeActionKind Kind, string TargetId)\n{\n    public string EventId { get; init; } = \u0022\u0022;\n    public string TrackId { get; init; } = \u0022\u0022;\n    public string LayerId { get; init; } = \u0022\u0022;\n    public float Value { get; init; }\n    public float FadeSeconds { get; init; }\n    public float StartOffsetSeconds { get; init; }\n    public string EmitterId { get; init; } = \u0022\u0022;\n    public DirectorVector? Position { get; init; }\n    public IReadOnlyList\u003Cstring\u003E Arguments { get; init; } = Array.Empty\u003Cstring\u003E();\n}\n\npublic sealed record MusicDecisionResult(long DecisionId, MusicDecisionResultKind Result, string Detail = \u0022\u0022, long InstanceId = 0);\n\npublic sealed class MusicRuntimeBatch\n{\n    public double Time { get; init; }\n    public List\u003CMusicRuntimeAction\u003E Actions { get; } = new();\n    public List\u003CMusicDecisionResult\u003E Results { get; } = new();\n}\n"},{"Ident":"notpointless.chomnr_adaptive_director","Path":"SboxDirector/WaveSequenceController.cs","FileName":"WaveSequenceController.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":337987,"Code":"namespace SboxDirector;\n\npublic sealed class WaveSequenceConfiguration\n{\n    public int WaveCount { get; init; } = 1;\n    public double InitialDelayMinimum { get; init; } = 1;\n    public double InitialDelayMaximum { get; init; } = 2;\n    public double CombatTimeout { get; init; } = 10;\n    public double PauseMinimum { get; init; } = 5;\n    public double PauseMaximum { get; init; } = 10;\n    public void Validate()\n    {\n        var durations = new[] { InitialDelayMinimum, InitialDelayMaximum, CombatTimeout, PauseMinimum, PauseMaximum };\n        if (WaveCount \u003C= 0 || durations.Any(x =\u003E !double.IsFinite(x)) || InitialDelayMinimum \u003C 0 || InitialDelayMaximum \u003C InitialDelayMinimum || CombatTimeout \u003C 0 || PauseMinimum \u003C 0 || PauseMaximum \u003C PauseMinimum) throw new ArgumentException(\u0022Invalid wave sequence configuration.\u0022);\n    }\n}\n\npublic readonly record struct WaveSequenceState(WaveState State, int IssuedWaves, int TargetWaves, double Deadline);\npublic readonly record struct WaveAction(bool IssueWave, bool Completed, string Reason);\n\npublic sealed class WaveSequenceController\n{\n    readonly WaveSequenceConfiguration _configuration; readonly DeterministicRandom _random;\n    public WaveState State { get; private set; } = WaveState.Inactive;\n    public int IssuedWaves { get; private set; }\n    public int TargetWaves { get; private set; }\n    public double Deadline { get; private set; }\n    public WaveSequenceController(WaveSequenceConfiguration configuration, DeterministicRandom random) { configuration.Validate(); _configuration = configuration; _random = random; }\n    public WaveSequenceController(WaveSequenceConfiguration configuration, DeterministicRandom random, WaveSequenceState state) : this(configuration, random) { State = state.State; IssuedWaves = state.IssuedWaves; TargetWaves = state.TargetWaves; Deadline = state.Deadline; if (!double.IsFinite(Deadline) || IssuedWaves \u003C 0 || TargetWaves \u003C 0 || IssuedWaves \u003E TargetWaves) throw new ArgumentException(\u0022Wave state is invalid.\u0022, nameof(state)); }\n    public WaveSequenceState Snapshot =\u003E new(State, IssuedWaves, TargetWaves, Deadline);\n    public void Start(double time, int? waveCount = null) { RequireTime(time); TargetWaves = waveCount ?? _configuration.WaveCount; if (TargetWaves \u003C= 0) throw new ArgumentOutOfRangeException(nameof(waveCount)); IssuedWaves = 0; State = WaveState.InitialDelay; Deadline = time \u002B Range(_configuration.InitialDelayMinimum, _configuration.InitialDelayMaximum); }\n    public void Cancel() { State = WaveState.Inactive; IssuedWaves = 0; TargetWaves = 0; Deadline = 0; }\n    public WaveAction Update(double time, bool relevantCombatantsRemain)\n    {\n        RequireTime(time);\n        if (State is WaveState.Inactive or WaveState.Done) return new(false, State == WaveState.Done, \u0022inactive or complete\u0022);\n        if (time \u003C Deadline) return new(false, false, \u0022waiting for timer\u0022);\n        if (State == WaveState.InitialDelay) { State = WaveState.IssueWave; return new(false, false, \u0022initial delay expired\u0022); }\n        if (State == WaveState.IssueWave) { IssuedWaves\u002B\u002B; State = WaveState.WaitForClear; Deadline = time \u002B _configuration.CombatTimeout; return new(true, false, \u0022wave issued\u0022); }\n        if (State == WaveState.WaitForClear)\n        {\n            if (relevantCombatantsRemain) { Deadline = time \u002B _configuration.CombatTimeout; return new(false, false, \u0022combat remains\u0022); }\n            if (IssuedWaves \u003E= TargetWaves) { State = WaveState.Done; return new(false, true, \u0022target waves issued and combat clear\u0022); }\n            State = WaveState.Pause; Deadline = time \u002B Range(_configuration.PauseMinimum, _configuration.PauseMaximum); return new(false, false, \u0022pausing before next wave\u0022);\n        }\n        if (State == WaveState.Pause) { State = WaveState.IssueWave; return new(false, false, \u0022pause expired\u0022); }\n        return new(false, false, \u0022no action\u0022);\n    }\n    double Range(double minimum, double maximum) =\u003E minimum \u002B (maximum - minimum) * _random.NextFloat();\n    static void RequireTime(double time) { if (!double.IsFinite(time)) throw new ArgumentOutOfRangeException(nameof(time)); }\n}\n"},{"Ident":"notpointless.chomnr_adaptive_director","Path":".obj/__compiler_extra.cs","FileName":"__compiler_extra.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":337987,"Code":"global using static Sandbox.Internal.GlobalGameNamespace;\r\nglobal using Microsoft.AspNetCore.Components;\r\nglobal using Microsoft.AspNetCore.Components.Rendering;\r\n[assembly: global::System.Reflection.AssemblyMetadata( \u0022AddonTitle\u0022, \u0022Adaptive Director\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \u0022AddonIdent\u0022, \u0022chomnr_adaptive_director\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \u0022OrgIdent\u0022, \u0022notpointless\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \u0022Ident\u0022, \u0022notpointless.chomnr_adaptive_director\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \u0022EngineVersion\u0022, \u002228\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \u0022EngineMinorVersion\u0022, \u00221\u0022 )]\r\n\r\n[assembly: System.Runtime.Versioning.TargetFramework( \u0022.NETCoreApp,Version=v9.0\u0022, FrameworkDisplayName = \u0022.NET 9.0\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \u0022CompileTime\u0022, \u00222026-08-01T14:11:11.8900023Z\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyVersion(\u00220.0.119.0\u0022)]\r\n[assembly: global::System.Reflection.AssemblyFileVersion(\u00220.0.119.0\u0022)]"},{"Ident":"notpointless.chomnr_adaptive_director","Path":"Code/SboxDirector/EncounterSchedules.cs","FileName":"EncounterSchedules.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":337987,"Code":"#nullable enable annotations\nnamespace SboxDirector;\n\npublic sealed class EncounterRule\n{\n    public string Id { get; init; } = \u0022rule\u0022;\n    public EncounterKind Kind { get; init; }\n    public int Count { get; init; } = 1;\n    public int Priority { get; init; }\n    public double CooldownSeconds { get; init; } = 10;\n    public float MinimumPressure { get; init; }\n    public float MaximumPressure { get; init; } = 1f;\n    public float Probability { get; init; } = 1f;\n    public IReadOnlySet\u003CTempoPhase\u003E Phases { get; init; } = new HashSet\u003CTempoPhase\u003E { TempoPhase.BuildUp, TempoPhase.SustainPeak };\n    public IReadOnlySet\u003Cstring\u003E Modes { get; init; } = new HashSet\u003Cstring\u003E();\n    public Func\u003CWorldSnapshot, bool\u003E? AdditionalCondition { get; init; }\n\n    public void Validate()\n    {\n        if (string.IsNullOrWhiteSpace(Id) || Count \u003C= 0 || !double.IsFinite(CooldownSeconds) || CooldownSeconds \u003C 0 || !float.IsFinite(MinimumPressure) || !float.IsFinite(MaximumPressure) || !float.IsFinite(Probability) || MinimumPressure \u003C 0 || MaximumPressure \u003E 1 || MaximumPressure \u003C MinimumPressure || Probability is \u003C 0 or \u003E 1) throw new ArgumentException($\u0022Invalid encounter rule \u0027{Id}\u0027.\u0022);\n    }\n}\n\npublic interface IEncounterTimingPolicy\n{\n    double AdjustCooldown(EncounterRule rule, double proposedSeconds, WorldSnapshot world, TempoPhase tempo, float teamPressure);\n}\n\npublic sealed class DefaultEncounterTimingPolicy : IEncounterTimingPolicy\n{\n    public double AdjustCooldown(EncounterRule rule, double proposedSeconds, WorldSnapshot world, TempoPhase tempo, float teamPressure) =\u003E proposedSeconds;\n}\n\npublic sealed class ModeEncounterTimingPolicy : IEncounterTimingPolicy\n{\n    readonly IReadOnlyDictionary\u003Cstring, double\u003E _modeMultipliers;\n    readonly double _highPressureMultiplier;\n    public ModeEncounterTimingPolicy(IReadOnlyDictionary\u003Cstring, double\u003E? modeMultipliers = null, double highPressureMultiplier = 1)\n    {\n        _modeMultipliers = new Dictionary\u003Cstring, double\u003E(modeMultipliers ?? new Dictionary\u003Cstring, double\u003E(), StringComparer.Ordinal); if (_modeMultipliers.Any(x =\u003E string.IsNullOrWhiteSpace(x.Key) || !double.IsFinite(x.Value) || x.Value \u003C 0) || !double.IsFinite(highPressureMultiplier) || highPressureMultiplier \u003C 0) throw new ArgumentException(\u0022Cooldown multipliers must be finite and non-negative.\u0022); _highPressureMultiplier = highPressureMultiplier;\n    }\n    public double AdjustCooldown(EncounterRule rule, double proposedSeconds, WorldSnapshot world, TempoPhase tempo, float teamPressure)\n    {\n        var mode = _modeMultipliers.TryGetValue(world.ModeId, out var multiplier) ? multiplier : 1; var pressure = 1 \u002B (_highPressureMultiplier - 1) * Math.Clamp(teamPressure, 0, 1); return proposedSeconds * mode * pressure;\n    }\n}\n\npublic sealed class RuleBasedEncounterSchedule : IEncounterSchedule, IPersistentEncounterSchedule, IEncounterScheduleFeedback\n{\n    readonly IReadOnlyList\u003CEncounterRule\u003E _rules;\n    readonly IEncounterTimingPolicy _timing;\n    readonly Dictionary\u003Cstring, double\u003E _nextTimes = new();\n    EncounterRule? _selected;\n    double _selectedPreviousTime;\n\n    public RuleBasedEncounterSchedule(IEnumerable\u003CEncounterRule\u003E rules, IEncounterTimingPolicy? timing = null)\n    {\n        _rules = rules.OrderByDescending(x =\u003E x.Priority).ThenBy(x =\u003E x.Id, StringComparer.Ordinal).ToArray();\n        if (_rules.Count == 0) throw new ArgumentException(\u0022At least one encounter rule is required.\u0022, nameof(rules));\n        foreach (var rule in _rules) rule.Validate();\n        if (_rules.Select(x =\u003E x.Id).Distinct(StringComparer.Ordinal).Count() != _rules.Count) throw new ArgumentException(\u0022Encounter rule ids must be unique.\u0022);\n        _timing = timing ?? new DefaultEncounterTimingPolicy();\n    }\n\n    public EncounterKind? SelectEncounter(WorldSnapshot world, TempoPhase tempo, float pressure, PopulationLedger population, DeterministicRandom random)\n    {\n        _selected = null;\n        foreach (var rule in _rules)\n        {\n            if (_nextTimes.TryGetValue(rule.Id, out var next) \u0026\u0026 world.Time \u003C next) continue;\n            if (!rule.Phases.Contains(tempo) || pressure \u003C rule.MinimumPressure || pressure \u003E rule.MaximumPressure) continue;\n            if (rule.Modes.Count \u003E 0 \u0026\u0026 !rule.Modes.Contains(world.ModeId)) continue;\n            if (rule.AdditionalCondition is not null \u0026\u0026 !rule.AdditionalCondition(world)) continue;\n            if (population.Available(rule.Kind, world.Population) \u003C= 0) continue;\n            var cooldown = _timing.AdjustCooldown(rule, rule.CooldownSeconds, world, tempo, pressure); if (!double.IsFinite(cooldown) || cooldown \u003C 0) throw new InvalidOperationException(\u0022Encounter timing policy returned an invalid cooldown.\u0022);\n            if (random.NextFloat() \u003E rule.Probability) { _nextTimes[rule.Id] = world.Time \u002B cooldown; continue; }\n            _selected = rule; _selectedPreviousTime = _nextTimes.TryGetValue(rule.Id, out var previous) ? previous : double.NegativeInfinity; _nextTimes[rule.Id] = world.Time \u002B cooldown; return rule.Kind;\n        }\n        return null;\n    }\n\n    public int RequestedCount(EncounterKind kind, WorldSnapshot world) =\u003E _selected?.Kind == kind ? _selected.Count : 1;\n    public IReadOnlyDictionary\u003Cstring, string\u003E CaptureScheduleState() =\u003E _nextTimes.ToDictionary(x =\u003E x.Key, x =\u003E x.Value.ToString(\u0022R\u0022, CultureInfo.InvariantCulture));\n    public void RestoreScheduleState(IReadOnlyDictionary\u003Cstring, string\u003E state)\n    {\n        _nextTimes.Clear(); foreach (var pair in state) if (double.TryParse(pair.Value, NumberStyles.Float, CultureInfo.InvariantCulture, out var value)) _nextTimes[pair.Key] = value;\n    }\n    public void SelectionFinalized(bool requestCreated) { if (_selected is not null \u0026\u0026 !requestCreated) { if (double.IsNegativeInfinity(_selectedPreviousTime)) _nextTimes.Remove(_selected.Id); else _nextTimes[_selected.Id] = _selectedPreviousTime; } _selected = null; }\n}\n\npublic sealed class MilestoneEncounterSchedule : IEncounterSchedule, IPersistentEncounterSchedule, IEncounterScheduleFeedback\n{\n    readonly string _id; readonly EncounterKind _kind; readonly float _minimumProgress; readonly float _maximumProgress; readonly int _count;\n    readonly HashSet\u003Cstring\u003E _completedScopes = new(); string? _selectedScope;\n    public MilestoneEncounterSchedule(string id, EncounterKind kind, float minimumProgress, float maximumProgress = 1f, int count = 1)\n    {\n        if (string.IsNullOrWhiteSpace(id) || minimumProgress \u003C 0 || maximumProgress \u003C minimumProgress || count \u003C= 0) throw new ArgumentException(\u0022Invalid milestone schedule.\u0022);\n        _id = id; _kind = kind; _minimumProgress = minimumProgress; _maximumProgress = maximumProgress; _count = count;\n    }\n    public EncounterKind? SelectEncounter(WorldSnapshot world, TempoPhase tempo, float teamPressure, PopulationLedger population, DeterministicRandom random)\n    {\n        var scope = string.IsNullOrWhiteSpace(world.RoundId) ? world.ModeId : world.ModeId \u002B \u0022:\u0022 \u002B world.RoundId;\n        if (_completedScopes.Contains(scope) || world.TeamProgress \u003C _minimumProgress || world.TeamProgress \u003E _maximumProgress || population.Available(_kind, world.Population) \u003C= 0) return null;\n        _selectedScope = scope; return _kind;\n    }\n    public int RequestedCount(EncounterKind kind, WorldSnapshot world) =\u003E kind == _kind \u0026\u0026 _selectedScope is not null ? _count : 1;\n    public IReadOnlyDictionary\u003Cstring, string\u003E CaptureScheduleState() =\u003E new Dictionary\u003Cstring, string\u003E { [\u0022id\u0022] = _id, [\u0022completed\u0022] = string.Join(\u0022|\u0022, _completedScopes.OrderBy(x =\u003E x, StringComparer.Ordinal)) };\n    public void RestoreScheduleState(IReadOnlyDictionary\u003Cstring, string\u003E state) { _completedScopes.Clear(); if (state.TryGetValue(\u0022completed\u0022, out var value)) foreach (var scope in value.Split(\u0027|\u0027, StringSplitOptions.RemoveEmptyEntries)) _completedScopes.Add(scope); }\n    public void SelectionFinalized(bool requestCreated) { if (requestCreated \u0026\u0026 _selectedScope is not null) _completedScopes.Add(_selectedScope); _selectedScope = null; }\n}\n\npublic sealed class CompositeEncounterSchedule : IEncounterSchedule, IPersistentEncounterSchedule, IEncounterScheduleFeedback\n{\n    readonly IReadOnlyList\u003CIEncounterSchedule\u003E _schedules; IEncounterSchedule? _selected;\n    public CompositeEncounterSchedule(params IEncounterSchedule[] schedules) { _schedules = schedules; if (schedules.Length == 0) throw new ArgumentException(\u0022At least one schedule is required.\u0022); }\n    public EncounterKind? SelectEncounter(WorldSnapshot world, TempoPhase tempo, float teamPressure, PopulationLedger population, DeterministicRandom random)\n    {\n        _selected = null; foreach (var schedule in _schedules) { var kind = schedule.SelectEncounter(world, tempo, teamPressure, population, random); if (kind is not null) { _selected = schedule; return kind; } }\n        return null;\n    }\n    public int RequestedCount(EncounterKind kind, WorldSnapshot world) =\u003E _selected?.RequestedCount(kind, world) ?? 1;\n    public IReadOnlyDictionary\u003Cstring, string\u003E CaptureScheduleState()\n    {\n        var output = new Dictionary\u003Cstring, string\u003E(); for (var i = 0; i \u003C _schedules.Count; i\u002B\u002B) if (_schedules[i] is IPersistentEncounterSchedule persistent) foreach (var pair in persistent.CaptureScheduleState()) output[$\u0022{i}:{pair.Key}\u0022] = pair.Value; return output;\n    }\n    public void RestoreScheduleState(IReadOnlyDictionary\u003Cstring, string\u003E state)\n    {\n        for (var i = 0; i \u003C _schedules.Count; i\u002B\u002B) if (_schedules[i] is IPersistentEncounterSchedule persistent) persistent.RestoreScheduleState(state.Where(x =\u003E x.Key.StartsWith(i \u002B \u0022:\u0022, StringComparison.Ordinal)).ToDictionary(x =\u003E x.Key[(x.Key.IndexOf(\u0027:\u0027) \u002B 1)..], x =\u003E x.Value));\n    }\n    public void SelectionFinalized(bool requestCreated) { if (_selected is IEncounterScheduleFeedback feedback) feedback.SelectionFinalized(requestCreated); _selected = null; }\n}\n"},{"Ident":"notpointless.chomnr_adaptive_director","Path":"Code/SboxDirector/Models.cs","FileName":"Models.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":337987,"Code":"#nullable enable annotations\nnamespace SboxDirector;\n\npublic enum TempoPhase { BuildUp, SustainPeak, PeakFade, Relax }\npublic enum PressureSeverity { Minor = 1, Moderate = 2, Major = 3, Critical = 4, Maximum = 5 }\npublic enum EncounterKind { Ambient, CommonWave, Special, Boss, DormantHazard, Objective, Custom }\npublic enum RequestResultKind { Succeeded, PartiallySucceeded, Rejected, Deferred, Failed }\npublic enum WaveState { Inactive, InitialDelay, IssueWave, WaitForClear, Pause, Done }\npublic enum ScenarioStatus { Inactive, Running, Complete, Failed }\npublic enum AdvanceRelation { Unknown, Ahead, Behind, Lateral, ObjectiveAdjacent }\npublic enum PlacementFallbackMode { None, IgnoreRouteConstraints, AnyReachableHidden }\npublic enum PlacementSelectionMode { WeightedScore, FirstEligible, HighestProgressFirstTie, NativeRandomTranspositionFirst }\n\npublic readonly record struct DirectorVector(float X, float Y, float Z)\n{\n    public static float DistanceSquared(DirectorVector a, DirectorVector b)\n    {\n        var x = a.X - b.X; var y = a.Y - b.Y; var z = a.Z - b.Z;\n        return x * x \u002B y * y \u002B z * z;\n    }\n}\n\npublic sealed class ParticipantSnapshot\n{\n    public string Id { get; init; } = \u0022\u0022;\n    public bool IsAlive { get; init; } = true;\n    public bool IsEligible { get; init; } = true;\n    public bool IsInCombat { get; init; }\n    public DirectorVector Position { get; init; }\n    public float Progress { get; init; }\n}\n\npublic sealed class PopulationSnapshot\n{\n    readonly Dictionary\u003CEncounterKind, int\u003E _counts = new();\n    public PopulationSnapshot() { }\n    public PopulationSnapshot(IReadOnlyDictionary\u003CEncounterKind, int\u003E counts) { foreach (var pair in counts) _counts[pair.Key] = Math.Max(0, pair.Value); }\n    public int this[EncounterKind kind] =\u003E _counts.TryGetValue(kind, out var value) ? value : 0;\n    public IReadOnlyDictionary\u003CEncounterKind, int\u003E Counts =\u003E _counts;\n}\n\npublic sealed class SpawnCandidate\n{\n    public string Id { get; init; } = \u0022\u0022;\n    public DirectorVector Position { get; init; }\n    public bool Reachable { get; init; }\n    public bool Spawnable { get; init; }\n    public bool Visible { get; init; }\n    public float NearestParticipantDistance { get; init; }\n    public float Progress { get; init; }\n    public float AreaCapacity { get; init; } = 1f;\n    public float ThreatSeparation { get; init; }\n    public float RelativeProgress { get; init; }\n    public float PathDistance { get; init; }\n    public float IngressDistance { get; init; }\n    public float ClearRadius { get; init; }\n    public float AreaWidth { get; init; }\n    public float AreaHeight { get; init; }\n    public AdvanceRelation AdvanceRelation { get; init; }\n    public IReadOnlySet\u003Cstring\u003E Tags { get; init; } = new HashSet\u003Cstring\u003E();\n}\n\npublic sealed class WorldSnapshot\n{\n    public double Time { get; init; }\n    public IReadOnlyList\u003CParticipantSnapshot\u003E Participants { get; init; } = Array.Empty\u003CParticipantSnapshot\u003E();\n    public PopulationSnapshot Population { get; init; } = new();\n    public IReadOnlyList\u003CSpawnCandidate\u003E SpawnCandidates { get; init; } = Array.Empty\u003CSpawnCandidate\u003E();\n    public bool CombatActive { get; init; }\n    public float TeamProgress { get; init; }\n    public string ModeId { get; init; } = \u0022continuous\u0022;\n    public string? RoundId { get; init; }\n    public ResourceWorldSnapshot Resources { get; init; } = new();\n\n    public void Validate()\n    {\n        if (double.IsNaN(Time) || double.IsInfinity(Time)) throw new ArgumentException(\u0022World time must be finite.\u0022);\n        if (float.IsNaN(TeamProgress) || float.IsInfinity(TeamProgress)) throw new ArgumentException(\u0022Team progress must be finite.\u0022);\n        if (Participants.Any(x =\u003E string.IsNullOrWhiteSpace(x.Id)) || Participants.Select(x =\u003E x.Id).Distinct(StringComparer.Ordinal).Count() != Participants.Count) throw new ArgumentException(\u0022Participant ids must be non-empty and unique.\u0022);\n        if (SpawnCandidates.Any(x =\u003E string.IsNullOrWhiteSpace(x.Id)) || SpawnCandidates.Select(x =\u003E x.Id).Distinct(StringComparer.Ordinal).Count() != SpawnCandidates.Count) throw new ArgumentException(\u0022Spawn candidate ids must be non-empty and unique.\u0022);\n        if (SpawnCandidates.Any(x =\u003E !float.IsFinite(x.NearestParticipantDistance) || !float.IsFinite(x.Progress) || !float.IsFinite(x.RelativeProgress) || !float.IsFinite(x.AreaCapacity) || !float.IsFinite(x.ThreatSeparation) || !float.IsFinite(x.PathDistance) || !float.IsFinite(x.IngressDistance) || !float.IsFinite(x.ClearRadius) || !float.IsFinite(x.AreaWidth) || !float.IsFinite(x.AreaHeight) || x.NearestParticipantDistance \u003C 0 || x.AreaCapacity \u003C 0 || x.ThreatSeparation \u003C 0 || x.PathDistance \u003C 0 || x.IngressDistance \u003C 0 || x.ClearRadius \u003C 0 || x.AreaWidth \u003C 0 || x.AreaHeight \u003C 0)) throw new ArgumentException(\u0022Spawn candidate distances, dimensions, and capacity must be finite and non-negative.\u0022);\n        Resources.Validate();\n    }\n}\n\npublic sealed record EncounterMember(string Archetype, int Count);\n\npublic sealed record SpawnRequest(\n    long RequestId,\n    EncounterKind Kind,\n    int RequestedCount,\n    string CandidateId,\n    string Archetype,\n    string Reason)\n{\n    public IReadOnlyList\u003CEncounterMember\u003E Composition { get; init; } = Array.Empty\u003CEncounterMember\u003E();\n    public int Attempt { get; init; } = 1;\n    public bool Equals(SpawnRequest? other) =\u003E other is not null \u0026\u0026 RequestId == other.RequestId \u0026\u0026 Kind == other.Kind \u0026\u0026 RequestedCount == other.RequestedCount \u0026\u0026 CandidateId == other.CandidateId \u0026\u0026 Archetype == other.Archetype \u0026\u0026 Reason == other.Reason \u0026\u0026 Attempt == other.Attempt \u0026\u0026 Composition.SequenceEqual(other.Composition);\n    public override int GetHashCode()\n    {\n        var hash = HashCode.Combine(RequestId, Kind, RequestedCount, CandidateId, Archetype, Reason, Attempt); foreach (var member in Composition) hash = HashCode.Combine(hash, member); return hash;\n    }\n}\n\npublic sealed record SpawnRequestResult(\n    long RequestId,\n    RequestResultKind Result,\n    int SpawnedCount,\n    string? Detail = null);\n\npublic sealed record DirectorEvent(string Name, string Reason, double Time);\n\npublic sealed class DirectorDecisionBatch\n{\n    public double Time { get; init; }\n    public TempoPhase Tempo { get; init; }\n    public float TeamPressure { get; init; }\n    public float MusicIntensity { get; init; }\n    public List\u003CSpawnRequest\u003E SpawnRequests { get; } = new();\n    public List\u003CResourceSpawnRequest\u003E ResourceRequests { get; } = new();\n    public List\u003CDirectorEvent\u003E Events { get; } = new();\n}\n\npublic sealed record PressureEvent(string ParticipantId, PressureSeverity Severity, string Reason);\n\npublic sealed record DirectorDiagnostic(double Time, string Category, string Message);\n"},{"Ident":"notpointless.chomnr_adaptive_director","Path":"Code/SboxDirector/MusicModels.cs","FileName":"MusicModels.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":337987,"Code":"#nullable enable annotations\nnamespace SboxDirector;\n\npublic enum MusicDecisionKind { SetMixerLayer, Play, StopEvent, StopTrack, StopAll, General }\npublic enum MusicPriority { Low = 1, Medium = 2, High = 3, Critical = 4 }\npublic enum MusicTriggerKind { Play, StopEvent, StopTrack, StopAll, General, Reset, ScenarioEnded, ParticipantRestored, AmbientMob, MobSpawn, MobSpawnBehind, LargeAreaRevealed, LandmarkRevealed }\npublic enum MusicRuntimeActionKind { Play, StopEvent, StopTrack, StopAll, SetTrackVolume, SetMixerLayer, General }\npublic enum MusicDecisionResultKind { Accepted, Deferred, RejectedUnknownEvent, RejectedBlocked, RejectedLifecycle, RejectedPriority, FailedAdapter }\n\n[Flags]\npublic enum MusicMasterFlags\n{\n    None = 0,\n    PlayToEnd = 1,\n    PlaySplit = 2,\n    DontDisengage = 4,\n    DontEngage = 8,\n    AllowAfterDeath = 16,\n    AllowAfterScenarioEnd = 32\n}\n\npublic static class MusicMixerLayers\n{\n    public const string MobBeating = \u0022mob_beating\u0022;\n    public const string MobRules = \u0022mob_rules\u0022;\n    public const string HazardRage = \u0022hazard_rage\u0022;\n    public const string Combat = \u0022combat\u0022;\n    public const string CombatSecondary = \u0022combat_secondary\u0022;\n    public const string CombatClose = \u0022combat_close\u0022;\n    public const string Adrenaline = \u0022adrenaline\u0022;\n    public const string Checkpoint = \u0022checkpoint\u0022;\n    public const string Ambient = \u0022ambient\u0022;\n}\n\npublic sealed record SpecialMusicThreat(string ArchetypeId, float Distance, string EmitterId = \u0022\u0022, DirectorVector? Position = null);\npublic sealed record PositionalMusicCandidate(string Id, DirectorVector Position, bool Eligible = true);\npublic sealed record WanderingMusicHazard(string Id, float Anger, DirectorVector Position);\n\npublic sealed class MusicParticipantSnapshot\n{\n    public string Id { get; init; } = \u0022\u0022;\n    public bool IsEligible { get; init; } = true;\n    public bool IsAlive { get; init; } = true;\n    public bool IsIncapacitated { get; init; }\n    public bool DynamicMusicAllowed { get; init; } = true;\n    public float CumulativeDamage { get; init; }\n    public float WeaponActivity { get; init; }\n    public bool InflictedDamage { get; init; }\n    public int VisibleCommonThreats { get; init; }\n    public int ActiveCommonThreats { get; init; }\n    public int ScannedCommonThreats { get; init; }\n    public float AttackingCommonFactor { get; init; }\n    public float CloseCommonAction { get; init; }\n    public float VeryCloseCommonFactor { get; init; }\n    public bool MobPressureActive { get; init; }\n    public bool GlobalCombatActive { get; init; }\n    public bool BossPresent { get; init; }\n    public string BossArchetypeId { get; init; } = \u0022\u0022;\n    public bool HazardPresent { get; init; }\n    public bool HazardAttacking { get; init; }\n    public bool HazardBurning { get; init; }\n    public float HazardRage { get; init; }\n    public string HazardEmitterId { get; init; } = \u0022\u0022;\n    public DirectorVector? HazardPosition { get; init; }\n    public float Adrenaline { get; init; }\n    public bool InCheckpoint { get; init; }\n    public IReadOnlyList\u003CSpecialMusicThreat\u003E SpecialThreats { get; init; } = Array.Empty\u003CSpecialMusicThreat\u003E();\n    public IReadOnlyList\u003CPositionalMusicCandidate\u003E PositionalCandidates { get; init; } = Array.Empty\u003CPositionalMusicCandidate\u003E();\n    public IReadOnlyList\u003CWanderingMusicHazard\u003E WanderingHazards { get; init; } = Array.Empty\u003CWanderingMusicHazard\u003E();\n\n    public void Validate()\n    {\n        if (string.IsNullOrWhiteSpace(Id)) throw new ArgumentException(\u0022A music participant id is required.\u0022);\n        var values = new[] { CumulativeDamage, WeaponActivity, AttackingCommonFactor, CloseCommonAction, VeryCloseCommonFactor, HazardRage, Adrenaline };\n        if (values.Any(x =\u003E !float.IsFinite(x)) || CumulativeDamage \u003C 0 || VisibleCommonThreats \u003C 0 || ActiveCommonThreats \u003C 0 || ScannedCommonThreats \u003C 0) throw new ArgumentException(\u0022Music participant measurements must be finite and non-negative.\u0022);\n        if (WeaponActivity is \u003C 0 or \u003E 1 || AttackingCommonFactor is \u003C 0 or \u003E 1 || CloseCommonAction is \u003C 0 or \u003E 1 || VeryCloseCommonFactor is \u003C 0 or \u003E 1 || HazardRage is \u003C 0 or \u003E 1 || Adrenaline is \u003C 0 or \u003E 1) throw new ArgumentException(\u0022Normalized music inputs must be in [0,1].\u0022);\n        if (SpecialThreats.Any(x =\u003E string.IsNullOrWhiteSpace(x.ArchetypeId) || !float.IsFinite(x.Distance) || x.Distance \u003C 0)) throw new ArgumentException(\u0022Special music threats require an archetype and finite non-negative distance.\u0022);\n        if (PositionalCandidates.Any(x =\u003E string.IsNullOrWhiteSpace(x.Id))) throw new ArgumentException(\u0022Positional music candidates require stable ids.\u0022);\n        if (WanderingHazards.Any(x =\u003E string.IsNullOrWhiteSpace(x.Id) || !float.IsFinite(x.Anger) || x.Anger is \u003C 0 or \u003E 1)) throw new ArgumentException(\u0022Wandering music hazards require stable ids and anger in [0,1].\u0022);\n    }\n}\n\npublic sealed class MusicWorldSnapshot\n{\n    public double Time { get; init; }\n    public IReadOnlyList\u003CMusicParticipantSnapshot\u003E Participants { get; init; } = Array.Empty\u003CMusicParticipantSnapshot\u003E();\n    public bool ScenarioEnded { get; init; }\n\n    public void Validate()\n    {\n        if (!double.IsFinite(Time)) throw new ArgumentException(\u0022Music world time must be finite.\u0022);\n        foreach (var participant in Participants) participant.Validate();\n        if (Participants.Select(x =\u003E x.Id).Distinct(StringComparer.Ordinal).Count() != Participants.Count) throw new ArgumentException(\u0022Music participant ids must be unique.\u0022);\n    }\n}\n\npublic sealed record MusicGameplayEvent(long Sequence, MusicTriggerKind Kind, string ParticipantId = \u0022\u0022, string TargetId = \u0022\u0022, float FadeSeconds = 0, string EmitterId = \u0022\u0022, DirectorVector? Position = null)\n{\n    public IReadOnlyList\u003Cstring\u003E Arguments { get; init; } = Array.Empty\u003Cstring\u003E();\n}\n\npublic sealed record MusicDecision(long DecisionId, MusicDecisionKind Kind, string ParticipantId, string TargetId, string Reason)\n{\n    public string LayerId { get; init; } = \u0022\u0022;\n    public float Value { get; init; }\n    public float FadeSeconds { get; init; }\n    public float StartOffsetSeconds { get; init; }\n    public string EmitterId { get; init; } = \u0022\u0022;\n    public DirectorVector? Position { get; init; }\n    public IReadOnlyList\u003Cstring\u003E Arguments { get; init; } = Array.Empty\u003Cstring\u003E();\n}\n\npublic sealed class MusicDecisionBatch\n{\n    public double Time { get; init; }\n    public List\u003CMusicDecision\u003E Decisions { get; } = new();\n}\n\npublic sealed record MusicTimingTag(int Index, double OffsetSeconds);\n\npublic sealed class MusicEventDefinition\n{\n    public string EventId { get; init; } = \u0022\u0022;\n    public string TrackId { get; init; } = \u0022main\u0022;\n    public MusicPriority Priority { get; init; } = MusicPriority.Low;\n    public MusicMasterFlags Flags { get; init; }\n    public float DefaultFadeOutSeconds { get; init; }\n    public double DelaySeconds { get; init; }\n    public string? AutoQueueEventId { get; init; }\n    public IReadOnlyList\u003Cstring\u003E BlockTrackList { get; init; } = Array.Empty\u003Cstring\u003E();\n    public IReadOnlyList\u003Cstring\u003E DuckTrackList { get; init; } = Array.Empty\u003Cstring\u003E();\n    public IReadOnlyList\u003Cstring\u003E StopTrackList { get; init; } = Array.Empty\u003Cstring\u003E();\n    public IReadOnlyList\u003CMusicTimingTag\u003E TimingTags { get; init; } = Array.Empty\u003CMusicTimingTag\u003E();\n    public double LoopSeconds { get; init; }\n    public string? SyncTrackId { get; init; }\n    public int? SyncTagIndex { get; init; }\n    public double SyncTagDelaySeconds { get; init; }\n    public double SyncTagDelayMultiplier { get; init; } = 1;\n\n    public void Validate()\n    {\n        if (string.IsNullOrWhiteSpace(EventId) || string.IsNullOrWhiteSpace(TrackId)) throw new ArgumentException(\u0022Music events require event and track ids.\u0022);\n        if (!float.IsFinite(DefaultFadeOutSeconds) || DefaultFadeOutSeconds \u003C 0 || !double.IsFinite(DelaySeconds) || DelaySeconds \u003C 0 || !double.IsFinite(LoopSeconds) || LoopSeconds \u003C 0 || !double.IsFinite(SyncTagDelaySeconds) || !double.IsFinite(SyncTagDelayMultiplier) || SyncTagDelayMultiplier \u003C 0) throw new ArgumentException(\u0022Music timing values must be finite and non-negative where applicable.\u0022);\n        if (TimingTags.Any(x =\u003E x.Index \u003C 0 || !double.IsFinite(x.OffsetSeconds) || x.OffsetSeconds \u003C 0) || TimingTags.Select(x =\u003E x.Index).Distinct().Count() != TimingTags.Count) throw new ArgumentException(\u0022Music timing tags require unique non-negative indices and offsets.\u0022);\n        if (BlockTrackList.Concat(DuckTrackList).Concat(StopTrackList).Any(string.IsNullOrWhiteSpace)) throw new ArgumentException(\u0022Music track lists cannot contain empty ids.\u0022);\n    }\n}\n\npublic interface IMusicEventCatalog\n{\n    bool TryGet(string eventId, out MusicEventDefinition definition);\n}\n\npublic sealed class MusicEventCatalog : IMusicEventCatalog\n{\n    readonly Dictionary\u003Cstring, MusicEventDefinition\u003E _events;\n    public MusicEventCatalog(IEnumerable\u003CMusicEventDefinition\u003E events)\n    {\n        _events = new(StringComparer.Ordinal);\n        foreach (var definition in events) { definition.Validate(); if (!_events.TryAdd(definition.EventId, definition)) throw new ArgumentException($\u0022Duplicate music event \u0027{definition.EventId}\u0027.\u0022); }\n        foreach (var definition in _events.Values) if (definition.AutoQueueEventId is { Length: \u003E 0 } next \u0026\u0026 !_events.ContainsKey(next)) throw new ArgumentException($\u0022Music event \u0027{definition.EventId}\u0027 queues unknown event \u0027{next}\u0027.\u0022);\n    }\n    public bool TryGet(string eventId, out MusicEventDefinition definition) =\u003E _events.TryGetValue(eventId, out definition!);\n}\n\npublic sealed record MusicRuntimeAction(long ActionId, MusicRuntimeActionKind Kind, string TargetId)\n{\n    public string EventId { get; init; } = \u0022\u0022;\n    public string TrackId { get; init; } = \u0022\u0022;\n    public string LayerId { get; init; } = \u0022\u0022;\n    public float Value { get; init; }\n    public float FadeSeconds { get; init; }\n    public float StartOffsetSeconds { get; init; }\n    public string EmitterId { get; init; } = \u0022\u0022;\n    public DirectorVector? Position { get; init; }\n    public IReadOnlyList\u003Cstring\u003E Arguments { get; init; } = Array.Empty\u003Cstring\u003E();\n}\n\npublic sealed record MusicDecisionResult(long DecisionId, MusicDecisionResultKind Result, string Detail = \u0022\u0022, long InstanceId = 0);\n\npublic sealed class MusicRuntimeBatch\n{\n    public double Time { get; init; }\n    public List\u003CMusicRuntimeAction\u003E Actions { get; } = new();\n    public List\u003CMusicDecisionResult\u003E Results { get; } = new();\n}\n"},{"Ident":"notpointless.chomnr_adaptive_director","Path":"dev/SboxDirector.Tests/Program.cs","FileName":"Program.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":337987,"Code":"using SboxDirector;\n\nstatic class Tests\n{\n    static int _count;\n    static void Check(bool value, string name) { _count\u002B\u002B; if (!value) throw new Exception($\u0022FAILED: {name}\u0022); }\n    static void Near(float actual, float expected, float epsilon, string name) =\u003E Check(MathF.Abs(actual - expected) \u003C= epsilon, $\u0022{name}: expected {expected}, got {actual}\u0022);\n\n    public static void Main()\n    {\n        Pressure(); TeamMaximum(); Tempo(); Placement(); AdvancedPlacement(); RecoveredCompatibility(); Population(); Wave(); Scenarios(); Core(); Persistence(); PersistentPolicies(); RuleSchedules(); ConcurrentSchedules(); CompositionAndRetry(); Resources(); Orchestration(); AdaptiveCallbacks(); HostRunner(); OwnershipAndResources(); MusicAuthority(); MusicRuntimeArbitration(); MusicPersistenceAndHost(); Validation(); LongDeterminism(); AdvancedDeterminism();\n        Console.WriteLine($\u0022PASS: {_count} adaptive-director assertions\u0022);\n    }\n\n    static void Pressure()\n    {\n        var p = new PressureTracker(new PressureConfiguration(), 0);\n        p.Add(PressureSeverity.Major, 0); Near(p.Instantaneous, .125f, .00001f, \u0022major coefficient\u0022);\n        p.Update(5); Near(p.Instantaneous, .125f, .00001f, \u0022hold\u0022);\n        p.Update(8); Near(p.Instantaneous, .025f, .0001f, \u0022linear decay\u0022);\n        p.Add(PressureSeverity.Maximum, 8); Near(p.Instantaneous, 1f, .00001f, \u0022maximum saturates\u0022);\n        var q = new PressureTracker(new PressureConfiguration(), 0); q.Add(PressureSeverity.Critical, 0); q.Update(1f / 15f);\n        Near(q.Averaged, MathF.Pow(.25f, (1f / 15f) / 20f), .0001f, \u0022recovered averaged formula\u0022);\n    }\n\n    static void TeamMaximum()\n    {\n        var team = new TeamPressure(new PressureConfiguration()); team.Apply(new(\u0022a\u0022, PressureSeverity.Critical, \u0022test\u0022), 0); team.Apply(new(\u0022b\u0022, PressureSeverity.Minor, \u0022test\u0022), 0);\n        var world = new WorldSnapshot { Time = 0, Participants = new[] { new ParticipantSnapshot { Id = \u0022a\u0022 }, new ParticipantSnapshot { Id = \u0022b\u0022 } } };\n        Near(team.UpdateAndGetMaximum(world), .25f, .0001f, \u0022team uses max\u0022);\n    }\n\n    static void Tempo()\n    {\n        var c = new TempoController(new TempoConfiguration { RelaxMinimumSeconds = 1, RelaxMaximumSeconds = 2, RelaxMaximumProgressTravel = .1f });\n        c.Update(new WorldSnapshot { Time = 1, TeamProgress = .2f }, 0); Check(c.Phase == TempoPhase.BuildUp, \u0022relax to buildup\u0022);\n        c.Update(new WorldSnapshot { Time = 20 }, 1); Check(c.Phase == TempoPhase.SustainPeak, \u0022buildup to peak\u0022);\n        var absoluteClock = new DirectorCore(new DirectorConfiguration { Tempo = new TempoConfiguration { RelaxMinimumSeconds = 20, RelaxMaximumSeconds = 45 } }); Check(absoluteClock.Tick(World(10000)).Tempo == TempoPhase.Relax, \u0022first tick aligns absolute clock\u0022);\n    }\n\n    static void Placement()\n    {\n        var candidates = new[] { new SpawnCandidate { Id = \u0022visible\u0022, Reachable = true, Spawnable = true, Visible = true, NearestParticipantDistance = 1000, ThreatSeparation = 1000 }, new SpawnCandidate { Id = \u0022ok\u0022, Reachable = true, Spawnable = true, NearestParticipantDistance = 1200, ThreatSeparation = 1000 } };\n        var selected = new PlacementSelector().Select(candidates, new PlacementProfile(), new DeterministicRandom(1)); Check(selected?.Candidate.Id == \u0022ok\u0022, \u0022placement filters visibility\u0022);\n        var vetoed = new PlacementSelector().Select(candidates, new PlacementProfile(), new DeterministicRandom(1), new WorldSnapshot(), new RejectAllPlacement()); Check(vetoed is null, \u0022custom placement veto\u0022);\n        var tagged = new SpawnCandidate { Id = \u0022tagged\u0022, Reachable = true, Spawnable = true, NearestParticipantDistance = 1200, ThreatSeparation = 1000, Tags = new HashSet\u003Cstring\u003E { \u0022outdoor\u0022 } }; Check(new PlacementSelector().Select(new[] { tagged }, new PlacementProfile { RequiredTags = new HashSet\u003Cstring\u003E { \u0022indoor\u0022 } }, new DeterministicRandom(1)) is null, \u0022required placement tags\u0022);\n    }\n\n    static void AdvancedPlacement()\n    {\n        var strict = new SpawnCandidate { Id = \u0022strict\u0022, Reachable = true, Spawnable = true, NearestParticipantDistance = 1200, ThreatSeparation = 2000, PathDistance = 800, IngressDistance = 300, ClearRadius = 500, AdvanceRelation = AdvanceRelation.Ahead };\n        var fallback = new SpawnCandidate { Id = \u0022fallback\u0022, Reachable = true, Spawnable = true, NearestParticipantDistance = 1200, ThreatSeparation = 2000, PathDistance = 20, IngressDistance = 10, ClearRadius = 20, AdvanceRelation = AdvanceRelation.Behind };\n        var profile = new PlacementProfile { MinimumPathDistance = 500, MinimumIngressDistance = 200, MinimumClearRadius = 300, AllowedRelations = new HashSet\u003CAdvanceRelation\u003E { AdvanceRelation.Ahead }, FallbackMode = PlacementFallbackMode.IgnoreRouteConstraints };\n        var selected = new PlacementSelector().Select(new[] { strict, fallback }, profile, new DeterministicRandom(1)); Check(selected?.Candidate.Id == \u0022strict\u0022 \u0026\u0026 !selected.IsFallback, \u0022route-constrained placement\u0022);\n        selected = new PlacementSelector().Select(new[] { fallback }, profile, new DeterministicRandom(1)); Check(selected?.Candidate.Id == \u0022fallback\u0022 \u0026\u0026 selected.IsFallback, \u0022route fallback placement\u0022);\n    }\n\n    static void RecoveredCompatibility()\n    {\n        var route = new[]\n        {\n            new SpawnCandidate { Id = \u0022first-tie\u0022, Reachable = true, Spawnable = true, Progress = 50 },\n            new SpawnCandidate { Id = \u0022lower\u0022, Reachable = true, Spawnable = true, Progress = 40 },\n            new SpawnCandidate { Id = \u0022second-tie\u0022, Reachable = true, Spawnable = true, Progress = 50 }\n        };\n        var routeChoice = new PlacementSelector().Select(route, RecoveredDirectorDefaults.CreateRouteStepProfile(), new DeterministicRandom(1));\n        Check(routeChoice?.Candidate.Id == \u0022first-tie\u0022, \u0022recovered route uses strict greater-than and first tie\u0022);\n\n        var threat = new[]\n        {\n            new SpawnCandidate { Id = \u0022too-small\u0022, Reachable = true, Spawnable = true, AreaWidth = 23, AreaHeight = 24 },\n            new SpawnCandidate { Id = \u0022a\u0022, Reachable = true, Spawnable = true, AreaWidth = 24, AreaHeight = 24 },\n            new SpawnCandidate { Id = \u0022b\u0022, Reachable = true, Spawnable = true, AreaWidth = 30, AreaHeight = 30 }\n        };\n        var expectedOrder = threat.ToArray(); var expectedRandom = new DeterministicRandom(17);\n        for (var index = 0; index \u003C expectedOrder.Length; index\u002B\u002B) { var other = expectedRandom.NextInt(expectedOrder.Length); (expectedOrder[index], expectedOrder[other]) = (expectedOrder[other], expectedOrder[index]); }\n        var expected = expectedOrder.First(x =\u003E x.AreaWidth \u003E= 24 \u0026\u0026 x.AreaHeight \u003E= 24).Id;\n        var threatChoice = new PlacementSelector().Select(threat, RecoveredDirectorDefaults.CreateThreatAreaProfile(), new DeterministicRandom(17));\n        Check(threatChoice?.Candidate.Id == expected, \u0022recovered threat shuffle uses full-range random transpositions then first valid\u0022);\n\n        var samples = HullVisibilitySamples.CreateFivePointPattern(new DirectorVector(10, 0, 2), new DirectorVector(0, 0, 2), 32, 72);\n        Check(samples.Count == 5, \u0022recovered visibility sample count\u0022);\n        Near(samples[0].Y, 16, .0001f, \u0022lower left hull offset\u0022); Near(samples[1].Y, -16, .0001f, \u0022lower right hull offset\u0022);\n        Near(samples[2].Z, 38, .0001f, \u0022upper center half-height\u0022); Near(samples[3].Z, 74, .0001f, \u0022upper corner full-height\u0022);\n\n        var uniform = new UniformEncounterComposer(new Dictionary\u003CEncounterKind, IReadOnlyList\u003Cstring\u003E\u003E { [EncounterKind.Special] = new[] { \u0022a\u0022, \u0022b\u0022, \u0022c\u0022 } });\n        var composition = uniform.Compose(EncounterKind.Special, 20, new WorldSnapshot(), new DefaultDirectorPolicy(), new DeterministicRandom(3));\n        Check(composition.Sum(x =\u003E x.Count) == 20 \u0026\u0026 composition.All(x =\u003E x.Archetype is \u0022a\u0022 or \u0022b\u0022 or \u0022c\u0022), \u0022uniform recovered composition\u0022);\n\n        var rotation = new SpecialClassRotation(new[] { \u0022a\u0022, \u0022b\u0022, \u0022c\u0022 });\n        rotation.RestoreState(new Dictionary\u003Cstring, double\u003E { [\u0022a\u0022] = 0, [\u0022b\u0022] = 0, [\u0022c\u0022] = 0 });\n        var selected = rotation.SelectDue(10, x =\u003E x != \u0022a\u0022, new DeterministicRandom(4));\n        Check(selected is \u0022b\u0022 or \u0022c\u0022, \u0022special rotation selects uniformly from due eligible classes\u0022);\n        Near((float)rotation.CaptureState()[\u0022a\u0022], 30, .0001f, \u0022ineligible class retry delay\u0022);\n        rotation.ReportSpawnFailure(selected!, 10); Near((float)rotation.CaptureState()[selected!], 15, .0001f, \u0022failed class retry delay\u0022);\n        rotation.ReportSpawnSuccess(selected!, 20); Near((float)rotation.CaptureState()[selected!], 1019, .0001f, \u0022successful class hold\u0022);\n        rotation.ReleaseAfterLifecycle(selected!, 30); Near((float)rotation.CaptureState()[selected!], 75, .0001f, \u0022special respawn interval\u0022);\n    }\n\n    static void Population()\n    {\n        var ledger = new PopulationLedger(new PopulationConfiguration()); var empty = new PopulationSnapshot(); Check(ledger.TryReserve(1, EncounterKind.Boss, 1, empty), \u0022reserve\u0022); Check(ledger.Available(EncounterKind.Boss, empty) == 0, \u0022reservation counts\u0022); ledger.Resolve(new(1, RequestResultKind.Failed, 0)); Check(ledger.Available(EncounterKind.Boss, empty) == 1, \u0022failure releases\u0022);\n        Check(ledger.TryReserve(2, EncounterKind.Boss, 1, empty, 10), \u0022reserve deferred\u0022); ledger.Resolve(new(2, RequestResultKind.Deferred, 0)); Check(ledger.Available(EncounterKind.Boss, empty) == 0, \u0022deferred remains reserved\u0022); Check(ledger.Expire(10).SequenceEqual(new long[] { 2 }), \u0022reservation expires\u0022); Check(ledger.Available(EncounterKind.Boss, empty) == 1, \u0022expiry releases\u0022);\n    }\n\n    static void Wave()\n    {\n        var wave = new WaveSequenceController(new WaveSequenceConfiguration { InitialDelayMinimum = 0, InitialDelayMaximum = 0, CombatTimeout = 0, PauseMinimum = 0, PauseMaximum = 0 }, new DeterministicRandom(2)); wave.Start(0); wave.Update(0, false); Check(wave.Update(0, false).IssueWave, \u0022wave issued\u0022); Check(wave.Update(0, false).Completed, \u0022wave completed\u0022);\n        var restored = new WaveSequenceController(new WaveSequenceConfiguration(), new DeterministicRandom(2), wave.Snapshot); Check(restored.State == WaveState.Done, \u0022wave restore\u0022); restored.Cancel(); Check(restored.State == WaveState.Inactive, \u0022wave cancel\u0022);\n    }\n\n    static void Scenarios()\n    {\n        var scenario = new ScenarioController(new[] { new ScenarioStageDefinition(\u0022one\u0022) }); scenario.Start(0); Check(scenario.TryAdvance(0, false) \u0026\u0026 scenario.Status == ScenarioStatus.Complete, \u0022scenario complete\u0022); var rounds = new RoundSessionFlow(); rounds.BeginRound(\u00221\u0022); Check(rounds.IsSessionActive, \u0022round adapter\u0022); rounds.EndRound(); Check(!rounds.IsSessionActive, \u0022round end\u0022); Check(new ContinuousSessionFlow().IsSessionActive, \u0022continuous adapter\u0022);\n        var restored = new ScenarioController(new[] { new ScenarioStageDefinition(\u0022one\u0022) }, scenario.Snapshot); Check(restored.Status == ScenarioStatus.Complete, \u0022scenario restore\u0022); restored.Reset(); Check(restored.Status == ScenarioStatus.Inactive, \u0022scenario reset\u0022);\n    }\n\n    static void Core()\n    {\n        var core = new DirectorCore(new DirectorConfiguration { Tempo = new TempoConfiguration { RelaxMinimumSeconds = 0, RelaxMaximumSeconds = 0 } });\n        var output = core.Tick(new WorldSnapshot { Time = 0, Participants = new[] { new ParticipantSnapshot { Id = \u0022p\u0022 } }, SpawnCandidates = new[] { new SpawnCandidate { Id = \u0022s\u0022, Reachable = true, Spawnable = true, NearestParticipantDistance = 800, ThreatSeparation = 2000 } } });\n        Check(output.Events.Count == 1, \u0022core emits tempo transition\u0022);\n        var multi = new DirectorCore(new DirectorConfiguration { MaximumRequestsPerTick = 2 }, schedule: new RepeatingSchedule()); var requests = multi.Tick(World(0)); Check(requests.SpawnRequests.Count == 2, \u0022multiple requests per tick\u0022);\n    }\n\n    static WorldSnapshot World(double time, string? round = null) =\u003E new() { Time = time, RoundId = round, Participants = new[] { new ParticipantSnapshot { Id = \u0022p\u0022 } }, SpawnCandidates = new[] { new SpawnCandidate { Id = \u0022s\u0022, Reachable = true, Spawnable = true, NearestParticipantDistance = 1000, ThreatSeparation = 3000 } } };\n\n    static void Persistence()\n    {\n        var config = new DirectorConfiguration { Seed = 42 }; Check(DirectorStateCodec.ToJson(new DirectorCore(config).CaptureState()).Contains(\u0022HasTicked\u0022), \u0022pre-tick state serializes\u0022); var first = new DirectorCore(config); first.ApplyPressure(new(\u0022p\u0022, PressureSeverity.Major, \u0022test\u0022), 0); var initial = first.Tick(World(0)); Check(initial.SpawnRequests.Count == 1, \u0022state setup request\u0022);\n        var json = DirectorStateCodec.ToJson(first.CaptureState()); Check(json.Contains(\u0022ConfigurationVersion\u0022), \u0022state JSON\u0022);\n        var restored = new DirectorCore(config, DirectorStateCodec.FromJson(json));\n        var a = first.Tick(World(4)); var b = restored.Tick(World(4)); Check(a.Tempo == b.Tempo \u0026\u0026 a.TeamPressure == b.TeamPressure \u0026\u0026 a.SpawnRequests.SequenceEqual(b.SpawnRequests), \u0022restored core remains deterministic\u0022);\n        var diagnostics = new DirectorTelemetryBuffer(restored, 2); restored.ApplyPressure(new(\u0022p\u0022, PressureSeverity.Minor, \u0022one\u0022), 4); restored.ApplyPressure(new(\u0022p\u0022, PressureSeverity.Minor, \u0022two\u0022), 4); restored.ApplyPressure(new(\u0022p\u0022, PressureSeverity.Minor, \u0022three\u0022), 4); Check(diagnostics.Snapshot().Count == 2, \u0022telemetry bounded\u0022); diagnostics.Clear(); Check(diagnostics.Snapshot().Count == 0, \u0022telemetry clear\u0022);\n        Check(restored.CancelPendingRequests().Count \u003E 0, \u0022cancel pending\u0022);\n    }\n\n    static void RuleSchedules()\n    {\n        var population = new PopulationLedger(new PopulationConfiguration()); var world = World(0); var random = new DeterministicRandom(4);\n        var rule = new RuleBasedEncounterSchedule(new[] { new EncounterRule { Id = \u0022boss\u0022, Kind = EncounterKind.Boss, Count = 1, Priority = 10, CooldownSeconds = 30, Phases = new HashSet\u003CTempoPhase\u003E { TempoPhase.Relax } } });\n        Check(rule.SelectEncounter(world, TempoPhase.Relax, 0, population, random) == EncounterKind.Boss, \u0022rule selection\u0022); rule.SelectionFinalized(false); Check(rule.SelectEncounter(world, TempoPhase.Relax, 0, population, random) == EncounterKind.Boss, \u0022failed rule rolls cooldown back\u0022); rule.SelectionFinalized(true); Check(rule.SelectEncounter(world, TempoPhase.Relax, 0, population, random) is null, \u0022rule cooldown committed\u0022);\n        var milestone = new MilestoneEncounterSchedule(\u0022boss-half\u0022, EncounterKind.Boss, .5f); var halfway = new WorldSnapshot { Time = 0, TeamProgress = .6f, ModeId = \u0022campaign\u0022, RoundId = \u0022r1\u0022 };\n        Check(milestone.SelectEncounter(halfway, TempoPhase.Relax, 0, population, random) == EncounterKind.Boss, \u0022milestone selected\u0022); milestone.SelectionFinalized(false); Check(milestone.SelectEncounter(halfway, TempoPhase.Relax, 0, population, random) == EncounterKind.Boss, \u0022milestone retry\u0022); milestone.SelectionFinalized(true); Check(milestone.SelectEncounter(halfway, TempoPhase.Relax, 0, population, random) is null, \u0022milestone one-shot per round\u0022);\n        var state = milestone.CaptureScheduleState(); var copy = new MilestoneEncounterSchedule(\u0022boss-half\u0022, EncounterKind.Boss, .5f); copy.RestoreScheduleState(state); Check(copy.SelectEncounter(halfway, TempoPhase.Relax, 0, population, random) is null, \u0022milestone persistence\u0022);\n    }\n\n    static void PersistentPolicies()\n    {\n        var configuration = new DirectorConfiguration();\n        var policy = new StatefulPolicy(\u0022tests.policy\u0022);\n        var placement = new StatefulPlacementPolicy(\u0022tests.placement\u0022);\n        var composer = new StatefulComposer(\u0022tests.composer\u0022);\n        var original = new DirectorCore(configuration, policy: policy, placementPolicy: placement, composer: composer);\n        original.Tick(World(0));\n        Check(policy.CallCount \u003E 0 \u0026\u0026 placement.CallCount \u003E 0, \u0022custom policy state setup\u0022);\n\n        var json = DirectorStateCodec.ToJson(original.CaptureState());\n        var restoredPolicy = new StatefulPolicy(\u0022tests.policy\u0022);\n        var restoredPlacement = new StatefulPlacementPolicy(\u0022tests.placement\u0022);\n        var restoredComposer = new StatefulComposer(\u0022tests.composer\u0022);\n        _ = new DirectorCore(configuration, DirectorStateCodec.FromJson(json), policy: restoredPolicy, placementPolicy: restoredPlacement, composer: restoredComposer);\n        Check(restoredPolicy.CallCount == policy.CallCount, \u0022custom Director policy restored\u0022);\n        Check(restoredPlacement.CallCount == placement.CallCount, \u0022custom placement policy restored\u0022);\n        Check(restoredComposer.CallCount == composer.CallCount, \u0022custom composer restored\u0022);\n\n        var threw = false;\n        try { _ = new DirectorCore(configuration, DirectorStateCodec.FromJson(json), policy: new StatefulPolicy(\u0022wrong.policy\u0022), placementPolicy: restoredPlacement, composer: restoredComposer); }\n        catch (InvalidOperationException) { threw = true; }\n        Check(threw, \u0022custom policy identity mismatch rejected\u0022);\n\n        threw = false;\n        try { _ = new DirectorCore(configuration, DirectorStateCodec.FromJson(json)); }\n        catch (InvalidOperationException) { threw = true; }\n        Check(threw, \u0022custom policy state cannot be silently discarded\u0022);\n    }\n\n    static void ConcurrentSchedules()\n    {\n        var schedule = new ConcurrentEncounterSchedule(new[]\n        {\n            new ScheduleLane { Id = \u0022ambient\u0022, Priority = 10, Schedule = new BasicEncounterSchedule(100, 100) },\n            new ScheduleLane { Id = \u0022secondary\u0022, Priority = 5, Schedule = new BasicEncounterSchedule(100, 100) }\n        });\n        var core = new DirectorCore(new DirectorConfiguration { MaximumRequestsPerTick = 2 }, schedule: schedule);\n        var output = core.Tick(World(0)); Check(output.SpawnRequests.Count == 2, \u0022independent schedule lanes share a tick\u0022);\n        var state = schedule.CaptureScheduleState(); var copy = new ConcurrentEncounterSchedule(new[] { new ScheduleLane { Id = \u0022ambient\u0022, Priority = 10, Schedule = new BasicEncounterSchedule(100, 100) }, new ScheduleLane { Id = \u0022secondary\u0022, Priority = 5, Schedule = new BasicEncounterSchedule(100, 100) } }); copy.RestoreScheduleState(state); Check(copy.CaptureScheduleState()[\u0022coordinator.served\u0022].Contains(\u0022ambient\u0022), \u0022concurrent schedule persistence\u0022);\n    }\n\n    static void CompositionAndRetry()\n    {\n        var composer = new WeightedEncounterComposer(new Dictionary\u003CEncounterKind, IReadOnlyList\u003CWeightedArchetype\u003E\u003E { [EncounterKind.Ambient] = new[] { new WeightedArchetype(\u0022grunt\u0022, 1) } });\n        var configuration = new DirectorConfiguration { Retry = new RetryConfiguration { MaximumAttempts = 3, DelaySeconds = 1 } };\n        var core = new DirectorCore(configuration, composer: composer); var first = core.Tick(World(0)).SpawnRequests.Single(); Check(first.Composition.Single() == new EncounterMember(\u0022grunt\u0022, 1) \u0026\u0026 first.Attempt == 1, \u0022weighted composition\u0022);\n        core.ApplyResult(new(first.RequestId, RequestResultKind.Failed, 0, \u0022blocked\u0022)); var saved = DirectorStateCodec.FromJson(DirectorStateCodec.ToJson(core.CaptureState())); Check(saved.RetryQueue.Count == 1, \u0022retry persisted\u0022);\n        var restored = new DirectorCore(configuration, saved, composer: composer); Check(restored.Tick(World(.5)).SpawnRequests.Count == 0, \u0022retry delay\u0022); var retry = restored.Tick(World(1)).SpawnRequests.Single(); Check(retry.Attempt == 2 \u0026\u0026 retry.Composition.SequenceEqual(first.Composition), \u0022retry preserves attempt and composition\u0022);\n        restored.ApplyResult(new(retry.RequestId, RequestResultKind.PartiallySucceeded, 0, \u0022partial\u0022)); Check(restored.CaptureState().RetryQueue.Single().RequestedCount == 1, \u0022partial remainder queued\u0022);\n    }\n\n    static void Resources()\n    {\n        var rules = new[] { new ResourceRule { Category = \u0022healing\u0022, TargetCount = 2, MaximumRequestsPerTick = 2 } }; var resources = new ResourcePopulationController(rules, 4);\n        var core = new DirectorCore(new DirectorConfiguration(), resources: resources); var baseWorld = World(0); var world = new WorldSnapshot { Time = baseWorld.Time, Participants = baseWorld.Participants, SpawnCandidates = baseWorld.SpawnCandidates, Resources = new ResourceWorldSnapshot { Candidates = new[] { new ResourceCandidate { Id = \u0022r1\u0022, Category = \u0022healing\u0022, AreaCapacity = 2 }, new ResourceCandidate { Id = \u0022r2\u0022, Category = \u0022healing\u0022, AreaCapacity = 1 } } } };\n        var output = core.Tick(world); Check(output.ResourceRequests.Count == 2, \u0022resource density requests\u0022); foreach (var request in output.ResourceRequests) core.ApplyResourceResult(new(request.RequestId, RequestResultKind.Succeeded));\n        var state = core.CaptureState(); Check(state.Resources?.Consumed.Count == 2, \u0022resource revisit state captured\u0022);\n        var restoredResources = new ResourcePopulationController(rules, 9); _ = new DirectorCore(new DirectorConfiguration(), state, resources: restoredResources); Check(restoredResources.CaptureState().Consumed.Count == 2, \u0022resource state restored\u0022);\n        var threw = false; try { _ = new ResourcePopulationController(new[] { new ResourceRule { Category = \u0022healing\u0022, TargetCount = 3 } }, state.Resources!.Value); } catch (InvalidOperationException) { threw = true; }\n        Check(threw, \u0022resource definition mismatch rejected\u0022);\n        var clusterRules = new[] { new ResourceRule { Category = \u0022ammo\u0022, TargetCount = 2, MaximumRequestsPerTick = 2, MaximumPerCluster = 1 } }; var clustered = new ResourcePopulationController(clusterRules); var clusteredWorld = new WorldSnapshot { Resources = new ResourceWorldSnapshot { Candidates = new[] { new ResourceCandidate { Id = \u0022a\u0022, Category = \u0022ammo\u0022, ClusterId = \u0022room\u0022 }, new ResourceCandidate { Id = \u0022b\u0022, Category = \u0022ammo\u0022, ClusterId = \u0022room\u0022 } } } }; var clusterRequest = clustered.Tick(clusteredWorld.Resources, clusteredWorld).Single(); Check(clusterRequest.CandidateId is \u0022a\u0022 or \u0022b\u0022, \u0022resource cluster limit\u0022); clustered.ApplyResult(new(clusterRequest.RequestId, RequestResultKind.Succeeded));\n        var clusterCopy = new ResourcePopulationController(clusterRules, clustered.CaptureState()); var changedSnapshot = new ResourceWorldSnapshot { Candidates = new[] { new ResourceCandidate { Id = \u0022replacement\u0022, Category = \u0022ammo\u0022, ClusterId = \u0022room\u0022 } } }; Check(clusterCopy.Tick(changedSnapshot, new WorldSnapshot { Resources = changedSnapshot }).Count == 0, \u0022resource cluster history survives absent candidates\u0022);\n        threw = false; try { _ = new ResourcePopulationController(new[] { new ResourceRule { Category = \u0022healing\u0022, TargetCount = 2, MaximumRequestsPerTick = 2, RequiredTags = new HashSet\u003Cstring\u003E { \u0022locked\u0022 } } }, state.Resources!.Value); } catch (InvalidOperationException) { threw = true; }\n        Check(threw, \u0022resource required-tag mismatch rejected\u0022);\n    }\n\n    static void Orchestration()\n    {\n        var orchestrationConfiguration = new DirectorOrchestrationConfiguration { ScenarioStages = new[] { new ScenarioStageDefinition(\u0022boss\u0022, Encounter: EncounterKind.Boss, MusicCue: \u0022boss_intro\u0022), new ScenarioStageDefinition(\u0022escape\u0022) }, Waves = new WaveSequenceConfiguration { InitialDelayMinimum = 0, InitialDelayMaximum = 0, CombatTimeout = 0, PauseMinimum = 0, PauseMaximum = 0 }, WaveEncounterCount = 3 };\n        var orchestration = new DirectorOrchestration(orchestrationConfiguration); var core = new DirectorCore(new DirectorConfiguration(), orchestration: orchestration); core.StartScenario(0); var stage = core.Tick(World(0)); Check(stage.SpawnRequests.Single().Kind == EncounterKind.Boss \u0026\u0026 stage.Events.Any(x =\u003E x.Name == \u0022music_cue\u0022), \u0022scenario encounter and music cue\u0022); core.ApplyResult(new(stage.SpawnRequests[0].RequestId, RequestResultKind.Succeeded, 1)); core.Tick(World(1)); Check(orchestration.ScenarioStatus == ScenarioStatus.Running, \u0022scenario advances stages\u0022);\n        var saved = core.CaptureState(); var restoredOrchestration = new DirectorOrchestration(orchestrationConfiguration, saved.Orchestration!.Value); _ = new DirectorCore(new DirectorConfiguration(), saved, orchestration: restoredOrchestration); Check(restoredOrchestration.ScenarioStatus == orchestration.ScenarioStatus, \u0022orchestration persistence\u0022);\n        var threw = false; try { _ = new DirectorOrchestration(new DirectorOrchestrationConfiguration { PersistenceId = \u0022wrong\u0022 }, saved.Orchestration.Value); } catch (InvalidOperationException) { threw = true; }\n        Check(threw, \u0022orchestration definition mismatch rejected\u0022);\n        threw = false; try { _ = new DirectorOrchestration(new DirectorOrchestrationConfiguration { ScenarioStages = new[] { new ScenarioStageDefinition(\u0022changed\u0022) } }, saved.Orchestration.Value); } catch (InvalidOperationException) { threw = true; }\n        Check(threw, \u0022orchestration content mismatch rejected\u0022);\n        threw = false; try { _ = new DirectorCore(new DirectorConfiguration(), saved, orchestration: new DirectorOrchestration(orchestrationConfiguration)); } catch (InvalidOperationException) { threw = true; }\n        Check(threw, \u0022fresh orchestration cannot replace restored state\u0022);\n        var waves = new DirectorOrchestration(orchestrationConfiguration); var waveCore = new DirectorCore(new DirectorConfiguration(), orchestration: waves); waveCore.StartWaveSequence(0); waveCore.Tick(World(0)); var issued = waveCore.Tick(World(0)); Check(issued.SpawnRequests.Single().Kind == EncounterKind.CommonWave \u0026\u0026 issued.SpawnRequests[0].RequestedCount == 3, \u0022integrated panic wave\u0022);\n\n        var retryConfiguration = new DirectorConfiguration { Retry = new RetryConfiguration { MaximumAttempts = 2, DelaySeconds = 0 } };\n        var retryScenario = new DirectorOrchestration(new DirectorOrchestrationConfiguration { ScenarioStages = new[] { new ScenarioStageDefinition(\u0022required\u0022, Encounter: EncounterKind.Boss) } }); var retryScenarioCore = new DirectorCore(retryConfiguration, orchestration: retryScenario); retryScenarioCore.StartScenario(0); var failedStage = retryScenarioCore.Tick(World(0)).SpawnRequests.Single(); retryScenarioCore.ApplyResult(new(failedStage.RequestId, RequestResultKind.Failed, 0)); var stageRetry = retryScenarioCore.Tick(World(0)).SpawnRequests.Single(); Check(stageRetry.Attempt == 2 \u0026\u0026 retryScenario.ScenarioStatus == ScenarioStatus.Running, \u0022scenario waits for retried host result\u0022); retryScenarioCore.ApplyResult(new(stageRetry.RequestId, RequestResultKind.Succeeded, 1)); Check(retryScenarioCore.Tick(World(0)).Events.Any(x =\u003E x.Name == \u0022scenario_advanced\u0022) \u0026\u0026 retryScenario.ScenarioStatus == ScenarioStatus.Complete, \u0022scenario advances after retry success\u0022);\n        var retryWaves = new DirectorOrchestration(new DirectorOrchestrationConfiguration { Waves = new WaveSequenceConfiguration { InitialDelayMinimum = 0, InitialDelayMaximum = 0, CombatTimeout = 0, PauseMinimum = 0, PauseMaximum = 0 } }); var retryWaveCore = new DirectorCore(retryConfiguration, orchestration: retryWaves); retryWaveCore.StartWaveSequence(0); retryWaveCore.Tick(World(0)); var failedWave = retryWaveCore.Tick(World(0)).SpawnRequests.Single(); retryWaveCore.ApplyResult(new(failedWave.RequestId, RequestResultKind.Failed, 0)); var waveRetry = retryWaveCore.Tick(World(0)).SpawnRequests.Single(); Check(waveRetry.Attempt == 2 \u0026\u0026 retryWaves.WaveActive, \u0022wave retry bypasses ordinary-schedule suppression\u0022); retryWaveCore.ApplyResult(new(waveRetry.RequestId, RequestResultKind.Succeeded, waveRetry.RequestedCount)); Check(retryWaveCore.Tick(World(0)).Events.Any(x =\u003E x.Name == \u0022wave_sequence_complete\u0022), \u0022wave waits for retry success before completion\u0022);\n        var terminalOrchestration = new DirectorOrchestration(new DirectorOrchestrationConfiguration { ScenarioStages = new[] { new ScenarioStageDefinition(\u0022required\u0022, Encounter: EncounterKind.Boss) } }); var terminalCore = new DirectorCore(new DirectorConfiguration { Retry = new RetryConfiguration { MaximumAttempts = 1 } }, orchestration: terminalOrchestration); terminalCore.StartScenario(0); var terminalRequest = terminalCore.Tick(World(0)).SpawnRequests.Single(); terminalCore.ApplyResult(new(terminalRequest.RequestId, RequestResultKind.Rejected, 0, \u0022veto\u0022)); Check(terminalCore.Tick(World(0)).Events.Any(x =\u003E x.Name == \u0022orchestration_failed\u0022) \u0026\u0026 terminalOrchestration.ScenarioStatus == ScenarioStatus.Failed, \u0022terminal orchestration failure is explicit\u0022);\n    }\n\n    static void AdaptiveCallbacks()\n    {\n        var policy = new AdaptivePolicy(); var core = new DirectorCore(new DirectorConfiguration(), policy: policy, schedule: new SingleKindSchedule(EncounterKind.Boss)); var output = core.Tick(World(0)); Check(output.SpawnRequests.Single().RequestedCount == 1 \u0026\u0026 output.MusicIntensity == .5f \u0026\u0026 output.Events.Any(x =\u003E x.Name == \u0022boss_music\u0022), \u0022adaptive count music and boss callback\u0022);\n        var populationCore = new DirectorCore(new DirectorConfiguration(), policy: policy, schedule: new SingleKindSchedule(EncounterKind.Ambient)); Check(populationCore.Tick(World(0)).SpawnRequests.Single().RequestedCount == 1, \u0022adaptive population limit\u0022);\n        var raisedLimit = new DirectorCore(new DirectorConfiguration { Population = new PopulationConfiguration { Limits = new Dictionary\u003CEncounterKind, int\u003E { [EncounterKind.Ambient] = 0 } } }, policy: policy, schedule: new SingleKindSchedule(EncounterKind.Ambient)); Check(raisedLimit.Tick(World(0)).SpawnRequests.Single().RequestedCount == 1, \u0022adaptive population policy can raise configured limit\u0022);\n        var timing = new RuleBasedEncounterSchedule(new[] { new EncounterRule { Id = \u0022timed\u0022, Kind = EncounterKind.Ambient, CooldownSeconds = 10, Phases = new HashSet\u003CTempoPhase\u003E { TempoPhase.Relax } } }, new ModeEncounterTimingPolicy(new Dictionary\u003Cstring, double\u003E { [\u0022survival\u0022] = .5 })); var ledger = new PopulationLedger(new PopulationConfiguration()); var timedWorld = new WorldSnapshot { Time = 0, ModeId = \u0022survival\u0022 }; Check(timing.SelectEncounter(timedWorld, TempoPhase.Relax, 0, ledger, new DeterministicRandom(1)) is not null, \u0022adaptive timing initial\u0022); timing.SelectionFinalized(true); Check(timing.SelectEncounter(new WorldSnapshot { Time = 4.9, ModeId = \u0022survival\u0022 }, TempoPhase.Relax, 0, ledger, new DeterministicRandom(1)) is null \u0026\u0026 timing.SelectEncounter(new WorldSnapshot { Time = 5, ModeId = \u0022survival\u0022 }, TempoPhase.Relax, 0, ledger, new DeterministicRandom(1)) is not null, \u0022mode cooldown multiplier\u0022);\n        var decisions = 0; var runner = new DirectorHostRunner(new DirectorCore(new DirectorConfiguration()), new ContinuousSessionFlow(), new DelegateWorldSource(() =\u003E World(0)), new DelegateSpawnExecutor(x =\u003E new(x.RequestId, RequestResultKind.Succeeded, x.RequestedCount)), notifications: new DelegateNotificationSink(_ =\u003E decisions\u002B\u002B)); runner.Update(); Check(decisions == 1, \u0022decision notification callback\u0022);\n    }\n\n    static void HostRunner()\n    {\n        var core = new DirectorCore(new DirectorConfiguration()); var flow = new ContinuousSessionFlow(); var executed = 0;\n        var runner = new DirectorHostRunner(core, flow, new DelegateWorldSource(() =\u003E World(0)), new DelegateSpawnExecutor(request =\u003E { executed\u002B\u002B; return new(request.RequestId, RequestResultKind.Succeeded, request.RequestedCount); }));\n        var result = runner.Update(); Check(result.Status == DirectorRunStatus.Updated \u0026\u0026 executed == 1 \u0026\u0026 result.Results.Count == 1, \u0022host executes decisions\u0022);\n        var denied = new DirectorHostRunner(new DirectorCore(new DirectorConfiguration()), flow, new DelegateWorldSource(() =\u003E throw new Exception(\u0022must not capture\u0022)), new DelegateSpawnExecutor(_ =\u003E throw new Exception()), new TestAuthority(false)); Check(denied.Update().Status == DirectorRunStatus.NotAuthoritative, \u0022authority gate\u0022);\n        flow.IsSessionActive = false; Check(runner.Update().Status == DirectorRunStatus.SessionInactive, \u0022session gate\u0022);\n        flow.IsSessionActive = true; var failureRunner = new DirectorHostRunner(new DirectorCore(new DirectorConfiguration()), flow, new DelegateWorldSource(() =\u003E World(0)), new DelegateSpawnExecutor(_ =\u003E throw new InvalidOperationException(\u0022host failure\u0022))); Check(failureRunner.Update().Results[0].Result == RequestResultKind.Failed, \u0022host exception becomes result\u0022);\n        var malformedRunner = new DirectorHostRunner(new DirectorCore(new DirectorConfiguration()), flow, new DelegateWorldSource(() =\u003E World(0)), new DelegateSpawnExecutor(request =\u003E new(request.RequestId \u002B 99, RequestResultKind.Succeeded, request.RequestedCount \u002B 1))); var malformed = malformedRunner.Update().Results.Single(); Check(malformed.RequestId == 1 \u0026\u0026 malformed.Result == RequestResultKind.Failed \u0026\u0026 malformed.SpawnedCount == 0, \u0022host runner normalizes malformed result\u0022);\n    }\n\n    static void OwnershipAndResources()\n    {\n        var allocator = new EncounterOwnershipAllocator(); var first = allocator.Select(new[] { new OwnershipCandidate(\u0022a\u0022, 1, false), new OwnershipCandidate(\u0022b\u0022, 1) }, new DeterministicRandom(1)); Check(first == \u0022b\u0022, \u0022ownership eligibility\u0022);\n        var r1 = new DeterministicRandom(9); var r2 = new DeterministicRandom(9); var candidates = new[] { new OwnershipCandidate(\u0022a\u0022, 1), new OwnershipCandidate(\u0022b\u0022, 3) }; Check(allocator.Select(candidates, r1) == allocator.Select(candidates, r2), \u0022ownership deterministic\u0022);\n        var budget = new ResourceBudget(new Dictionary\u003Cstring, float\u003E { [\u0022health\u0022] = 2 }); Check(budget.TrySpend(\u0022health\u0022, 1.5f) \u0026\u0026 !budget.TrySpend(\u0022health\u0022, 1), \u0022resource budget\u0022); Near(budget.Capture()[\u0022health\u0022], .5f, .0001f, \u0022resource capture\u0022);\n    }\n\n    static MusicEventCatalog TestMusicCatalog() =\u003E new(new[]\n    {\n        new MusicEventDefinition { EventId = \u0022ambient.safe\u0022, TrackId = \u0022ambient\u0022, Priority = MusicPriority.Low },\n        new MusicEventDefinition { EventId = \u0022mobbed\u0022, TrackId = \u0022mob\u0022, Priority = MusicPriority.High, DuckTrackList = new[] { \u0022all\u0022 } },\n        new MusicEventDefinition { EventId = \u0022boss\u0022, TrackId = \u0022boss\u0022, Priority = MusicPriority.Critical, BlockTrackList = new[] { \u0022combat\u0022 } },\n        new MusicEventDefinition { EventId = \u0022combat.low\u0022, TrackId = \u0022combat\u0022, Priority = MusicPriority.Low },\n        new MusicEventDefinition { EventId = \u0022combat.high\u0022, TrackId = \u0022combat\u0022, Priority = MusicPriority.High },\n        new MusicEventDefinition { EventId = \u0022delayed\u0022, TrackId = \u0022stinger\u0022, Priority = MusicPriority.Medium, DelaySeconds = 2 },\n        new MusicEventDefinition { EventId = \u0022intro\u0022, TrackId = \u0022intro\u0022, Priority = MusicPriority.High, AutoQueueEventId = \u0022loop\u0022 },\n        new MusicEventDefinition { EventId = \u0022loop\u0022, TrackId = \u0022loop\u0022, Priority = MusicPriority.High },\n        new MusicEventDefinition { EventId = \u0022death\u0022, TrackId = \u0022death\u0022, Priority = MusicPriority.Critical, Flags = MusicMasterFlags.AllowAfterDeath },\n        new MusicEventDefinition { EventId = \u0022tag.master\u0022, TrackId = \u0022master\u0022, Priority = MusicPriority.High, TimingTags = new[] { new MusicTimingTag(2, 4) }, LoopSeconds = 8 },\n        new MusicEventDefinition { EventId = \u0022tag.child\u0022, TrackId = \u0022child\u0022, Priority = MusicPriority.High, SyncTrackId = \u0022master\u0022, SyncTagIndex = 2 },\n        new MusicEventDefinition { EventId = \u0022split.a\u0022, TrackId = \u0022split\u0022, Priority = MusicPriority.High },\n        new MusicEventDefinition { EventId = \u0022split.b\u0022, TrackId = \u0022split\u0022, Priority = MusicPriority.High, Flags = MusicMasterFlags.PlaySplit }\n    });\n\n    static MusicDecisionBatch MusicBatch(double time, params MusicDecision[] decisions)\n    {\n        var batch = new MusicDecisionBatch { Time = time }; batch.Decisions.AddRange(decisions); return batch;\n    }\n\n    static void MusicAuthority()\n    {\n        var special = new SpecialMusicAlertConfiguration { ArchetypeId = \u0022ambusher\u0022, CloseEvent = \u0022alert.close\u0022, MiddleEvent = \u0022alert.middle\u0022, FarEvent = \u0022alert.far\u0022 };\n        var cues = new MusicCueConfiguration { CombatIntroEvents = new[] { \u0022combat.intro\u0022 }, CombatSecondaryEvent = \u0022combat.secondary\u0022, CombatCloseEvent = \u0022combat.close\u0022, MobbedEvent = \u0022mobbed\u0022, BossApproachingEvent = \u0022boss\u0022, BossTrackId = \u0022boss\u0022, HazardBurningEvent = \u0022hazard.burn\u0022, HazardAttackEvent = \u0022hazard.attack\u0022, HazardRageEvent = \u0022hazard.rage\u0022, SafeAtmosphereEvent = \u0022ambient.safe\u0022, DangerAtmosphereEvent = \u0022ambient.danger\u0022, PositionalStingerEvent = \u0022choir\u0022 };\n        var configuration = RecoveredMusicDirectorDefaults.CreateConfiguration(cues, new[] { special }, 41);\n        var core = new MusicDirectorCore(configuration);\n        var idle = new MusicWorldSnapshot { Time = 0, Participants = new[] { new MusicParticipantSnapshot { Id = \u0022p\u0022 } } };\n        var initial = core.Tick(idle); Check(initial.Decisions.Count(x =\u003E x.Kind == MusicDecisionKind.SetMixerLayer) == 9, \u0022music authority emits initial mixer snapshot\u0022);\n        var combatInput = new MusicParticipantSnapshot { Id = \u0022p\u0022, CumulativeDamage = 10, VisibleCommonThreats = 3, ActiveCommonThreats = 20, ScannedCommonThreats = 5, VeryCloseCommonFactor = 1, AttackingCommonFactor = .4f, CloseCommonAction = .42f, InflictedDamage = true, SpecialThreats = new[] { new SpecialMusicThreat(\u0022ambusher\u0022, 1000, \u0022s1\u0022) } };\n        var combat = core.Tick(new MusicWorldSnapshot { Time = 1, Participants = new[] { combatInput } });\n        Check(combat.Decisions.Any(x =\u003E x.Kind == MusicDecisionKind.Play \u0026\u0026 x.TargetId == \u0022combat.intro\u0022), \u0022music authority starts combat group\u0022);\n        Check(combat.Decisions.Any(x =\u003E x.TargetId == \u0022alert.close\u0022), \u0022music authority selects close special alert\u0022);\n        Near(combat.Decisions.Single(x =\u003E x.LayerId == MusicMixerLayers.MobBeating).Value, .37f, .0001f, \u0022recovered damage duck equation\u0022);\n        Near(combat.Decisions.Single(x =\u003E x.LayerId == MusicMixerLayers.MobRules).Value, 0f, .0001f, \u0022recovered mob pressure equation\u0022);\n        Near(combat.Decisions.Single(x =\u003E x.LayerId == MusicMixerLayers.CombatClose).Value, 0f, .0001f, \u0022recovered close action equation\u0022);\n\n        var boss = core.Tick(new MusicWorldSnapshot { Time = 2, Participants = new[] { new MusicParticipantSnapshot { Id = \u0022p\u0022, BossPresent = true } } });\n        Check(boss.Decisions.Any(x =\u003E x.Kind == MusicDecisionKind.Play \u0026\u0026 x.TargetId == \u0022boss\u0022), \u0022music boss approach transition\u0022);\n        var defeated = core.Tick(new MusicWorldSnapshot { Time = 3, Participants = new[] { new MusicParticipantSnapshot { Id = \u0022p\u0022 } } });\n        Check(defeated.Decisions.Any(x =\u003E x.Kind == MusicDecisionKind.StopTrack \u0026\u0026 x.TargetId == \u0022boss\u0022 \u0026\u0026 x.FadeSeconds == 2), \u0022music boss defeat track stop\u0022);\n        var hazard = core.Tick(new MusicWorldSnapshot { Time = 4, Participants = new[] { new MusicParticipantSnapshot { Id = \u0022p\u0022, HazardPresent = true, HazardBurning = true, HazardAttacking = true, HazardRage = .75f, HazardEmitterId = \u0022h\u0022, HazardPosition = new DirectorVector(1, 2, 3), WanderingHazards = new[] { new WanderingMusicHazard(\u0022w\u0022, .2f, new DirectorVector(4, 5, 6)) } } } });\n        Check(new[] { \u0022hazard.burn\u0022, \u0022hazard.attack\u0022, \u0022hazard.rage\u0022 }.All(id =\u003E hazard.Decisions.Any(x =\u003E x.Kind == MusicDecisionKind.Play \u0026\u0026 x.TargetId == id)), \u0022hazard transition cues\u0022);\n        Near(hazard.Decisions.Single(x =\u003E x.LayerId == MusicMixerLayers.HazardRage).Value, .25f, .0001f, \u0022hazard rage inverse layer\u0022);\n        Check(hazard.Decisions.Any(x =\u003E x.TargetId == \u0022hazard.wandering.tier4\u0022 \u0026\u0026 x.Position is not null), \u0022wandering hazard low-anger tier and position\u0022);\n\n        var generic = core.Tick(new MusicWorldSnapshot { Time = 5, Participants = new[] { new MusicParticipantSnapshot { Id = \u0022p\u0022 } } }, new[] { new MusicGameplayEvent(1, MusicTriggerKind.Play, \u0022p\u0022, \u0022custom.event\u0022) });\n        Check(generic.Decisions.Any(x =\u003E x.TargetId == \u0022custom.event\u0022), \u0022generic gameplay music event\u0022);\n        var duplicate = core.Tick(new MusicWorldSnapshot { Time = 6, Participants = new[] { new MusicParticipantSnapshot { Id = \u0022p\u0022 } } }, new[] { new MusicGameplayEvent(1, MusicTriggerKind.Play, \u0022p\u0022, \u0022should.not.repeat\u0022) });\n        Check(!duplicate.Decisions.Any(x =\u003E x.TargetId == \u0022should.not.repeat\u0022), \u0022music events apply exactly once\u0022);\n        var reveal = core.Tick(new MusicWorldSnapshot { Time = 7, Participants = new[] { new MusicParticipantSnapshot { Id = \u0022p\u0022 } } }, new[] { new MusicGameplayEvent(2, MusicTriggerKind.LargeAreaRevealed, \u0022p\u0022) });\n        var revealLimited = core.Tick(new MusicWorldSnapshot { Time = 8, Participants = new[] { new MusicParticipantSnapshot { Id = \u0022p\u0022 } } }, new[] { new MusicGameplayEvent(3, MusicTriggerKind.LargeAreaRevealed, \u0022p\u0022) });\n        Check(reveal.Decisions.Any(x =\u003E x.TargetId == \u0022world.large_area_reveal\u0022) \u0026\u0026 !revealLimited.Decisions.Any(x =\u003E x.TargetId == \u0022world.large_area_reveal\u0022), \u0022large-area reveal cooldown\u0022);\n    }\n\n    static void MusicRuntimeArbitration()\n    {\n        var catalog = TestMusicCatalog(); var runtime = new MusicRuntime(\u0022p\u0022, catalog);\n        var ambient = runtime.Apply(MusicBatch(0, new MusicDecision(1, MusicDecisionKind.Play, \u0022p\u0022, \u0022ambient.safe\u0022, \u0022test\u0022)));\n        Check(ambient.Actions.Any(x =\u003E x.Kind == MusicRuntimeActionKind.Play \u0026\u0026 x.EventId == \u0022ambient.safe\u0022), \u0022runtime plays resolved event\u0022);\n        Check(runtime.Apply(MusicBatch(0, new MusicDecision(1, MusicDecisionKind.Play, \u0022p\u0022, \u0022ambient.safe\u0022, \u0022duplicate\u0022))).Actions.Count == 0, \u0022runtime decisions apply exactly once\u0022);\n        var mob = runtime.Apply(MusicBatch(1, new MusicDecision(2, MusicDecisionKind.Play, \u0022p\u0022, \u0022mobbed\u0022, \u0022test\u0022)));\n        Check(mob.Actions.Any(x =\u003E x.Kind == MusicRuntimeActionKind.SetTrackVolume \u0026\u0026 x.TrackId == \u0022ambient\u0022 \u0026\u0026 x.Value == .5f), \u0022runtime duck list\u0022);\n        Check(!mob.Actions.Any(x =\u003E x.Kind == MusicRuntimeActionKind.SetTrackVolume \u0026\u0026 x.TrackId == \u0022mob\u0022 \u0026\u0026 x.Value == .5f), \u0022duck source does not duck itself\u0022);\n        runtime.Apply(MusicBatch(2, new MusicDecision(3, MusicDecisionKind.Play, \u0022p\u0022, \u0022boss\u0022, \u0022test\u0022)));\n        var blocked = runtime.Apply(MusicBatch(3, new MusicDecision(4, MusicDecisionKind.Play, \u0022p\u0022, \u0022combat.high\u0022, \u0022test\u0022)));\n        Check(blocked.Results.Single(x =\u003E x.DecisionId == 4).Result == MusicDecisionResultKind.RejectedBlocked, \u0022runtime active block list\u0022);\n\n        var priority = new MusicRuntime(\u0022p\u0022, catalog); priority.Apply(MusicBatch(0, new MusicDecision(10, MusicDecisionKind.Play, \u0022p\u0022, \u0022combat.low\u0022, \u0022test\u0022)));\n        var high = priority.Apply(MusicBatch(1, new MusicDecision(11, MusicDecisionKind.Play, \u0022p\u0022, \u0022combat.high\u0022, \u0022test\u0022)));\n        Check(high.Actions.Any(x =\u003E x.Kind == MusicRuntimeActionKind.StopEvent \u0026\u0026 x.EventId == \u0022combat.low\u0022) \u0026\u0026 high.Actions.Any(x =\u003E x.Kind == MusicRuntimeActionKind.Play \u0026\u0026 x.EventId == \u0022combat.high\u0022), \u0022higher priority replaces same track\u0022);\n        var rejectedLow = priority.Apply(MusicBatch(2, new MusicDecision(12, MusicDecisionKind.Play, \u0022p\u0022, \u0022combat.low\u0022, \u0022test\u0022)));\n        Check(rejectedLow.Results.Single(x =\u003E x.DecisionId == 12).Result == MusicDecisionResultKind.RejectedPriority, \u0022lower priority cannot replace active track\u0022);\n\n        var delayed = new MusicRuntime(\u0022p\u0022, catalog); var deferred = delayed.Apply(MusicBatch(0, new MusicDecision(20, MusicDecisionKind.Play, \u0022p\u0022, \u0022delayed\u0022, \u0022test\u0022)));\n        Check(deferred.Results.Single().Result == MusicDecisionResultKind.Deferred \u0026\u0026 delayed.Tick(1).Actions.Count == 0, \u0022runtime delay queue\u0022);\n        Check(delayed.Tick(2).Actions.Any(x =\u003E x.EventId == \u0022delayed\u0022), \u0022runtime delayed engagement\u0022);\n\n        var tagged = new MusicRuntime(\u0022p\u0022, catalog); tagged.Apply(MusicBatch(0, new MusicDecision(30, MusicDecisionKind.Play, \u0022p\u0022, \u0022tag.master\u0022, \u0022test\u0022)));\n        var tagDeferred = tagged.Apply(MusicBatch(1, new MusicDecision(31, MusicDecisionKind.Play, \u0022p\u0022, \u0022tag.child\u0022, \u0022test\u0022)));\n        Check(tagDeferred.Results.Single().Result == MusicDecisionResultKind.Deferred \u0026\u0026 tagged.Tick(3.9).Actions.Count == 0 \u0026\u0026 tagged.Tick(4).Actions.Any(x =\u003E x.EventId == \u0022tag.child\u0022), \u0022runtime timing-tag alignment\u0022);\n\n        var split = new MusicRuntime(\u0022p\u0022, catalog); split.Apply(MusicBatch(0, new MusicDecision(35, MusicDecisionKind.Play, \u0022p\u0022, \u0022split.a\u0022, \u0022test\u0022))); split.Apply(MusicBatch(1, new MusicDecision(36, MusicDecisionKind.Play, \u0022p\u0022, \u0022split.b\u0022, \u0022test\u0022)));\n        Check(split.Active.Count == 2, \u0022runtime split flag permits concurrent same-track events\u0022);\n\n        var auto = new MusicRuntime(\u0022p\u0022, catalog); var intro = auto.Apply(MusicBatch(0, new MusicDecision(40, MusicDecisionKind.Play, \u0022p\u0022, \u0022intro\u0022, \u0022test\u0022))); var instance = intro.Results.Single().InstanceId;\n        Check(auto.ReportCompleted(instance, 1).Actions.Any(x =\u003E x.EventId == \u0022loop\u0022), \u0022runtime autoqueue on completion\u0022);\n        auto.SetLifecycle(true, false); var deadReject = auto.Apply(MusicBatch(2, new MusicDecision(41, MusicDecisionKind.Play, \u0022p\u0022, \u0022ambient.safe\u0022, \u0022test\u0022), new MusicDecision(42, MusicDecisionKind.Play, \u0022p\u0022, \u0022death\u0022, \u0022test\u0022)));\n        Check(deadReject.Results.Single(x =\u003E x.DecisionId == 41).Result == MusicDecisionResultKind.RejectedLifecycle \u0026\u0026 deadReject.Results.Single(x =\u003E x.DecisionId == 42).Result == MusicDecisionResultKind.Accepted, \u0022runtime after-death gate\u0022);\n        var general = auto.Apply(MusicBatch(3, new MusicDecision(43, MusicDecisionKind.General, \u0022p\u0022, \u0022custom.command\u0022, \u0022test\u0022) { Arguments = new[] { \u0022a\u0022, \u0022b\u0022 } }));\n        Check(general.Actions.Single(x =\u003E x.Kind == MusicRuntimeActionKind.General).Arguments.SequenceEqual(new[] { \u0022a\u0022, \u0022b\u0022 }), \u0022runtime forwards generalized commands\u0022);\n    }\n\n    static void MusicPersistenceAndHost()\n    {\n        var config = RecoveredMusicDirectorDefaults.CreateConfiguration(seed: 99); var first = new MusicDirectorCore(config); first.Tick(new MusicWorldSnapshot { Time = 0, Participants = new[] { new MusicParticipantSnapshot { Id = \u0022p\u0022 } } });\n        first.Tick(new MusicWorldSnapshot { Time = 1, Participants = new[] { new MusicParticipantSnapshot { Id = \u0022p\u0022, VisibleCommonThreats = 2, WeaponActivity = .5f } } });\n        var restored = new MusicDirectorCore(config, MusicStateCodec.DirectorFromJson(MusicStateCodec.DirectorToJson(first.CaptureState())));\n        var nextWorld = new MusicWorldSnapshot { Time = 2, Participants = new[] { new MusicParticipantSnapshot { Id = \u0022p\u0022, VisibleCommonThreats = 1 } } };\n        Check(first.Tick(nextWorld).Decisions.SequenceEqual(restored.Tick(nextWorld).Decisions), \u0022music authority save restore determinism\u0022);\n\n        var catalog = TestMusicCatalog(); var runtime = new MusicRuntime(\u0022p\u0022, catalog); runtime.Apply(MusicBatch(0, new MusicDecision(1, MusicDecisionKind.Play, \u0022p\u0022, \u0022ambient.safe\u0022, \u0022test\u0022), new MusicDecision(2, MusicDecisionKind.Play, \u0022p\u0022, \u0022delayed\u0022, \u0022test\u0022)));\n        var restoredRuntime = new MusicRuntime(\u0022p\u0022, catalog, MusicStateCodec.RuntimeFromJson(MusicStateCodec.RuntimeToJson(runtime.CaptureState())));\n        Check(runtime.Tick(2).Actions.SequenceEqual(restoredRuntime.Tick(2).Actions), \u0022music runtime save restore determinism\u0022);\n\n        var actions = new List\u003CMusicRuntimeAction\u003E(); var hostCore = new MusicDirectorCore(RecoveredMusicDirectorDefaults.CreateConfiguration(new MusicCueConfiguration { CombatIntroEvents = new[] { \u0022ambient.safe\u0022 }, CombatSecondaryEvent = \u0022\u0022, CombatCloseEvent = \u0022\u0022, SafeAtmosphereEvent = \u0022\u0022, DangerAtmosphereEvent = \u0022\u0022, PositionalStingerEvent = \u0022\u0022 })); var hostRuntime = new MusicRuntime(\u0022p\u0022, catalog); var runner = new MusicDirectorHostRunner(hostCore, hostRuntime, new DelegateMusicAudioSink(actions.Add));\n        runner.Update(new MusicWorldSnapshot { Time = 0, Participants = new[] { new MusicParticipantSnapshot { Id = \u0022p\u0022 } } }); runner.Update(new MusicWorldSnapshot { Time = 1, Participants = new[] { new MusicParticipantSnapshot { Id = \u0022p\u0022, VisibleCommonThreats = 1 } } });\n        Check(actions.Any(x =\u003E x.Kind == MusicRuntimeActionKind.Play \u0026\u0026 x.EventId == \u0022ambient.safe\u0022), \u0022music host runner bridges decisions to audio sink\u0022);\n    }\n\n    static void Validation()\n    {\n        var backwards = new DirectorCore(new DirectorConfiguration()); backwards.Tick(World(2)); var threw = false; try { backwards.Tick(World(1)); } catch (InvalidOperationException) { threw = true; }\n        Check(threw, \u0022reject backwards world time\u0022);\n        threw = false; try { _ = new DirectorConfiguration { MaximumRequestsPerTick = 0 }; new DirectorConfiguration { MaximumRequestsPerTick = 0 }.Validate(); } catch (ArgumentOutOfRangeException) { threw = true; }\n        Check(threw, \u0022configuration validation\u0022);\n        threw = false; try { _ = DirectorStateCodec.FromJson(\u0022{}\u0022); } catch (InvalidOperationException) { threw = true; }\n        Check(threw, \u0022state validation\u0022);\n        threw = false; try { new WorldSnapshot { Participants = new[] { new ParticipantSnapshot { Id = \u0022same\u0022 }, new ParticipantSnapshot { Id = \u0022same\u0022 } } }.Validate(); } catch (ArgumentException) { threw = true; }\n        Check(threw, \u0022duplicate participant validation\u0022);\n        var incomplete = new DirectorCore(new DirectorConfiguration()); var incompleteRequest = incomplete.Tick(World(0)).SpawnRequests.Single(); incomplete.ApplyResult(new(incompleteRequest.RequestId, RequestResultKind.Succeeded, 0)); Check(incomplete.CaptureState().RetryQueue.Count == 1, \u0022incomplete success becomes partial retry\u0022);\n        var excessive = new DirectorCore(new DirectorConfiguration()); var excessiveRequest = excessive.Tick(World(0)).SpawnRequests.Single(); threw = false; try { excessive.ApplyResult(new(excessiveRequest.RequestId, RequestResultKind.Succeeded, excessiveRequest.RequestedCount \u002B 1)); } catch (ArgumentOutOfRangeException) { threw = true; }\n        Check(threw, \u0022excessive host result rejected\u0022);\n    }\n\n    static void LongDeterminism()\n    {\n        var configuration = new DirectorConfiguration { Seed = 7823, RequestTimeoutSeconds = 2 }; DirectorCore first = new(configuration); DirectorCore second = new(configuration);\n        for (var index = 0; index \u003C 1000; index\u002B\u002B)\n        {\n            var time = index * .1;\n            if (index % 73 == 0) { var pressure = new PressureEvent(\u0022p\u0022, (PressureSeverity)(index / 73 % 4 \u002B 1), \u0022simulation\u0022); first.ApplyPressure(pressure, time); second.ApplyPressure(pressure, time); }\n            var world = World(time); var a = first.Tick(world); var b = second.Tick(world);\n            Check(a.Tempo == b.Tempo \u0026\u0026 a.TeamPressure == b.TeamPressure \u0026\u0026 a.MusicIntensity == b.MusicIntensity \u0026\u0026 a.SpawnRequests.SequenceEqual(b.SpawnRequests) \u0026\u0026 a.Events.SequenceEqual(b.Events), $\u0022deterministic tick {index}\u0022);\n            foreach (var request in a.SpawnRequests) { var result = new SpawnRequestResult(request.RequestId, RequestResultKind.Succeeded, request.RequestedCount); first.ApplyResult(result); second.ApplyResult(result); }\n            if (index == 500) second = new DirectorCore(configuration, DirectorStateCodec.FromJson(DirectorStateCodec.ToJson(second.CaptureState())));\n        }\n    }\n\n    sealed class RejectAllPlacement : IPlacementPolicy { public bool IsAllowed(SpawnCandidate candidate, PlacementProfile profile, WorldSnapshot world) =\u003E false; public float ScoreAdjustment(SpawnCandidate candidate, PlacementProfile profile, WorldSnapshot world) =\u003E 0; }\n    sealed class TestAuthority : IDirectorAuthority { public TestAuthority(bool value) { IsAuthoritative = value; } public bool IsAuthoritative { get; } }\n    sealed class RepeatingSchedule : IEncounterSchedule { int _index; public EncounterKind? SelectEncounter(WorldSnapshot world, TempoPhase tempo, float teamPressure, PopulationLedger population, DeterministicRandom random) =\u003E _index\u002B\u002B % 2 == 0 ? EncounterKind.Ambient : EncounterKind.Special; public int RequestedCount(EncounterKind kind, WorldSnapshot world) =\u003E 1; }\n    sealed class SingleKindSchedule : IEncounterSchedule { readonly EncounterKind _kind; public SingleKindSchedule(EncounterKind kind) { _kind = kind; } public EncounterKind? SelectEncounter(WorldSnapshot world, TempoPhase tempo, float teamPressure, PopulationLedger population, DeterministicRandom random) =\u003E _kind; public int RequestedCount(EncounterKind kind, WorldSnapshot world) =\u003E 2; }\n    sealed class AdaptivePolicy : IDirectorPolicy, IAdaptiveDirectorPolicy, IAdaptivePopulationPolicy\n    {\n        public bool AllowEncounter(EncounterKind kind, WorldSnapshot world) =\u003E true;\n        public bool CanCoexist(EncounterKind requested, PopulationSnapshot population) =\u003E true;\n        public string SelectArchetype(EncounterKind kind, WorldSnapshot world, DeterministicRandom random) =\u003E \u0022boss\u0022;\n        public int AdjustEncounterCount(EncounterKind kind, int proposedCount, TempoPhase tempo, float teamPressure, WorldSnapshot world) =\u003E 1;\n        public float AdjustMusicIntensity(float proposedIntensity, TempoPhase tempo, float teamPressure, WorldSnapshot world) =\u003E .5f;\n        public bool ShouldPlayBossMusic(EncounterKind kind, WorldSnapshot world) =\u003E true;\n        public int AdjustPopulationLimit(EncounterKind kind, int configuredLimit, WorldSnapshot world) =\u003E 1;\n    }\n\n    static void AdvancedDeterminism()\n    {\n        var configuration = new DirectorConfiguration { Seed = 99, MaximumRequestsPerTick = 3, Retry = new RetryConfiguration { MaximumAttempts = 3, DelaySeconds = .5 } };\n        DirectorCore first = BuildAdvancedCore(configuration); DirectorCore second = BuildAdvancedCore(configuration); first.StartScenario(0); second.StartScenario(0); first.StartWaveSequence(0, 2); second.StartWaveSequence(0, 2);\n        for (var index = 0; index \u003C 500; index\u002B\u002B)\n        {\n            var baseWorld = World(index * .25); var world = new WorldSnapshot { Time = baseWorld.Time, Participants = baseWorld.Participants, SpawnCandidates = baseWorld.SpawnCandidates, Resources = new ResourceWorldSnapshot { Candidates = new[] { new ResourceCandidate { Id = \u0022supply-a\u0022, Category = \u0022supply\u0022, AreaCapacity = 2 }, new ResourceCandidate { Id = \u0022supply-b\u0022, Category = \u0022supply\u0022, AreaCapacity = 1 } } } };\n            var a = first.Tick(world); var b = second.Tick(world); Check(a.SpawnRequests.SequenceEqual(b.SpawnRequests) \u0026\u0026 a.ResourceRequests.SequenceEqual(b.ResourceRequests) \u0026\u0026 a.Events.SequenceEqual(b.Events) \u0026\u0026 a.MusicIntensity == b.MusicIntensity, $\u0022advanced deterministic tick {index}\u0022);\n            foreach (var request in a.SpawnRequests) { var result = new SpawnRequestResult(request.RequestId, request.RequestId % 7 == 0 ? RequestResultKind.Failed : RequestResultKind.Succeeded, request.RequestId % 7 == 0 ? 0 : request.RequestedCount); first.ApplyResult(result); second.ApplyResult(result); }\n            foreach (var request in a.ResourceRequests) { var result = new ResourceSpawnResult(request.RequestId, RequestResultKind.Succeeded); first.ApplyResourceResult(result); second.ApplyResourceResult(result); }\n            if (index == 250) second = BuildAdvancedCore(configuration, DirectorStateCodec.FromJson(DirectorStateCodec.ToJson(second.CaptureState())));\n        }\n    }\n\n    static DirectorCore BuildAdvancedCore(DirectorConfiguration configuration, DirectorState? state = null)\n    {\n        IEncounterSchedule Schedule() =\u003E new ConcurrentEncounterSchedule(new[] { new ScheduleLane { Id = \u0022ambient\u0022, Priority = 10, Schedule = new BasicEncounterSchedule(2, 4) }, new ScheduleLane { Id = \u0022special\u0022, Priority = 5, Schedule = new RuleBasedEncounterSchedule(new[] { new EncounterRule { Id = \u0022special\u0022, Kind = EncounterKind.Special, Count = 1, CooldownSeconds = 5, Probability = .5f, Phases = new HashSet\u003CTempoPhase\u003E { TempoPhase.Relax, TempoPhase.BuildUp, TempoPhase.SustainPeak } } }) } });\n        var composer = new WeightedEncounterComposer(new Dictionary\u003CEncounterKind, IReadOnlyList\u003CWeightedArchetype\u003E\u003E { [EncounterKind.CommonWave] = new[] { new WeightedArchetype(\u0022grunt\u0022, 3), new WeightedArchetype(\u0022brute\u0022, 1) } });\n        var resources = new ResourcePopulationController(new[] { new ResourceRule { Category = \u0022supply\u0022, TargetCount = 2, MaximumRequestsPerTick = 2 } }, 77);\n        var orchestrationConfiguration = new DirectorOrchestrationConfiguration { PersistenceId = \u0022advanced-test-v1\u0022, ScenarioStages = new[] { new ScenarioStageDefinition(\u0022opening\u0022, .5), new ScenarioStageDefinition(\u0022boss\u0022, Encounter: EncounterKind.Boss), new ScenarioStageDefinition(\u0022done\u0022) }, Waves = new WaveSequenceConfiguration { WaveCount = 2, InitialDelayMinimum = 0, InitialDelayMaximum = 0, CombatTimeout = 0, PauseMinimum = .25, PauseMaximum = .25 } };\n        if (state is null) return new(configuration, schedule: Schedule(), composer: composer, resources: resources, orchestration: new(orchestrationConfiguration));\n        var orchestration = state.Value.Orchestration is { } orchestrationState ? new DirectorOrchestration(orchestrationConfiguration, orchestrationState) : null;\n        return new(configuration, state.Value, schedule: Schedule(), composer: composer, resources: resources, orchestration: orchestration);\n    }\n    sealed class StatefulPolicy : IDirectorPolicy, IPersistentDirectorPolicy\n    {\n        public StatefulPolicy(string id) { PersistenceId = id; }\n        public string PersistenceId { get; }\n        public int CallCount { get; private set; }\n        public bool AllowEncounter(EncounterKind kind, WorldSnapshot world) { CallCount\u002B\u002B; return true; }\n        public bool CanCoexist(EncounterKind requested, PopulationSnapshot population) =\u003E true;\n        public string SelectArchetype(EncounterKind kind, WorldSnapshot world, DeterministicRandom random) =\u003E kind.ToString();\n        public IReadOnlyDictionary\u003Cstring, string\u003E CapturePolicyState() =\u003E new Dictionary\u003Cstring, string\u003E { [\u0022calls\u0022] = CallCount.ToString(CultureInfo.InvariantCulture) };\n        public void RestorePolicyState(IReadOnlyDictionary\u003Cstring, string\u003E state) =\u003E CallCount = int.Parse(state[\u0022calls\u0022], CultureInfo.InvariantCulture);\n    }\n    sealed class StatefulPlacementPolicy : IPlacementPolicy, IPersistentPlacementPolicy\n    {\n        public StatefulPlacementPolicy(string id) { PersistenceId = id; }\n        public string PersistenceId { get; }\n        public int CallCount { get; private set; }\n        public bool IsAllowed(SpawnCandidate candidate, PlacementProfile profile, WorldSnapshot world) { CallCount\u002B\u002B; return true; }\n        public float ScoreAdjustment(SpawnCandidate candidate, PlacementProfile profile, WorldSnapshot world) =\u003E 0;\n        public IReadOnlyDictionary\u003Cstring, string\u003E CapturePlacementState() =\u003E new Dictionary\u003Cstring, string\u003E { [\u0022calls\u0022] = CallCount.ToString(CultureInfo.InvariantCulture) };\n        public void RestorePlacementState(IReadOnlyDictionary\u003Cstring, string\u003E state) =\u003E CallCount = int.Parse(state[\u0022calls\u0022], CultureInfo.InvariantCulture);\n    }\n    sealed class StatefulComposer : IEncounterComposer, IPersistentEncounterComposer\n    {\n        public StatefulComposer(string id) { PersistenceId = id; }\n        public string PersistenceId { get; }\n        public int CallCount { get; private set; }\n        public IReadOnlyList\u003CEncounterMember\u003E Compose(EncounterKind kind, int requestedCount, WorldSnapshot world, IDirectorPolicy policy, DeterministicRandom random) { CallCount\u002B\u002B; return new[] { new EncounterMember(\u0022stateful\u0022, requestedCount) }; }\n        public IReadOnlyDictionary\u003Cstring, string\u003E CaptureComposerState() =\u003E new Dictionary\u003Cstring, string\u003E { [\u0022calls\u0022] = CallCount.ToString(CultureInfo.InvariantCulture) };\n        public void RestoreComposerState(IReadOnlyDictionary\u003Cstring, string\u003E state) =\u003E CallCount = int.Parse(state[\u0022calls\u0022], CultureInfo.InvariantCulture);\n    }\n}\n"},{"Ident":"notpointless.chomnr_adaptive_director","Path":"SboxDirector/MusicDirectorCore.cs","FileName":"MusicDirectorCore.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":337987,"Code":"#nullable enable annotations\nnamespace SboxDirector;\n\npublic sealed record MusicEnvelopeState(float Damage, float FastActivity, float SlowActivity, float DamageInflicted, float CommonSight, float MobPressure, float Action, float Threat, float Calm, float AmbientBasis);\n\npublic sealed record MusicParticipantState(\n    string ParticipantId,\n    float PreviousDamage,\n    MusicEnvelopeState Envelopes,\n    bool CombatActive,\n    bool MobbedActive,\n    bool BossPresent,\n    bool HazardBurning,\n    bool HazardAttacking,\n    bool HazardRaging,\n    bool WanderingHazardActive,\n    bool DangerAtmosphereActive,\n    bool ScenarioEnded,\n    double AtmosphereReadyAt,\n    double PositionalStingerReadyAt,\n    double WanderingHazardReadyAt,\n    double LargeAreaRevealReadyAt,\n    IReadOnlyDictionary\u003Cstring, double\u003E SpecialAlertReadyAt,\n    IReadOnlyDictionary\u003Cstring, float\u003E LastMixerLayers);\n\npublic sealed record MusicDirectorState(\n    string ConfigurationVersion,\n    bool HasTicked,\n    double LastTickTime,\n    long LastGameplayEventSequence,\n    long NextDecisionId,\n    RandomState Random,\n    IReadOnlyList\u003CMusicParticipantState\u003E Participants);\n\npublic sealed class MusicDirectorCore\n{\n    readonly MusicDirectorConfiguration _configuration;\n    DeterministicRandom _random;\n    readonly Dictionary\u003Cstring, ParticipantMemory\u003E _participants = new(StringComparer.Ordinal);\n    long _lastEventSequence;\n    long _nextDecisionId = 1;\n    double _lastTickTime;\n    bool _hasTicked;\n\n    public MusicDirectorCore(MusicDirectorConfiguration configuration)\n    {\n        _configuration = configuration; configuration.Validate(); _random = new(configuration.Seed);\n    }\n\n    public MusicDirectorCore(MusicDirectorConfiguration configuration, MusicDirectorState state)\n    {\n        _configuration = configuration; configuration.Validate();\n        if (!string.Equals(configuration.Version, state.ConfigurationVersion, StringComparison.Ordinal)) throw new InvalidOperationException($\u0022Music state version \u0027{state.ConfigurationVersion}\u0027 does not match configuration version \u0027{configuration.Version}\u0027.\u0022);\n        if (state.NextDecisionId \u003C= 0 || state.Participants is null || state.Participants.Select(x =\u003E x.ParticipantId).Distinct(StringComparer.Ordinal).Count() != state.Participants.Count) throw new InvalidOperationException(\u0022Music Director state is incomplete or invalid.\u0022);\n        _random = new(state.Random); _hasTicked = state.HasTicked; _lastTickTime = state.LastTickTime; _lastEventSequence = state.LastGameplayEventSequence; _nextDecisionId = state.NextDecisionId;\n        foreach (var participant in state.Participants)\n        {\n            if (string.IsNullOrWhiteSpace(participant.ParticipantId) || participant.SpecialAlertReadyAt is null || participant.LastMixerLayers is null) throw new InvalidOperationException(\u0022Music participant state is incomplete.\u0022);\n            _participants.Add(participant.ParticipantId, new ParticipantMemory(participant));\n        }\n    }\n\n    public MusicDecisionBatch Tick(MusicWorldSnapshot world, IReadOnlyList\u003CMusicGameplayEvent\u003E? gameplayEvents = null)\n    {\n        if (_configuration.ValidateSnapshots) world.Validate();\n        if (_hasTicked \u0026\u0026 world.Time \u003C _lastTickTime) throw new InvalidOperationException(\u0022Music world time cannot move backwards.\u0022);\n        var delta = _hasTicked ? world.Time - _lastTickTime : 0;\n        _hasTicked = true; _lastTickTime = world.Time;\n        var output = new MusicDecisionBatch { Time = world.Time };\n\n        foreach (var input in world.Participants.OrderBy(x =\u003E x.Id, StringComparer.Ordinal)) if (!_participants.ContainsKey(input.Id)) _participants.Add(input.Id, new ParticipantMemory(input.Id, input.CumulativeDamage, _configuration.SpecialAlerts));\n        ApplyGameplayEvents(world, gameplayEvents ?? Array.Empty\u003CMusicGameplayEvent\u003E(), output);\n        foreach (var input in world.Participants.OrderBy(x =\u003E x.Id, StringComparer.Ordinal))\n        {\n            var state = _participants[input.Id];\n            if (world.ScenarioEnded \u0026\u0026 !state.ScenarioEnded) output.Decisions.Add(NewDecision(MusicDecisionKind.General, input.Id, \u0022scenario_ended\u0022, \u0022world scenario ended\u0022));\n            state.ScenarioEnded = world.ScenarioEnded;\n            UpdateParticipant(input, state, delta, world.Time, output);\n        }\n        return output;\n    }\n\n    void ApplyGameplayEvents(MusicWorldSnapshot world, IReadOnlyList\u003CMusicGameplayEvent\u003E events, MusicDecisionBatch output)\n    {\n        if (events.Select(x =\u003E x.Sequence).Distinct().Count() != events.Count) throw new ArgumentException(\u0022Music gameplay event sequences must be unique within a tick.\u0022);\n        foreach (var input in events.OrderBy(x =\u003E x.Sequence))\n        {\n            if (input.Sequence \u003C= _lastEventSequence) continue;\n            if (!float.IsFinite(input.FadeSeconds) || input.FadeSeconds \u003C 0) throw new ArgumentException(\u0022Music event fades must be finite and non-negative.\u0022);\n            _lastEventSequence = input.Sequence;\n            var targets = string.IsNullOrWhiteSpace(input.ParticipantId)\n                ? world.Participants.Select(x =\u003E x.Id).OrderBy(x =\u003E x, StringComparer.Ordinal).ToArray()\n                : new[] { input.ParticipantId };\n            foreach (var participantId in targets)\n            {\n                var kind = input.Kind switch\n                {\n                    MusicTriggerKind.Play =\u003E MusicDecisionKind.Play,\n                    MusicTriggerKind.StopEvent =\u003E MusicDecisionKind.StopEvent,\n                    MusicTriggerKind.StopTrack =\u003E MusicDecisionKind.StopTrack,\n                    MusicTriggerKind.StopAll =\u003E MusicDecisionKind.StopAll,\n                    MusicTriggerKind.AmbientMob or MusicTriggerKind.MobSpawn or MusicTriggerKind.MobSpawnBehind or MusicTriggerKind.LargeAreaRevealed or MusicTriggerKind.LandmarkRevealed =\u003E MusicDecisionKind.Play,\n                    _ =\u003E MusicDecisionKind.General\n                };\n                var target = input.Kind switch\n                {\n                    MusicTriggerKind.Reset =\u003E \u0022reset\u0022,\n                    MusicTriggerKind.ScenarioEnded =\u003E \u0022scenario_ended\u0022,\n                    MusicTriggerKind.ParticipantRestored =\u003E \u0022participant_restored\u0022,\n                    MusicTriggerKind.AmbientMob =\u003E _configuration.Cues.AmbientMobEvent,\n                    MusicTriggerKind.MobSpawn =\u003E _configuration.Cues.MobSpawnEvent,\n                    MusicTriggerKind.MobSpawnBehind =\u003E _configuration.Cues.MobSpawnBehindEvent,\n                    MusicTriggerKind.LargeAreaRevealed =\u003E _configuration.Cues.LargeAreaRevealEvent,\n                    _ =\u003E input.TargetId\n                };\n                if (input.Kind == MusicTriggerKind.LargeAreaRevealed \u0026\u0026 _participants.TryGetValue(participantId, out var revealState))\n                {\n                    if (revealState.LargeAreaRevealReadyAt \u003E world.Time) continue;\n                    revealState.LargeAreaRevealReadyAt = world.Time \u002B _configuration.Dynamic.LargeAreaRevealCooldownSeconds;\n                }\n                if (kind == MusicDecisionKind.Play \u0026\u0026 string.IsNullOrWhiteSpace(target)) continue;\n                output.Decisions.Add(NewDecision(kind, participantId, target, $\u0022gameplay:{input.Kind}\u0022) with { FadeSeconds = input.FadeSeconds, EmitterId = input.EmitterId, Position = input.Position, Arguments = input.Arguments });\n                if ((input.Kind is MusicTriggerKind.Reset or MusicTriggerKind.ParticipantRestored) \u0026\u0026 _participants.TryGetValue(participantId, out var state))\n                {\n                    state.ResetDynamic();\n                    var current = world.Participants.FirstOrDefault(x =\u003E string.Equals(x.Id, participantId, StringComparison.Ordinal)); if (current is not null) state.PreviousDamage = current.CumulativeDamage;\n                }\n            }\n        }\n    }\n\n    void UpdateParticipant(MusicParticipantSnapshot input, ParticipantMemory state, double delta, double time, MusicDecisionBatch output)\n    {\n        var d = _configuration.Dynamic;\n        var dt = (float)Math.Max(0, delta);\n        var damageDelta = Math.Max(0, input.CumulativeDamage - state.PreviousDamage); state.PreviousDamage = input.CumulativeDamage;\n        state.Damage = Advance(state.Damage, damageDelta \u003E 0 ? 1 : 0, dt, d.DamageRisePerSecond, d.DamageDecaySeconds);\n        state.FastActivity = Advance(state.FastActivity, input.WeaponActivity, dt, d.FastActivityRisePerSecond, d.FastActivityDecaySeconds);\n        state.SlowActivity = Advance(state.SlowActivity, input.WeaponActivity, dt, d.SlowActivityRisePerSecond, d.SlowActivityDecaySeconds);\n        state.DamageInflicted = Advance(state.DamageInflicted, input.InflictedDamage ? 1 : 0, dt, d.InflictedDamageRisePerSecond, d.InflictedDamageDecaySeconds);\n        state.CommonSight = Advance(state.CommonSight, Clamp01(input.VisibleCommonThreats / d.CommonSightNormalizer), dt, float.PositiveInfinity, d.CommonSightDecaySeconds);\n\n        var damageDriver = Math.Min(5 * input.VeryCloseCommonFactor, state.Damage);\n        var damageDuck = RemapClamped(damageDriver, d.DamageDuckInputMinimum, d.DamageDuckInputMaximum, 0, d.DamageDuckOutputMaximum);\n        var mobDriver = Math.Min(4 * input.VeryCloseCommonFactor, state.Damage);\n        var mobTarget = mobDriver \u003C d.MobDamageMinimum ? 0 : RemapClamped(mobDriver, d.MobDamageMinimum, d.MobDamageMaximum, d.MobOutputMinimum, d.MobOutputMaximum);\n        if (input.IsIncapacitated) mobTarget = 0;\n        state.MobPressure = Advance(state.MobPressure, mobTarget, dt, float.PositiveInfinity, d.MobPressureDecaySeconds);\n\n        var rawAction = Math.Max(Math.Max(input.GlobalCombatActive || input.BossPresent ? 1 : 0, 5 * input.AttackingCommonFactor), state.FastActivity);\n        rawAction = Clamp01(rawAction);\n        var rawThreat = Clamp01(Math.Max(input.MobPressureActive || input.BossPresent || input.HazardPresent ? 1 : 0, 2 * input.AttackingCommonFactor));\n        state.Action = Advance(state.Action, rawAction, dt, float.PositiveInfinity, d.ActionDecaySeconds);\n        state.Threat = Advance(state.Threat, rawThreat, dt, float.PositiveInfinity, d.ThreatDecaySeconds);\n        state.Calm = Advance(state.Calm, 1 - rawAction, dt, float.PositiveInfinity, d.CalmDecaySeconds);\n        var ambientTarget = RemapClamped(state.Calm, d.AmbientInputMinimum, d.AmbientInputMaximum, 0, 1);\n        state.AmbientBasis = Advance(state.AmbientBasis, ambientTarget, dt, float.PositiveInfinity, d.AmbientDecaySeconds);\n        var ambient = state.AmbientBasis * (1 - input.HazardRage);\n        var mobNetwork = state.MobPressure \u003C .1f ? 0 : state.MobPressure;\n        var secondary = Clamp01(2 * state.DamageInflicted);\n        var close = RemapClamped(input.CloseCommonAction, 0, d.CloseActionMaximum, 0, 1);\n\n        UpdateCombat(input, state, time, output);\n        EmitLayer(state, output, input.Id, MusicMixerLayers.MobBeating, damageDuck, \u0022damage/common pressure\u0022);\n        EmitLayer(state, output, input.Id, MusicMixerLayers.MobRules, 1 - mobNetwork, \u0022mob pressure\u0022);\n        EmitLayer(state, output, input.Id, MusicMixerLayers.HazardRage, 1 - input.HazardRage, \u0022hazard rage\u0022);\n        EmitLayer(state, output, input.Id, MusicMixerLayers.Combat, state.CombatActive ? 1 : 0, \u0022combat state\u0022);\n        EmitLayer(state, output, input.Id, MusicMixerLayers.CombatSecondary, 1 - secondary, \u0022damage inflicted\u0022);\n        EmitLayer(state, output, input.Id, MusicMixerLayers.CombatClose, 1 - close, \u0022close common action\u0022);\n        EmitLayer(state, output, input.Id, MusicMixerLayers.Adrenaline, input.Adrenaline, \u0022adrenaline\u0022);\n        EmitLayer(state, output, input.Id, MusicMixerLayers.Checkpoint, input.InCheckpoint ? 1 : 0, \u0022checkpoint state\u0022);\n        EmitLayer(state, output, input.Id, MusicMixerLayers.Ambient, ambient, \u0022calm and hazard rage\u0022);\n\n        UpdateMobbed(input, state, mobNetwork, output);\n        UpdateBoss(input, state, output);\n        UpdateHazard(input, state, time, output);\n        UpdateSpecialAlerts(input, state, time, ambient, output);\n        UpdateAtmosphere(input, state, time, ambient, output);\n        UpdatePositionalStinger(input, state, time, output);\n    }\n\n    void UpdateCombat(MusicParticipantSnapshot input, ParticipantMemory state, double time, MusicDecisionBatch output)\n    {\n        var allowed = input.DynamicMusicAllowed \u0026\u0026 input.IsEligible \u0026\u0026 input.IsAlive \u0026\u0026 !input.IsIncapacitated \u0026\u0026 !input.BossPresent \u0026\u0026 !input.HazardAttacking;\n        if (!state.CombatActive \u0026\u0026 allowed \u0026\u0026 input.VisibleCommonThreats \u003E 0)\n        {\n            state.CombatActive = true;\n            var intros = _configuration.Cues.CombatIntroEvents.Where(x =\u003E !string.IsNullOrWhiteSpace(x)).ToArray();\n            if (intros.Length \u003E 0) Play(output, input.Id, intros[_random.NextInt(intros.Length)], \u0022combat started\u0022);\n            PlayIfConfigured(output, input.Id, _configuration.Cues.CombatSecondaryEvent, \u0022combat secondary started\u0022);\n            PlayIfConfigured(output, input.Id, _configuration.Cues.CombatCloseEvent, \u0022combat close layer started\u0022);\n        }\n        var stopSignal = Math.Max(state.CommonSight, state.SlowActivity);\n        var canStop = !input.MobPressureActive \u0026\u0026 !input.IsIncapacitated \u0026\u0026 !input.BossPresent \u0026\u0026 !input.HazardAttacking \u0026\u0026 input.ActiveCommonThreats \u003C= _configuration.Dynamic.CombatStopActiveCount \u0026\u0026 input.ScannedCommonThreats \u003C= _configuration.Dynamic.CombatStopScannedCount;\n        if (state.CombatActive \u0026\u0026 (!allowed || canStop \u0026\u0026 stopSignal \u003C _configuration.Dynamic.CombatStopSignal))\n        {\n            state.CombatActive = false;\n            foreach (var track in _configuration.Cues.CombatTrackIds.Where(x =\u003E !string.IsNullOrWhiteSpace(x))) output.Decisions.Add(NewDecision(MusicDecisionKind.StopTrack, input.Id, track, \u0022combat ended\u0022) with { FadeSeconds = _configuration.CombatStopFadeSeconds });\n        }\n    }\n\n    void UpdateMobbed(MusicParticipantSnapshot input, ParticipantMemory state, float mobNetwork, MusicDecisionBatch output)\n    {\n        var active = mobNetwork \u003E 0;\n        if (active \u0026\u0026 !state.MobbedActive) PlayIfConfigured(output, input.Id, _configuration.Cues.MobbedEvent, \u0022mob pressure entered\u0022);\n        else if (!active \u0026\u0026 state.MobbedActive \u0026\u0026 !string.IsNullOrWhiteSpace(_configuration.Cues.MobbedEvent)) output.Decisions.Add(NewDecision(MusicDecisionKind.StopEvent, input.Id, _configuration.Cues.MobbedEvent, \u0022mob pressure cleared\u0022));\n        state.MobbedActive = active;\n    }\n\n    void UpdateBoss(MusicParticipantSnapshot input, ParticipantMemory state, MusicDecisionBatch output)\n    {\n        if (input.BossPresent \u0026\u0026 !state.BossPresent) PlayIfConfigured(output, input.Id, _configuration.Cues.BossApproachingEvent, \u0022boss approaching\u0022);\n        else if (!input.BossPresent \u0026\u0026 state.BossPresent \u0026\u0026 !string.IsNullOrWhiteSpace(_configuration.Cues.BossTrackId)) output.Decisions.Add(NewDecision(MusicDecisionKind.StopTrack, input.Id, _configuration.Cues.BossTrackId, \u0022boss defeated\u0022) with { FadeSeconds = _configuration.BossStopFadeSeconds });\n        state.BossPresent = input.BossPresent;\n    }\n\n    void UpdateHazard(MusicParticipantSnapshot input, ParticipantMemory state, double time, MusicDecisionBatch output)\n    {\n        TransitionEvent(input.Id, input.HazardBurning, ref state.HazardBurning, _configuration.Cues.HazardBurningEvent, _configuration.HazardStopFadeSeconds, \u0022hazard burning\u0022, output);\n        TransitionEvent(input.Id, input.HazardAttacking, ref state.HazardAttacking, _configuration.Cues.HazardAttackEvent, _configuration.HazardStopFadeSeconds, \u0022hazard attacking\u0022, output);\n        var raging = input.HazardRage \u003E 0;\n        if (raging \u0026\u0026 !state.HazardRaging \u0026\u0026 !string.IsNullOrWhiteSpace(_configuration.Cues.HazardRageEvent)) output.Decisions.Add(NewDecision(MusicDecisionKind.Play, input.Id, _configuration.Cues.HazardRageEvent, \u0022hazard rage started\u0022) with { EmitterId = input.HazardEmitterId, Position = input.HazardPosition });\n        else if (!raging \u0026\u0026 state.HazardRaging \u0026\u0026 !string.IsNullOrWhiteSpace(_configuration.Cues.HazardRageEvent)) output.Decisions.Add(NewDecision(MusicDecisionKind.StopEvent, input.Id, _configuration.Cues.HazardRageEvent, \u0022hazard rage ended\u0022) with { FadeSeconds = _configuration.HazardRageStopFadeSeconds });\n        state.HazardRaging = raging;\n\n        var wandering = input.WanderingHazards.OrderBy(x =\u003E x.Anger).ThenBy(x =\u003E x.Id, StringComparer.Ordinal).FirstOrDefault();\n        if (wandering is null)\n        {\n            if (state.WanderingHazardActive \u0026\u0026 !string.IsNullOrWhiteSpace(_configuration.Cues.WanderingHazardTrackId)) output.Decisions.Add(NewDecision(MusicDecisionKind.StopTrack, input.Id, _configuration.Cues.WanderingHazardTrackId, \u0022no wandering hazard\u0022) with { FadeSeconds = _configuration.HazardStopFadeSeconds });\n            state.WanderingHazardActive = false;\n        }\n        else if (time \u003E= state.WanderingHazardReadyAt)\n        {\n            var tier = wandering.Anger \u003C .25f ? 0 : wandering.Anger \u003C .5f ? 1 : wandering.Anger \u003C .75f ? 2 : 3;\n            output.Decisions.Add(NewDecision(MusicDecisionKind.Play, input.Id, _configuration.Cues.WanderingHazardEventsLowToHigh[tier], \u0022wandering hazard anger tier\u0022) with { EmitterId = wandering.Id, Position = wandering.Position });\n            state.WanderingHazardActive = true; state.WanderingHazardReadyAt = time \u002B _configuration.Dynamic.WanderingHazardIntervalSeconds;\n        }\n    }\n\n    void TransitionEvent(string participantId, bool active, ref bool previous, string eventId, float fade, string reason, MusicDecisionBatch output)\n    {\n        if (active \u0026\u0026 !previous) PlayIfConfigured(output, participantId, eventId, reason \u002B \u0022 started\u0022);\n        else if (!active \u0026\u0026 previous \u0026\u0026 !string.IsNullOrWhiteSpace(eventId)) output.Decisions.Add(NewDecision(MusicDecisionKind.StopEvent, participantId, eventId, reason \u002B \u0022 ended\u0022) with { FadeSeconds = fade });\n        previous = active;\n    }\n\n    void UpdateSpecialAlerts(MusicParticipantSnapshot input, ParticipantMemory state, double time, float ambient, MusicDecisionBatch output)\n    {\n        if (ambient \u003C= _configuration.SpecialAlertMinimumAmbient) return;\n        var profiles = _configuration.SpecialAlerts;\n        foreach (var threat in input.SpecialThreats.OrderBy(x =\u003E x.ArchetypeId, StringComparer.Ordinal).ThenBy(x =\u003E x.EmitterId, StringComparer.Ordinal))\n        {\n            var index = -1;\n            for (var i = 0; i \u003C profiles.Count; i\u002B\u002B) if (string.Equals(profiles[i].ArchetypeId, threat.ArchetypeId, StringComparison.Ordinal)) { index = i; break; }\n            state.SpecialReadyAt.TryGetValue(threat.ArchetypeId, out var readyAt);\n            if (index \u003C 0 || threat.Distance \u003E profiles[index].ScanMaximumDistance || readyAt \u003E time) continue;\n            var profile = profiles[index];\n            var eventId = threat.Distance \u003C= profile.CloseMaximumDistance ? profile.CloseEvent : threat.Distance \u003E= profile.FarMinimumDistance ? profile.FarEvent : profile.MiddleEvent;\n            output.Decisions.Add(NewDecision(MusicDecisionKind.Play, input.Id, eventId, $\u0022special alert:{threat.ArchetypeId}\u0022) with { EmitterId = threat.EmitterId, Position = threat.Position });\n            var min = threat.Distance \u003C= profile.CloseMaximumDistance ? profile.RandomMultiplierMaximum : threat.Distance \u003E= profile.FarMinimumDistance ? profile.RandomMultiplierMaximum * 2 : profile.RandomMultiplierMaximum;\n            var max = threat.Distance \u003C= profile.CloseMaximumDistance ? (int)MathF.Round(profile.RandomMultiplierMaximum * 1.4f) : threat.Distance \u003E= profile.FarMinimumDistance ? (int)MathF.Round(profile.RandomMultiplierMaximum * 2.5f) : profile.RandomMultiplierMaximum * 2;\n            var multiplier = min \u002B _random.NextInt(max - min \u002B 1);\n            state.SpecialReadyAt[threat.ArchetypeId] = time \u002B multiplier * profile.IntervalBeats * (60.0 / profile.Bpm);\n            for (var step = 1; step \u003C profiles.Count; step\u002B\u002B)\n            {\n                var peer = profiles[(index \u002B step) % profiles.Count].ArchetypeId;\n                var floor = time \u002B _configuration.SpecialPeerStaggerStartSeconds \u002B (step - 1) * _configuration.SpecialPeerStaggerStepSeconds;\n                state.SpecialReadyAt.TryGetValue(peer, out var peerReady); if (peerReady \u003C floor) state.SpecialReadyAt[peer] = floor;\n            }\n            break;\n        }\n    }\n\n    void UpdateAtmosphere(MusicParticipantSnapshot input, ParticipantMemory state, double time, float ambient, MusicDecisionBatch output)\n    {\n        var danger = state.Threat \u003E 0;\n        if (danger \u0026\u0026 !state.DangerAtmosphereActive \u0026\u0026 time \u003E= state.AtmosphereReadyAt)\n        {\n            PlayIfConfigured(output, input.Id, _configuration.Cues.DangerAtmosphereEvent, \u0022danger atmosphere\u0022); state.DangerAtmosphereActive = true; state.AtmosphereReadyAt = time \u002B _configuration.AtmosphereRetrySeconds;\n        }\n        else if (!danger \u0026\u0026 state.DangerAtmosphereActive \u0026\u0026 ambient \u003C= 0)\n        {\n            if (!string.IsNullOrWhiteSpace(_configuration.Cues.DangerAtmosphereEvent)) output.Decisions.Add(NewDecision(MusicDecisionKind.StopEvent, input.Id, _configuration.Cues.DangerAtmosphereEvent, \u0022danger atmosphere cleared\u0022));\n            state.DangerAtmosphereActive = false; state.AtmosphereReadyAt = time \u002B _configuration.AtmosphereTransitionSeconds;\n        }\n        else if (!danger \u0026\u0026 !state.DangerAtmosphereActive \u0026\u0026 ambient \u003E 0 \u0026\u0026 time \u003E= state.AtmosphereReadyAt)\n        {\n            PlayIfConfigured(output, input.Id, _configuration.Cues.SafeAtmosphereEvent, \u0022safe atmosphere\u0022); state.AtmosphereReadyAt = time \u002B _configuration.AtmosphereRetrySeconds \u002B 10;\n        }\n    }\n\n    void UpdatePositionalStinger(MusicParticipantSnapshot input, ParticipantMemory state, double time, MusicDecisionBatch output)\n    {\n        if (state.AmbientBasis \u003C= _configuration.Dynamic.PositionalStingerAmbientMinimum || time \u003C state.PositionalStingerReadyAt || string.IsNullOrWhiteSpace(_configuration.Cues.PositionalStingerEvent)) return;\n        var candidates = input.PositionalCandidates.Where(x =\u003E x.Eligible).OrderBy(x =\u003E x.Id, StringComparer.Ordinal).ToArray(); if (candidates.Length == 0) return;\n        var selected = candidates[_random.NextInt(candidates.Length)];\n        output.Decisions.Add(NewDecision(MusicDecisionKind.Play, input.Id, _configuration.Cues.PositionalStingerEvent, \u0022positional stinger\u0022) with { EmitterId = selected.Id, Position = selected.Position, StartOffsetSeconds = .1f });\n        var multiplier = 1 \u002B _random.NextInt(_configuration.Dynamic.PositionalStingerRandomMultiplierMaximum);\n        state.PositionalStingerReadyAt = time \u002B multiplier * _configuration.Dynamic.PositionalStingerBeats * (60.0 / _configuration.Dynamic.PositionalStingerBpm);\n    }\n\n    void EmitLayer(ParticipantMemory state, MusicDecisionBatch output, string participantId, string layer, float value, string reason)\n    {\n        value = Clamp01(value);\n        if (state.LastLayers.TryGetValue(layer, out var previous) \u0026\u0026 MathF.Abs(previous - value) \u003C= .000001f) return;\n        state.LastLayers[layer] = value;\n        output.Decisions.Add(NewDecision(MusicDecisionKind.SetMixerLayer, participantId, \u0022\u0022, reason) with { LayerId = layer, Value = value });\n    }\n\n    void PlayIfConfigured(MusicDecisionBatch output, string participantId, string eventId, string reason) { if (!string.IsNullOrWhiteSpace(eventId)) Play(output, participantId, eventId, reason); }\n    void Play(MusicDecisionBatch output, string participantId, string eventId, string reason) =\u003E output.Decisions.Add(NewDecision(MusicDecisionKind.Play, participantId, eventId, reason));\n    MusicDecision NewDecision(MusicDecisionKind kind, string participantId, string target, string reason) =\u003E new(_nextDecisionId\u002B\u002B, kind, participantId, target, reason);\n\n    static float Advance(float current, float target, float delta, float risePerSecond, float decaySeconds)\n    {\n        target = Clamp01(target);\n        if (target \u003E= current) return float.IsPositiveInfinity(risePerSecond) ? target : Math.Min(target, current \u002B risePerSecond * delta);\n        return Math.Max(target, current - delta / decaySeconds);\n    }\n    static float RemapClamped(float value, float inputMin, float inputMax, float outputMin, float outputMax) =\u003E inputMax \u003C= inputMin ? outputMax : outputMin \u002B Clamp01((value - inputMin) / (inputMax - inputMin)) * (outputMax - outputMin);\n    static float Clamp01(float value) =\u003E Math.Clamp(value, 0, 1);\n\n    public MusicDirectorState CaptureState() =\u003E new(\n        _configuration.Version, _hasTicked, _lastTickTime, _lastEventSequence, _nextDecisionId, _random.State,\n        _participants.Values.OrderBy(x =\u003E x.Id, StringComparer.Ordinal).Select(x =\u003E x.Capture()).ToArray());\n\n    public bool RemoveParticipant(string participantId) =\u003E _participants.Remove(participantId);\n    public void Reset()\n    {\n        _participants.Clear(); _lastEventSequence = 0; _nextDecisionId = 1; _lastTickTime = 0; _hasTicked = false; _random = new(_configuration.Seed);\n    }\n\n    sealed class ParticipantMemory\n    {\n        public readonly string Id;\n        public float PreviousDamage, Damage, FastActivity, SlowActivity, DamageInflicted, CommonSight, MobPressure, Action, Threat, Calm, AmbientBasis;\n        public bool CombatActive, MobbedActive, BossPresent, HazardBurning, HazardAttacking, HazardRaging, WanderingHazardActive, DangerAtmosphereActive, ScenarioEnded;\n        public double AtmosphereReadyAt, PositionalStingerReadyAt, WanderingHazardReadyAt, LargeAreaRevealReadyAt;\n        public readonly Dictionary\u003Cstring, double\u003E SpecialReadyAt = new(StringComparer.Ordinal);\n        public readonly Dictionary\u003Cstring, float\u003E LastLayers = new(StringComparer.Ordinal);\n        public ParticipantMemory(string id, float previousDamage, IReadOnlyList\u003CSpecialMusicAlertConfiguration\u003E alerts) { Id = id; PreviousDamage = previousDamage; foreach (var alert in alerts) SpecialReadyAt[alert.ArchetypeId] = 0; }\n        public ParticipantMemory(MusicParticipantState state)\n        {\n            Id = state.ParticipantId; PreviousDamage = state.PreviousDamage;\n            (Damage, FastActivity, SlowActivity, DamageInflicted, CommonSight, MobPressure, Action, Threat, Calm, AmbientBasis) = state.Envelopes;\n            CombatActive = state.CombatActive; MobbedActive = state.MobbedActive; BossPresent = state.BossPresent; HazardBurning = state.HazardBurning; HazardAttacking = state.HazardAttacking; HazardRaging = state.HazardRaging; WanderingHazardActive = state.WanderingHazardActive; DangerAtmosphereActive = state.DangerAtmosphereActive; ScenarioEnded = state.ScenarioEnded; AtmosphereReadyAt = state.AtmosphereReadyAt; PositionalStingerReadyAt = state.PositionalStingerReadyAt; WanderingHazardReadyAt = state.WanderingHazardReadyAt; LargeAreaRevealReadyAt = state.LargeAreaRevealReadyAt;\n            foreach (var pair in state.SpecialAlertReadyAt) SpecialReadyAt[pair.Key] = pair.Value; foreach (var pair in state.LastMixerLayers) LastLayers[pair.Key] = pair.Value;\n        }\n        public MusicParticipantState Capture() =\u003E new(Id, PreviousDamage, new(Damage, FastActivity, SlowActivity, DamageInflicted, CommonSight, MobPressure, Action, Threat, Calm, AmbientBasis), CombatActive, MobbedActive, BossPresent, HazardBurning, HazardAttacking, HazardRaging, WanderingHazardActive, DangerAtmosphereActive, ScenarioEnded, AtmosphereReadyAt, PositionalStingerReadyAt, WanderingHazardReadyAt, LargeAreaRevealReadyAt, new Dictionary\u003Cstring, double\u003E(SpecialReadyAt), new Dictionary\u003Cstring, float\u003E(LastLayers));\n        public void ResetDynamic()\n        {\n            Damage = FastActivity = SlowActivity = DamageInflicted = CommonSight = MobPressure = Action = Threat = Calm = AmbientBasis = 0; CombatActive = MobbedActive = BossPresent = HazardBurning = HazardAttacking = HazardRaging = WanderingHazardActive = DangerAtmosphereActive = ScenarioEnded = false; AtmosphereReadyAt = PositionalStingerReadyAt = WanderingHazardReadyAt = LargeAreaRevealReadyAt = 0; LastLayers.Clear(); foreach (var key in SpecialReadyAt.Keys.ToArray()) SpecialReadyAt[key] = 0;\n        }\n    }\n}\n"},{"Ident":"notpointless.chomnr_adaptive_director","Path":"SboxDirector/Policies.cs","FileName":"Policies.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":337987,"Code":"namespace SboxDirector;\n\npublic interface IDirectorPolicy\n{\n    bool AllowEncounter(EncounterKind kind, WorldSnapshot world);\n    string SelectArchetype(EncounterKind kind, WorldSnapshot world, DeterministicRandom random);\n    bool CanCoexist(EncounterKind requested, PopulationSnapshot population);\n}\n\n/// \u003Csummary\u003EImplement this alongside IDirectorPolicy when a custom policy owns mutable deterministic state.\u003C/summary\u003E\npublic interface IPersistentDirectorPolicy\n{\n    string PersistenceId { get; }\n    IReadOnlyDictionary\u003Cstring, string\u003E CapturePolicyState();\n    void RestorePolicyState(IReadOnlyDictionary\u003Cstring, string\u003E state);\n}\n\n/// \u003Csummary\u003EOptional mode/difficulty override surface corresponding to recovered scripted Director policy.\u003C/summary\u003E\npublic interface IAdaptiveDirectorPolicy\n{\n    int AdjustEncounterCount(EncounterKind kind, int proposedCount, TempoPhase tempo, float teamPressure, WorldSnapshot world);\n    float AdjustMusicIntensity(float proposedIntensity, TempoPhase tempo, float teamPressure, WorldSnapshot world);\n    bool ShouldPlayBossMusic(EncounterKind kind, WorldSnapshot world);\n}\n\npublic interface IAdaptivePopulationPolicy\n{\n    int AdjustPopulationLimit(EncounterKind kind, int configuredLimit, WorldSnapshot world);\n}\n\npublic sealed class DefaultDirectorPolicy : IDirectorPolicy\n{\n    public bool AllowEncounter(EncounterKind kind, WorldSnapshot world) =\u003E true;\n    public bool CanCoexist(EncounterKind requested, PopulationSnapshot population) =\u003E requested != EncounterKind.Boss || population[EncounterKind.Boss] == 0;\n    public string SelectArchetype(EncounterKind kind, WorldSnapshot world, DeterministicRandom random) =\u003E kind.ToString().ToLowerInvariant();\n}\n\npublic interface IEncounterSchedule\n{\n    EncounterKind? SelectEncounter(WorldSnapshot world, TempoPhase tempo, float teamPressure, PopulationLedger population, DeterministicRandom random);\n    int RequestedCount(EncounterKind kind, WorldSnapshot world);\n}\n\npublic interface IPersistentEncounterSchedule\n{\n    IReadOnlyDictionary\u003Cstring, string\u003E CaptureScheduleState();\n    void RestoreScheduleState(IReadOnlyDictionary\u003Cstring, string\u003E state);\n}\n\npublic interface IEncounterScheduleFeedback\n{\n    void SelectionFinalized(bool requestCreated);\n}\n\npublic sealed class BasicEncounterSchedule : IEncounterSchedule, IPersistentEncounterSchedule, IEncounterScheduleFeedback\n{\n    readonly double _ambientInterval;\n    readonly double _activeInterval;\n    double _nextEncounterTime;\n    double _previousEncounterTime;\n    bool _selectionPending;\n\n    public BasicEncounterSchedule(double ambientInterval = 3, double activeInterval = 8)\n    {\n        if (ambientInterval \u003C 0 || activeInterval \u003C 0) throw new ArgumentOutOfRangeException(\u0022Encounter intervals must be non-negative.\u0022);\n        _ambientInterval = ambientInterval; _activeInterval = activeInterval;\n    }\n\n    public EncounterKind? SelectEncounter(WorldSnapshot world, TempoPhase tempo, float teamPressure, PopulationLedger population, DeterministicRandom random)\n    {\n        if (world.Time \u003C _nextEncounterTime) return null;\n        if (tempo == TempoPhase.Relax \u0026\u0026 population.Available(EncounterKind.Ambient, world.Population) \u003E 0)\n        {\n            _previousEncounterTime = _nextEncounterTime; _selectionPending = true; _nextEncounterTime = world.Time \u002B _ambientInterval;\n            return EncounterKind.Ambient;\n        }\n        if (tempo is TempoPhase.BuildUp or TempoPhase.SustainPeak)\n        {\n            if (population.Available(EncounterKind.Special, world.Population) \u003E 0 \u0026\u0026 random.NextFloat() \u003C 0.25f) { _previousEncounterTime = _nextEncounterTime; _selectionPending = true; _nextEncounterTime = world.Time \u002B _activeInterval; return EncounterKind.Special; }\n            if (population.Available(EncounterKind.CommonWave, world.Population) \u003E 0) { _previousEncounterTime = _nextEncounterTime; _selectionPending = true; _nextEncounterTime = world.Time \u002B _activeInterval; return EncounterKind.CommonWave; }\n        }\n        return null;\n    }\n    public int RequestedCount(EncounterKind kind, WorldSnapshot world) =\u003E kind switch { EncounterKind.Ambient =\u003E 1, EncounterKind.Special =\u003E 1, EncounterKind.CommonWave =\u003E 10, _ =\u003E 1 };\n    public IReadOnlyDictionary\u003Cstring, string\u003E CaptureScheduleState() =\u003E new Dictionary\u003Cstring, string\u003E { [\u0022nextEncounterTime\u0022] = _nextEncounterTime.ToString(\u0022R\u0022, CultureInfo.InvariantCulture) };\n    public void RestoreScheduleState(IReadOnlyDictionary\u003Cstring, string\u003E state)\n    {\n        if (state.TryGetValue(\u0022nextEncounterTime\u0022, out var value) \u0026\u0026 double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var parsed)) _nextEncounterTime = parsed;\n    }\n    public void SelectionFinalized(bool requestCreated) { if (_selectionPending \u0026\u0026 !requestCreated) _nextEncounterTime = _previousEncounterTime; _selectionPending = false; }\n}\n"},{"Ident":"notpointless.chomnr_adaptive_director","Path":"SboxDirector/ResourcePopulation.cs","FileName":"ResourcePopulation.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":337987,"Code":"#nullable enable annotations\nnamespace SboxDirector;\n\npublic sealed class ResourceCandidate\n{\n    public string Id { get; init; } = \u0022\u0022;\n    public string Category { get; init; } = \u0022\u0022;\n    public bool Reachable { get; init; } = true;\n    public bool Visible { get; init; }\n    public float AreaCapacity { get; init; } = 1;\n    public string ClusterId { get; init; } = \u0022\u0022;\n    public IReadOnlySet\u003Cstring\u003E Tags { get; init; } = new HashSet\u003Cstring\u003E();\n}\n\npublic sealed class ResourceWorldSnapshot\n{\n    public IReadOnlyDictionary\u003Cstring, int\u003E ExistingCounts { get; init; } = new Dictionary\u003Cstring, int\u003E();\n    public IReadOnlyList\u003CResourceCandidate\u003E Candidates { get; init; } = Array.Empty\u003CResourceCandidate\u003E();\n    public void Validate()\n    {\n        if (ExistingCounts.Any(x =\u003E string.IsNullOrWhiteSpace(x.Key) || x.Value \u003C 0)) throw new ArgumentException(\u0022Resource counts require non-empty categories and non-negative counts.\u0022);\n        if (Candidates.Any(x =\u003E string.IsNullOrWhiteSpace(x.Id) || string.IsNullOrWhiteSpace(x.Category) || !float.IsFinite(x.AreaCapacity) || x.AreaCapacity \u003C 0 || x.Tags is null || x.Tags.Any(string.IsNullOrWhiteSpace))) throw new ArgumentException(\u0022Resource candidates require ids, categories, non-negative finite capacity, and valid tags.\u0022);\n        if (Candidates.Select(x =\u003E x.Id).Distinct(StringComparer.Ordinal).Count() != Candidates.Count) throw new ArgumentException(\u0022Resource candidate ids must be unique.\u0022);\n    }\n}\n\npublic sealed class ResourceRule\n{\n    public string Category { get; init; } = \u0022resource\u0022;\n    public int TargetCount { get; init; } = 1;\n    public int MaximumRequestsPerTick { get; init; } = 1;\n    public bool AllowVisible { get; init; }\n    public int MaximumPerCluster { get; init; } = int.MaxValue;\n    public IReadOnlySet\u003Cstring\u003E RequiredTags { get; init; } = new HashSet\u003Cstring\u003E();\n    public void Validate() { if (string.IsNullOrWhiteSpace(Category) || TargetCount \u003C 0 || MaximumRequestsPerTick \u003C= 0 || MaximumPerCluster \u003C= 0 || RequiredTags is null || RequiredTags.Any(string.IsNullOrWhiteSpace)) throw new ArgumentException(\u0022Invalid resource rule.\u0022); }\n}\n\npublic interface IResourcePolicy\n{\n    bool AllowResourceSpawn(string category, ResourceCandidate candidate, WorldSnapshot world);\n    string ConvertResource(string category, ResourceCandidate candidate, WorldSnapshot world);\n    bool ShouldAvoidResource(string archetype, ResourceCandidate candidate, WorldSnapshot world);\n    bool CanPickupObject(string archetype, string participantId, WorldSnapshot world);\n}\n\npublic interface IPersistentResourcePolicy\n{\n    string PersistenceId { get; }\n    IReadOnlyDictionary\u003Cstring, string\u003E CaptureResourcePolicyState();\n    void RestoreResourcePolicyState(IReadOnlyDictionary\u003Cstring, string\u003E state);\n}\n\npublic sealed class DefaultResourcePolicy : IResourcePolicy\n{\n    public bool AllowResourceSpawn(string category, ResourceCandidate candidate, WorldSnapshot world) =\u003E true;\n    public string ConvertResource(string category, ResourceCandidate candidate, WorldSnapshot world) =\u003E category;\n    public bool ShouldAvoidResource(string archetype, ResourceCandidate candidate, WorldSnapshot world) =\u003E false;\n    public bool CanPickupObject(string archetype, string participantId, WorldSnapshot world) =\u003E true;\n}\n\npublic sealed record ResourceSpawnRequest(long RequestId, string Category, string Archetype, string CandidateId, string Reason);\npublic sealed record ResourceSpawnResult(long RequestId, RequestResultKind Result, string? Detail = null);\npublic readonly record struct PendingResourceState(long RequestId, string Category, string CandidateId, string ClusterId);\npublic readonly record struct ResourcePopulationState(string ConfigurationSignature, long NextRequestId, RandomState Random, IReadOnlyList\u003CPendingResourceState\u003E Pending, IReadOnlyList\u003CPendingResourceState\u003E Consumed, ExtensionState Policy);\n\n/// \u003Csummary\u003EGeneralized density/conversion/revisit controller backed by recovered item-population responsibilities.\u003C/summary\u003E\npublic sealed class ResourcePopulationController\n{\n    readonly IReadOnlyList\u003CResourceRule\u003E _rules;\n    readonly IResourcePolicy _policy;\n    DeterministicRandom _random;\n    readonly Dictionary\u003Clong, PendingResourceState\u003E _pending = new();\n    readonly Dictionary\u003Cstring, PendingResourceState\u003E _consumed = new(StringComparer.Ordinal);\n    readonly string _configurationSignature;\n    long _nextRequestId = 1;\n\n    public ResourcePopulationController(IEnumerable\u003CResourceRule\u003E rules, int seed = 1, IResourcePolicy? policy = null)\n    {\n        _rules = rules.OrderBy(x =\u003E x.Category, StringComparer.Ordinal).ToArray(); foreach (var rule in _rules) rule.Validate();\n        if (_rules.Select(x =\u003E x.Category).Distinct(StringComparer.Ordinal).Count() != _rules.Count) throw new ArgumentException(\u0022Resource rule categories must be unique.\u0022);\n        _policy = policy ?? new DefaultResourcePolicy();\n        _random = new DeterministicRandom(seed);\n        _configurationSignature = string.Join(\u0022;\u0022, _rules.Select(x =\u003E $\u0022{Encode(x.Category)}:{x.TargetCount}:{x.MaximumRequestsPerTick}:{x.AllowVisible}:{x.MaximumPerCluster}:{string.Join(\u0022,\u0022, x.RequiredTags.OrderBy(tag =\u003E tag, StringComparer.Ordinal).Select(Encode))}\u0022));\n    }\n\n    public ResourcePopulationController(IEnumerable\u003CResourceRule\u003E rules, ResourcePopulationState state, IResourcePolicy? policy = null) : this(rules, 1, policy)\n    {\n        RestoreState(state);\n    }\n\n    public IReadOnlyList\u003CResourceSpawnRequest\u003E Tick(ResourceWorldSnapshot resources, WorldSnapshot world)\n    {\n        var output = new List\u003CResourceSpawnRequest\u003E();\n        foreach (var rule in _rules)\n        {\n            var existing = resources.ExistingCounts.TryGetValue(rule.Category, out var count) ? count : 0;\n            var reserved = _pending.Values.Count(x =\u003E x.Category == rule.Category);\n            var needed = Math.Min(rule.MaximumRequestsPerTick, Math.Max(0, rule.TargetCount - existing - reserved));\n            for (var requestIndex = 0; requestIndex \u003C needed; requestIndex\u002B\u002B)\n            {\n                var candidates = resources.Candidates.Where(x =\u003E x.Category == rule.Category \u0026\u0026 x.Reachable \u0026\u0026 (rule.AllowVisible || !x.Visible) \u0026\u0026 !_consumed.ContainsKey(x.Id) \u0026\u0026 !_pending.Values.Any(p =\u003E p.CandidateId == x.Id) \u0026\u0026 ClusterAvailable(x, rule) \u0026\u0026 rule.RequiredTags.All(x.Tags.Contains) \u0026\u0026 _policy.AllowResourceSpawn(rule.Category, x, world)).ToArray();\n                if (candidates.Length == 0) break;\n                var bestCapacity = candidates.Max(x =\u003E x.AreaCapacity); var best = candidates.Where(x =\u003E Math.Abs(x.AreaCapacity - bestCapacity) \u003C .00001f).ToArray(); var candidate = best[_random.NextInt(best.Length)];\n                var archetype = _policy.ConvertResource(rule.Category, candidate, world); if (string.IsNullOrWhiteSpace(archetype) || _policy.ShouldAvoidResource(archetype, candidate, world)) { _consumed[candidate.Id] = new(0, rule.Category, candidate.Id, candidate.ClusterId); requestIndex--; continue; }\n                var id = _nextRequestId\u002B\u002B; _pending[id] = new(id, rule.Category, candidate.Id, candidate.ClusterId); output.Add(new(id, rule.Category, archetype, candidate.Id, $\u0022target={rule.TargetCount}; existing={existing}; reserved={reserved}; cluster={candidate.ClusterId}\u0022)); reserved\u002B\u002B;\n            }\n        }\n        return output;\n    }\n\n    public void ApplyResult(ResourceSpawnResult result)\n    {\n        if (result.Result == RequestResultKind.Deferred) return;\n        if (!_pending.Remove(result.RequestId, out var pending)) return;\n        if (result.Result is RequestResultKind.Succeeded or RequestResultKind.PartiallySucceeded) _consumed[pending.CandidateId] = pending;\n    }\n\n    public void ResetRevisitState() =\u003E _consumed.Clear();\n    public void CancelPending() =\u003E _pending.Clear();\n    public bool CanPickup(string archetype, string participantId, WorldSnapshot world) =\u003E _policy.CanPickupObject(archetype, participantId, world);\n    public ResourcePopulationState CaptureState() =\u003E new(_configurationSignature, _nextRequestId, _random.State, _pending.Values.OrderBy(x =\u003E x.RequestId).ToArray(), _consumed.Values.OrderBy(x =\u003E x.CandidateId, StringComparer.Ordinal).ToArray(), CapturePolicyState());\n    public void RestoreState(ResourcePopulationState state)\n    {\n        if (!string.Equals(state.ConfigurationSignature, _configurationSignature, StringComparison.Ordinal)) throw new InvalidOperationException(\u0022Resource population state does not match the configured rules.\u0022);\n        _nextRequestId = state.NextRequestId; _random = new DeterministicRandom(state.Random); _pending.Clear(); _consumed.Clear();\n        foreach (var item in state.Pending) _pending[item.RequestId] = item; foreach (var item in state.Consumed) _consumed[item.CandidateId] = item;\n        if (state.Policy.HasState)\n        {\n            if (_policy is not IPersistentResourcePolicy persistent || persistent.PersistenceId != state.Policy.PersistenceId) throw new InvalidOperationException($\u0022Resource policy state \u0027{state.Policy.PersistenceId}\u0027 requires the matching {nameof(IPersistentResourcePolicy)} implementation.\u0022);\n            persistent.RestoreResourcePolicyState(state.Policy.Values);\n        }\n    }\n    ExtensionState CapturePolicyState() =\u003E _policy is IPersistentResourcePolicy persistent ? new(string.IsNullOrWhiteSpace(persistent.PersistenceId) ? throw new InvalidOperationException(\u0022Persistent resource policy requires an id.\u0022) : persistent.PersistenceId, persistent.CaptureResourcePolicyState() ?? new Dictionary\u003Cstring, string\u003E()) : ExtensionState.Empty;\n    static string Encode(string value) =\u003E $\u0022{value.Length}:{value}\u0022;\n    bool ClusterAvailable(ResourceCandidate candidate, ResourceRule rule)\n    {\n        if (string.IsNullOrWhiteSpace(candidate.ClusterId) || rule.MaximumPerCluster == int.MaxValue) return true;\n        var pending = _pending.Values.Count(x =\u003E x.Category == rule.Category \u0026\u0026 x.ClusterId == candidate.ClusterId);\n        var consumed = _consumed.Values.Count(x =\u003E x.Category == rule.Category \u0026\u0026 x.ClusterId == candidate.ClusterId);\n        return pending \u002B consumed \u003C rule.MaximumPerCluster;\n    }\n}\n"},{"Ident":"notpointless.chomnr_adaptive_director","Path":"SboxDirector/Sandbox/AdaptiveDirectorComponent.cs","FileName":"AdaptiveDirectorComponent.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":337987,"Code":"#nullable enable annotations\nusing Sandbox;\n\nnamespace SboxDirector;\n\n/// \u003Csummary\u003E\n/// Optional s\u0026amp;box component bridge. Derive from this class and translate your\n/// game\u0027s entities/navigation into a WorldSnapshot and SpawnRequestResult.\n/// \u003C/summary\u003E\n[Category(\u0022Gameplay\u0022)]\n[Title(\u0022Adaptive Director\u0022)]\n[Icon(\u0022neurology\u0022)]\npublic abstract class AdaptiveDirectorComponent : Component\n{\n    [Property] public bool DirectorEnabled { get; set; } = true;\n    [Property] public int Seed { get; set; } = 1;\n    [Property] public bool RoundBased { get; set; }\n    [Property] public bool ResetDirectorOnRoundBegin { get; set; } = true;\n\n    public DirectorCore? Director { get; private set; }\n    public ISessionFlow? Session { get; private set; }\n    public DirectorDecisionBatch? LastDecisions { get; private set; }\n    public DirectorRunStatus LastRunStatus { get; private set; } = DirectorRunStatus.SessionInactive;\n\n    DirectorHostRunner? _runner;\n\n    protected override void OnStart() =\u003E InitializeDirector();\n\n    protected override void OnFixedUpdate()\n    {\n        if (!DirectorEnabled || _runner is null) return;\n        var result = _runner.Update(); LastRunStatus = result.Status; LastDecisions = result.Decisions;\n        if (result.Decisions is not null) OnDirectorDecisions(result.Decisions, result.Results);\n    }\n\n    public void InitializeDirector()\n    {\n        Director = CreateDirector();\n        Session = RoundBased ? new RoundSessionFlow() : new ContinuousSessionFlow();\n        BindRunner();\n        OnDirectorInitialized(Director);\n    }\n\n    public void BeginRound(string roundId)\n    {\n        if (Session is not RoundSessionFlow rounds) throw new InvalidOperationException(\u0022This component is not configured as round-based.\u0022);\n        _runner?.Stop();\n        if (ResetDirectorOnRoundBegin) { Director = CreateDirector(); BindRunner(); OnDirectorInitialized(Director); }\n        rounds.BeginRound(roundId);\n    }\n\n    public void EndRound()\n    {\n        if (Session is not RoundSessionFlow rounds) return;\n        _runner?.Stop(); rounds.EndRound();\n    }\n\n    public void ReportPressure(PressureEvent input, double time) =\u003E _runner?.ReportPressure(input, time);\n    public string SaveDirectorState() =\u003E Director is null ? throw new InvalidOperationException(\u0022Director has not initialized.\u0022) : DirectorStateCodec.ToJson(Director.CaptureState());\n    public void LoadDirectorState(string json)\n    {\n        _runner?.Stop(); Director = RestoreDirector(DirectorStateCodec.FromJson(json)); BindRunner(); OnDirectorInitialized(Director);\n    }\n\n    protected virtual DirectorConfiguration CreateConfiguration() =\u003E new() { Seed = Seed };\n    protected virtual IDirectorPolicy CreatePolicy() =\u003E new DefaultDirectorPolicy();\n    protected virtual IEncounterSchedule CreateSchedule() =\u003E new BasicEncounterSchedule();\n    protected virtual IPlacementPolicy CreatePlacementPolicy() =\u003E new DefaultPlacementPolicy();\n    protected virtual IEncounterComposer CreateComposer() =\u003E new DefaultEncounterComposer();\n    protected virtual ResourcePopulationController? CreateResourceController() =\u003E null;\n    protected virtual DirectorOrchestration? CreateOrchestration() =\u003E null;\n    protected virtual DirectorOrchestration? RestoreOrchestration(DirectorOrchestrationState? state) =\u003E state is null ? CreateOrchestration() : throw new InvalidOperationException(\u0022Override RestoreOrchestration when orchestration persistence is enabled.\u0022);\n    protected virtual DirectorCore CreateDirector() =\u003E new(CreateConfiguration(), CreatePolicy(), CreateSchedule(), CreatePlacementPolicy(), CreateComposer(), CreateResourceController(), CreateOrchestration());\n    protected virtual DirectorCore RestoreDirector(DirectorState state) =\u003E new(CreateConfiguration(), state, CreatePolicy(), CreateSchedule(), CreatePlacementPolicy(), CreateComposer(), CreateResourceController(), RestoreOrchestration(state.Orchestration));\n    protected virtual bool HasDirectorAuthority =\u003E !IsProxy;\n    protected virtual void OnDirectorInitialized(DirectorCore director) { }\n    protected virtual void OnDirectorDecisions(DirectorDecisionBatch decisions, IReadOnlyList\u003CSpawnRequestResult\u003E results) { }\n    protected abstract WorldSnapshot CaptureDirectorWorld();\n    protected abstract SpawnRequestResult ExecuteDirectorSpawn(SpawnRequest request);\n    protected virtual ResourceSpawnResult ExecuteDirectorResourceSpawn(ResourceSpawnRequest request) =\u003E new(request.RequestId, RequestResultKind.Rejected, \u0022Override ExecuteDirectorResourceSpawn to enable resource population.\u0022);\n    void BindRunner()\n    {\n        if (Director is null || Session is null) throw new InvalidOperationException(\u0022Director component is incomplete.\u0022);\n        _runner = new DirectorHostRunner(Director, Session, new DelegateWorldSource(CaptureDirectorWorld), new DelegateSpawnExecutor(ExecuteDirectorSpawn), new ComponentAuthority(this), new DelegateResourceExecutor(ExecuteDirectorResourceSpawn));\n    }\n\n    sealed class ComponentAuthority : IDirectorAuthority\n    {\n        readonly AdaptiveDirectorComponent _component;\n        public ComponentAuthority(AdaptiveDirectorComponent component) { _component = component; }\n        public bool IsAuthoritative =\u003E _component.HasDirectorAuthority;\n    }\n}\n"},{"Ident":"notpointless.chomnr_adaptive_director","Path":"SboxDirector/Scenarios.cs","FileName":"Scenarios.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":337987,"Code":"#nullable enable annotations\nnamespace SboxDirector;\n\npublic sealed record ScenarioStageDefinition(\n    string Id,\n    double MinimumDuration = 0,\n    bool RequiresCombatClear = false,\n    EncounterKind? Encounter = null,\n    int EncounterCount = 1,\n    string? MusicCue = null);\npublic readonly record struct ScenarioState(ScenarioStatus Status, int StageIndex, double StageStarted);\n\npublic sealed class ScenarioController\n{\n    readonly IReadOnlyList\u003CScenarioStageDefinition\u003E _stages;\n    public ScenarioStatus Status { get; private set; } = ScenarioStatus.Inactive;\n    public int StageIndex { get; private set; } = -1;\n    public double StageStarted { get; private set; }\n    public ScenarioStageDefinition? Current =\u003E StageIndex \u003E= 0 \u0026\u0026 StageIndex \u003C _stages.Count ? _stages[StageIndex] : null;\n    public ScenarioController(IReadOnlyList\u003CScenarioStageDefinition\u003E stages)\n    {\n        _stages = stages ?? throw new ArgumentNullException(nameof(stages));\n        if (_stages.Any(x =\u003E string.IsNullOrWhiteSpace(x.Id) || !double.IsFinite(x.MinimumDuration) || x.MinimumDuration \u003C 0 || x.EncounterCount \u003C= 0) || _stages.Select(x =\u003E x.Id).Distinct(StringComparer.Ordinal).Count() != _stages.Count) throw new ArgumentException(\u0022Scenario stages are invalid.\u0022, nameof(stages));\n    }\n    public ScenarioController(IReadOnlyList\u003CScenarioStageDefinition\u003E stages, ScenarioState state) : this(stages) { Status = state.Status; StageIndex = state.StageIndex; StageStarted = state.StageStarted; if (!double.IsFinite(StageStarted) || StageIndex \u003C -1 || StageIndex \u003E _stages.Count || (Status == ScenarioStatus.Running \u0026\u0026 Current is null) || (Status == ScenarioStatus.Complete \u0026\u0026 StageIndex != _stages.Count)) throw new ArgumentException(\u0022Scenario state does not match the definition.\u0022); }\n    public void Start(double time) { RequireTime(time); if (_stages.Count == 0) { Status = ScenarioStatus.Complete; StageIndex = 0; StageStarted = time; return; } Status = ScenarioStatus.Running; StageIndex = 0; StageStarted = time; }\n    public bool TryAdvance(double time, bool combatActive)\n    {\n        RequireTime(time); if (time \u003C StageStarted) throw new InvalidOperationException(\u0022Scenario time cannot move backwards.\u0022);\n        if (Status != ScenarioStatus.Running || Current is null) return false;\n        if (time - StageStarted \u003C Current.MinimumDuration || (Current.RequiresCombatClear \u0026\u0026 combatActive)) return false;\n        StageIndex\u002B\u002B; StageStarted = time; if (StageIndex \u003E= _stages.Count) Status = ScenarioStatus.Complete; return true;\n    }\n    public void Fail() { if (Status == ScenarioStatus.Running) Status = ScenarioStatus.Failed; }\n    public void Reset() { Status = ScenarioStatus.Inactive; StageIndex = -1; StageStarted = 0; }\n    public ScenarioState Snapshot =\u003E new(Status, StageIndex, StageStarted);\n    static void RequireTime(double time) { if (!double.IsFinite(time)) throw new ArgumentOutOfRangeException(nameof(time)); }\n}\n\npublic interface ISessionFlow\n{\n    string ModeId { get; }\n    string? CurrentRoundId { get; }\n    bool IsSessionActive { get; }\n}\n\npublic sealed class ContinuousSessionFlow : ISessionFlow\n{\n    public string ModeId { get; init; } = \u0022continuous\u0022;\n    public string? CurrentRoundId =\u003E null;\n    public bool IsSessionActive { get; set; } = true;\n}\n\npublic sealed class RoundSessionFlow : ISessionFlow\n{\n    public string ModeId { get; init; } = \u0022rounds\u0022;\n    public string? CurrentRoundId { get; private set; }\n    public bool IsSessionActive =\u003E CurrentRoundId is not null;\n    public void BeginRound(string id) { CurrentRoundId = string.IsNullOrWhiteSpace(id) ? throw new ArgumentException(\u0022Round id is required.\u0022) : id; }\n    public void EndRound() =\u003E CurrentRoundId = null;\n}\n"},{"Ident":"notpointless.chomnr_adaptive_director","Path":"Code/SboxDirector/Configuration.cs","FileName":"Configuration.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":337987,"Code":"namespace SboxDirector;\n\npublic sealed class PressureConfiguration\n{\n    public float Gain { get; init; } = 0.25f;\n    public float HoldSeconds { get; init; } = 5f;\n    public float FullDecaySeconds { get; init; } = 30f;\n    public float AveragedFollowingSeconds { get; init; } = 20f;\n    public float LockValue { get; init; } = -1f;\n    public IReadOnlyList\u003Cfloat\u003E SeverityWeights { get; init; } = new[] { 0.05f, 0.20f, 0.50f, 1.00f, 999999.875f };\n\n    public void Validate()\n    {\n        if (!float.IsFinite(Gain) || Gain \u003C 0f) throw new ArgumentOutOfRangeException(nameof(Gain));\n        if (!float.IsFinite(HoldSeconds) || HoldSeconds \u003C 0f) throw new ArgumentOutOfRangeException(nameof(HoldSeconds));\n        if (!float.IsFinite(FullDecaySeconds) || FullDecaySeconds \u003C= 0f) throw new ArgumentOutOfRangeException(nameof(FullDecaySeconds));\n        if (!float.IsFinite(AveragedFollowingSeconds) || AveragedFollowingSeconds \u003C= 0f) throw new ArgumentOutOfRangeException(nameof(AveragedFollowingSeconds));\n        if (SeverityWeights.Count != 5 || SeverityWeights.Any(x =\u003E !float.IsFinite(x) || x \u003C 0f))\n            throw new ArgumentException(\u0022Exactly five non-negative severity weights are required.\u0022);\n    }\n}\n\npublic sealed class TempoConfiguration\n{\n    public double BuildUpMinimumSeconds { get; init; } = 15;\n    public double SustainPeakSeconds { get; init; } = 10;\n    public double RelaxMinimumSeconds { get; init; } = 20;\n    public double RelaxMaximumSeconds { get; init; } = 45;\n    public float PeakThreshold { get; init; } = 0.9f;\n    public float RelaxThreshold { get; init; } = 0.9f;\n    public float RelaxMaximumProgressTravel { get; init; } = 0.15f;\n\n    public void Validate()\n    {\n        if (!double.IsFinite(BuildUpMinimumSeconds) || !double.IsFinite(SustainPeakSeconds) || !double.IsFinite(RelaxMinimumSeconds) || !double.IsFinite(RelaxMaximumSeconds) || BuildUpMinimumSeconds \u003C 0 || SustainPeakSeconds \u003C 0 || RelaxMinimumSeconds \u003C 0) throw new ArgumentOutOfRangeException(\u0022Tempo durations must be finite and non-negative.\u0022);\n        if (RelaxMaximumSeconds \u003C RelaxMinimumSeconds) throw new ArgumentException(\u0022RelaxMaximumSeconds must be \u003E= RelaxMinimumSeconds.\u0022);\n        if (!float.IsFinite(PeakThreshold) || !float.IsFinite(RelaxThreshold) || !float.IsFinite(RelaxMaximumProgressTravel) || PeakThreshold is \u003C 0f or \u003E 1f || RelaxThreshold is \u003C 0f or \u003E 1f || RelaxMaximumProgressTravel \u003C 0f) throw new ArgumentOutOfRangeException(\u0022Tempo thresholds and travel must be finite and non-negative; thresholds must be in [0,1].\u0022);\n    }\n}\n\npublic sealed class PlacementProfile\n{\n    public PlacementSelectionMode SelectionMode { get; init; } = PlacementSelectionMode.WeightedScore;\n    public float MinimumDistance { get; init; } = 500f;\n    public float MaximumDistance { get; init; } = 2500f;\n    public float IdealDistance { get; init; } = 1200f;\n    public float MinimumThreatSeparation { get; init; } = 500f;\n    public bool AllowVisible { get; init; }\n    public float DistanceWeight { get; init; } = 1f;\n    public float CapacityWeight { get; init; } = 0.25f;\n    public float SeparationWeight { get; init; } = 0.25f;\n    public float BehindProgressWeight { get; init; }\n    public float MinimumPathDistance { get; init; }\n    public float MinimumIngressDistance { get; init; }\n    public float MinimumClearRadius { get; init; }\n    public float MinimumAreaWidth { get; init; }\n    public float MinimumAreaHeight { get; init; }\n    public float PathDistanceWeight { get; init; }\n    public float IngressWeight { get; init; }\n    public float ClearRadiusWeight { get; init; }\n    public IReadOnlySet\u003CAdvanceRelation\u003E AllowedRelations { get; init; } = new HashSet\u003CAdvanceRelation\u003E();\n    public PlacementFallbackMode FallbackMode { get; init; } = PlacementFallbackMode.None;\n    public IReadOnlySet\u003Cstring\u003E RequiredTags { get; init; } = new HashSet\u003Cstring\u003E();\n    public IReadOnlySet\u003Cstring\u003E ExcludedTags { get; init; } = new HashSet\u003Cstring\u003E();\n    public void Validate()\n    {\n        var measurements = new[] { MinimumDistance, MaximumDistance, IdealDistance, MinimumThreatSeparation, DistanceWeight, CapacityWeight, SeparationWeight, BehindProgressWeight, MinimumPathDistance, MinimumIngressDistance, MinimumClearRadius, MinimumAreaWidth, MinimumAreaHeight, PathDistanceWeight, IngressWeight, ClearRadiusWeight };\n        if (measurements.Any(x =\u003E !float.IsFinite(x)) || MinimumDistance \u003C 0 || MaximumDistance \u003C MinimumDistance || IdealDistance \u003C MinimumDistance || IdealDistance \u003E MaximumDistance || MinimumThreatSeparation \u003C 0 || MinimumPathDistance \u003C 0 || MinimumIngressDistance \u003C 0 || MinimumClearRadius \u003C 0 || MinimumAreaWidth \u003C 0 || MinimumAreaHeight \u003C 0) throw new ArgumentException(\u0022Invalid placement distance or area profile.\u0022);\n        if (RequiredTags.Overlaps(ExcludedTags)) throw new ArgumentException(\u0022Placement tags cannot be both required and excluded.\u0022);\n    }\n}\n\npublic sealed class PopulationConfiguration\n{\n    public IReadOnlyDictionary\u003CEncounterKind, int\u003E Limits { get; init; } = new Dictionary\u003CEncounterKind, int\u003E\n    {\n        [EncounterKind.Ambient] = 30,\n        [EncounterKind.CommonWave] = 30,\n        [EncounterKind.Special] = 4,\n        [EncounterKind.Boss] = 1,\n        [EncounterKind.DormantHazard] = 1,\n        [EncounterKind.Objective] = 8,\n        [EncounterKind.Custom] = 16\n    };\n}\n\npublic sealed class DirectorConfiguration\n{\n    public string Version { get; init; } = \u00221.2\u0022;\n    public PressureConfiguration Pressure { get; init; } = new();\n    public TempoConfiguration Tempo { get; init; } = new();\n    public PopulationConfiguration Population { get; init; } = new();\n    public IReadOnlyDictionary\u003CEncounterKind, PlacementProfile\u003E Placement { get; init; } = DefaultPlacement();\n    public int Seed { get; init; } = 1;\n    public double RequestTimeoutSeconds { get; init; } = 15;\n    public int MaximumRequestsPerTick { get; init; } = 1;\n    public bool ValidateWorldSnapshots { get; init; } = true;\n    public RetryConfiguration Retry { get; init; } = new();\n\n    public void Validate()\n    {\n        Pressure.Validate(); Tempo.Validate();\n        if (string.IsNullOrWhiteSpace(Version)) throw new ArgumentException(\u0022A configuration version is required.\u0022, nameof(Version));\n        if (RequestTimeoutSeconds \u003C= 0) throw new ArgumentOutOfRangeException(nameof(RequestTimeoutSeconds));\n        if (MaximumRequestsPerTick \u003C= 0) throw new ArgumentOutOfRangeException(nameof(MaximumRequestsPerTick));\n        Retry.Validate();\n        foreach (var pair in Population.Limits) if (pair.Value \u003C 0) throw new ArgumentOutOfRangeException($\u0022Negative population limit for {pair.Key}.\u0022);\n        foreach (var profile in Placement.Values) profile.Validate();\n    }\n\n    static IReadOnlyDictionary\u003CEncounterKind, PlacementProfile\u003E DefaultPlacement() =\u003E new Dictionary\u003CEncounterKind, PlacementProfile\u003E\n    {\n        [EncounterKind.Ambient] = new() { MinimumDistance = 300f, IdealDistance = 800f, MaximumDistance = 1800f },\n        [EncounterKind.CommonWave] = new(),\n        [EncounterKind.Special] = new() { MinimumDistance = 600f, IdealDistance = 1400f },\n        [EncounterKind.Boss] = new() { MinimumDistance = 900f, IdealDistance = 1800f, MinimumThreatSeparation = 1200f },\n        [EncounterKind.DormantHazard] = new() { MinimumDistance = 700f, IdealDistance = 1500f, MinimumThreatSeparation = 1000f },\n        [EncounterKind.Objective] = new() { AllowVisible = true, MinimumDistance = 0f, MaximumDistance = 5000f },\n        [EncounterKind.Custom] = new()\n    };\n}\n\npublic sealed class RetryConfiguration\n{\n    public int MaximumAttempts { get; init; } = 3;\n    public double DelaySeconds { get; init; } = 1;\n    public bool RetryRejected { get; init; } = true;\n    public bool RetryFailed { get; init; } = true;\n    public bool RetryPartialRemainder { get; init; } = true;\n    public void Validate()\n    {\n        if (MaximumAttempts \u003C= 0 || !double.IsFinite(DelaySeconds) || DelaySeconds \u003C 0) throw new ArgumentException(\u0022Invalid request retry configuration.\u0022);\n    }\n}\n"},{"Ident":"notpointless.chomnr_adaptive_director","Path":"Code/SboxDirector/PlacementSelector.cs","FileName":"PlacementSelector.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":337987,"Code":"#nullable enable annotations\nnamespace SboxDirector;\n\npublic sealed record PlacementChoice(SpawnCandidate Candidate, float Score, bool IsFallback = false);\n\npublic interface IPlacementPolicy\n{\n    bool IsAllowed(SpawnCandidate candidate, PlacementProfile profile, WorldSnapshot world);\n    float ScoreAdjustment(SpawnCandidate candidate, PlacementProfile profile, WorldSnapshot world);\n}\n\n/// \u003Csummary\u003EImplement this alongside IPlacementPolicy when a custom placement policy owns mutable deterministic state.\u003C/summary\u003E\npublic interface IPersistentPlacementPolicy\n{\n    string PersistenceId { get; }\n    IReadOnlyDictionary\u003Cstring, string\u003E CapturePlacementState();\n    void RestorePlacementState(IReadOnlyDictionary\u003Cstring, string\u003E state);\n}\n\npublic sealed class DefaultPlacementPolicy : IPlacementPolicy\n{\n    public bool IsAllowed(SpawnCandidate candidate, PlacementProfile profile, WorldSnapshot world) =\u003E true;\n    public float ScoreAdjustment(SpawnCandidate candidate, PlacementProfile profile, WorldSnapshot world) =\u003E 0f;\n}\n\npublic sealed class PlacementSelector\n{\n    public PlacementChoice? Select(IReadOnlyList\u003CSpawnCandidate\u003E candidates, PlacementProfile profile, DeterministicRandom random, WorldSnapshot? world = null, IPlacementPolicy? policy = null)\n    {\n        var ordered = OrderCandidates(candidates, profile.SelectionMode, random);\n        var strict = BuildChoices(ordered, profile, world, policy, profile.SelectionMode, ignoreRoute: false, broadFallback: false);\n        if (strict.Count \u003E 0) return Choose(strict, profile.SelectionMode, random, false);\n        if (profile.FallbackMode == PlacementFallbackMode.None) return null;\n        ordered = OrderCandidates(candidates, profile.SelectionMode, random);\n        var fallback = BuildChoices(ordered, profile, world, policy, profile.SelectionMode, ignoreRoute: true, broadFallback: profile.FallbackMode == PlacementFallbackMode.AnyReachableHidden);\n        return fallback.Count == 0 ? null : Choose(fallback, profile.SelectionMode, random, true);\n    }\n\n    static List\u003CPlacementChoice\u003E BuildChoices(IReadOnlyList\u003CSpawnCandidate\u003E candidates, PlacementProfile profile, WorldSnapshot? world, IPlacementPolicy? policy, PlacementSelectionMode mode, bool ignoreRoute, bool broadFallback)\n    {\n        var choices = new List\u003CPlacementChoice\u003E();\n        foreach (var candidate in candidates)\n        {\n            if (!candidate.Reachable || !candidate.Spawnable || (!profile.AllowVisible \u0026\u0026 candidate.Visible)) continue;\n            if (candidate.AreaWidth \u003C profile.MinimumAreaWidth || candidate.AreaHeight \u003C profile.MinimumAreaHeight) continue;\n            if (!broadFallback \u0026\u0026 (candidate.NearestParticipantDistance \u003C profile.MinimumDistance || candidate.NearestParticipantDistance \u003E profile.MaximumDistance)) continue;\n            if (!broadFallback \u0026\u0026 candidate.ThreatSeparation \u003C profile.MinimumThreatSeparation) continue;\n            if (profile.RequiredTags.Any(tag =\u003E !candidate.Tags.Contains(tag)) || profile.ExcludedTags.Any(candidate.Tags.Contains)) continue;\n            if (!ignoreRoute \u0026\u0026 (candidate.PathDistance \u003C profile.MinimumPathDistance || candidate.IngressDistance \u003C profile.MinimumIngressDistance || candidate.ClearRadius \u003C profile.MinimumClearRadius)) continue;\n            if (!ignoreRoute \u0026\u0026 profile.AllowedRelations.Count \u003E 0 \u0026\u0026 !profile.AllowedRelations.Contains(candidate.AdvanceRelation)) continue;\n            if (world is not null \u0026\u0026 policy is not null \u0026\u0026 !policy.IsAllowed(candidate, profile, world)) continue;\n            var score = mode == PlacementSelectionMode.HighestProgressFirstTie ? candidate.Progress : 0f;\n            if (mode == PlacementSelectionMode.WeightedScore)\n            {\n                var distanceRange = Math.Max(1f, profile.MaximumDistance - profile.MinimumDistance);\n                var distanceScore = 1f - Math.Min(1f, Math.Abs(candidate.NearestParticipantDistance - profile.IdealDistance) / distanceRange);\n                score = distanceScore * profile.DistanceWeight \u002B candidate.AreaCapacity * profile.CapacityWeight \u002B Math.Min(1f, candidate.ThreatSeparation / Math.Max(1f, profile.MinimumThreatSeparation * 2f)) * profile.SeparationWeight;\n                if (candidate.RelativeProgress \u003C 0) score \u002B= Math.Min(1f, -candidate.RelativeProgress) * profile.BehindProgressWeight;\n                score \u002B= Math.Min(1f, candidate.PathDistance / Math.Max(1f, profile.MinimumPathDistance * 2f)) * profile.PathDistanceWeight;\n                score \u002B= Math.Min(1f, candidate.IngressDistance / Math.Max(1f, profile.MinimumIngressDistance * 2f)) * profile.IngressWeight;\n                score \u002B= Math.Min(1f, candidate.ClearRadius / Math.Max(1f, profile.MinimumClearRadius * 2f)) * profile.ClearRadiusWeight;\n                if (world is not null \u0026\u0026 policy is not null)\n                {\n                    var adjustment = policy.ScoreAdjustment(candidate, profile, world); if (!float.IsFinite(adjustment)) throw new InvalidOperationException(\u0022Placement policy returned a non-finite score adjustment.\u0022); score \u002B= adjustment;\n                }\n            }\n            if (!float.IsFinite(score)) throw new InvalidOperationException(\u0022Placement scoring produced a non-finite value.\u0022);\n            choices.Add(new PlacementChoice(candidate, score));\n        }\n        return choices;\n    }\n\n    static IReadOnlyList\u003CSpawnCandidate\u003E OrderCandidates(IReadOnlyList\u003CSpawnCandidate\u003E candidates, PlacementSelectionMode mode, DeterministicRandom random)\n    {\n        if (mode != PlacementSelectionMode.NativeRandomTranspositionFirst) return candidates;\n        var shuffled = candidates.ToArray();\n        for (var index = 0; index \u003C shuffled.Length; index\u002B\u002B)\n        {\n            var other = random.NextInt(shuffled.Length);\n            (shuffled[index], shuffled[other]) = (shuffled[other], shuffled[index]);\n        }\n        return shuffled;\n    }\n\n    static PlacementChoice Choose(List\u003CPlacementChoice\u003E choices, PlacementSelectionMode mode, DeterministicRandom random, bool fallback)\n    {\n        if (mode is PlacementSelectionMode.FirstEligible or PlacementSelectionMode.NativeRandomTranspositionFirst) return choices[0] with { IsFallback = fallback };\n        if (mode == PlacementSelectionMode.HighestProgressFirstTie)\n        {\n            var progressChoice = choices[0];\n            for (var index = 1; index \u003C choices.Count; index\u002B\u002B) if (choices[index].Candidate.Progress \u003E progressChoice.Candidate.Progress) progressChoice = choices[index];\n            return progressChoice with { IsFallback = fallback };\n        }\n        var best = choices.Max(x =\u003E x.Score); var tied = choices.Where(x =\u003E Math.Abs(x.Score - best) \u003C 0.00001f).ToArray();\n        var selected = tied[random.NextInt(tied.Length)]; return selected with { IsFallback = fallback };\n    }\n}\n"},{"Ident":"notpointless.chomnr_adaptive_director","Path":"Code/SboxDirector/WaveSequenceController.cs","FileName":"WaveSequenceController.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":337987,"Code":"namespace SboxDirector;\n\npublic sealed class WaveSequenceConfiguration\n{\n    public int WaveCount { get; init; } = 1;\n    public double InitialDelayMinimum { get; init; } = 1;\n    public double InitialDelayMaximum { get; init; } = 2;\n    public double CombatTimeout { get; init; } = 10;\n    public double PauseMinimum { get; init; } = 5;\n    public double PauseMaximum { get; init; } = 10;\n    public void Validate()\n    {\n        var durations = new[] { InitialDelayMinimum, InitialDelayMaximum, CombatTimeout, PauseMinimum, PauseMaximum };\n        if (WaveCount \u003C= 0 || durations.Any(x =\u003E !double.IsFinite(x)) || InitialDelayMinimum \u003C 0 || InitialDelayMaximum \u003C InitialDelayMinimum || CombatTimeout \u003C 0 || PauseMinimum \u003C 0 || PauseMaximum \u003C PauseMinimum) throw new ArgumentException(\u0022Invalid wave sequence configuration.\u0022);\n    }\n}\n\npublic readonly record struct WaveSequenceState(WaveState State, int IssuedWaves, int TargetWaves, double Deadline);\npublic readonly record struct WaveAction(bool IssueWave, bool Completed, string Reason);\n\npublic sealed class WaveSequenceController\n{\n    readonly WaveSequenceConfiguration _configuration; readonly DeterministicRandom _random;\n    public WaveState State { get; private set; } = WaveState.Inactive;\n    public int IssuedWaves { get; private set; }\n    public int TargetWaves { get; private set; }\n    public double Deadline { get; private set; }\n    public WaveSequenceController(WaveSequenceConfiguration configuration, DeterministicRandom random) { configuration.Validate(); _configuration = configuration; _random = random; }\n    public WaveSequenceController(WaveSequenceConfiguration configuration, DeterministicRandom random, WaveSequenceState state) : this(configuration, random) { State = state.State; IssuedWaves = state.IssuedWaves; TargetWaves = state.TargetWaves; Deadline = state.Deadline; if (!double.IsFinite(Deadline) || IssuedWaves \u003C 0 || TargetWaves \u003C 0 || IssuedWaves \u003E TargetWaves) throw new ArgumentException(\u0022Wave state is invalid.\u0022, nameof(state)); }\n    public WaveSequenceState Snapshot =\u003E new(State, IssuedWaves, TargetWaves, Deadline);\n    public void Start(double time, int? waveCount = null) { RequireTime(time); TargetWaves = waveCount ?? _configuration.WaveCount; if (TargetWaves \u003C= 0) throw new ArgumentOutOfRangeException(nameof(waveCount)); IssuedWaves = 0; State = WaveState.InitialDelay; Deadline = time \u002B Range(_configuration.InitialDelayMinimum, _configuration.InitialDelayMaximum); }\n    public void Cancel() { State = WaveState.Inactive; IssuedWaves = 0; TargetWaves = 0; Deadline = 0; }\n    public WaveAction Update(double time, bool relevantCombatantsRemain)\n    {\n        RequireTime(time);\n        if (State is WaveState.Inactive or WaveState.Done) return new(false, State == WaveState.Done, \u0022inactive or complete\u0022);\n        if (time \u003C Deadline) return new(false, false, \u0022waiting for timer\u0022);\n        if (State == WaveState.InitialDelay) { State = WaveState.IssueWave; return new(false, false, \u0022initial delay expired\u0022); }\n        if (State == WaveState.IssueWave) { IssuedWaves\u002B\u002B; State = WaveState.WaitForClear; Deadline = time \u002B _configuration.CombatTimeout; return new(true, false, \u0022wave issued\u0022); }\n        if (State == WaveState.WaitForClear)\n        {\n            if (relevantCombatantsRemain) { Deadline = time \u002B _configuration.CombatTimeout; return new(false, false, \u0022combat remains\u0022); }\n            if (IssuedWaves \u003E= TargetWaves) { State = WaveState.Done; return new(false, true, \u0022target waves issued and combat clear\u0022); }\n            State = WaveState.Pause; Deadline = time \u002B Range(_configuration.PauseMinimum, _configuration.PauseMaximum); return new(false, false, \u0022pausing before next wave\u0022);\n        }\n        if (State == WaveState.Pause) { State = WaveState.IssueWave; return new(false, false, \u0022pause expired\u0022); }\n        return new(false, false, \u0022no action\u0022);\n    }\n    double Range(double minimum, double maximum) =\u003E minimum \u002B (maximum - minimum) * _random.NextFloat();\n    static void RequireTime(double time) { if (!double.IsFinite(time)) throw new ArgumentOutOfRangeException(nameof(time)); }\n}\n"},{"Ident":"notpointless.chomnr_adaptive_director","Path":"examples/SaveRestoreEverythingExample.cs","FileName":"SaveRestoreEverythingExample.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":337987,"Code":"using SboxDirector;\n\nnamespace AdaptiveDirectorExamples;\n\npublic sealed record ExampleSaveDocument(string DirectorJson, string MusicAuthorityJson, string MusicRuntimeJson);\n\n/// \u003Csummary\u003EPersists encounter direction, authoritative music, and client/local track queues together.\u003C/summary\u003E\npublic sealed class SaveRestoreEverythingExample\n{\n    readonly DirectorConfiguration _directorConfiguration = new() { Version = \u0022save-example-v1\u0022, Seed = 42 };\n    readonly MusicDirectorConfiguration _musicConfiguration = RecoveredMusicDirectorDefaults.CreateConfiguration(ExampleMusicSetup.CreateCues(), ExampleMusicSetup.CreateSpecialAlerts(), seed: 42);\n    DirectorCore _director;\n    MusicDirectorCore _music;\n    MusicRuntime _runtime;\n\n    public SaveRestoreEverythingExample(string participantId)\n    {\n        ParticipantId = participantId; _director = new DirectorCore(_directorConfiguration); _music = new MusicDirectorCore(_musicConfiguration); _runtime = new MusicRuntime(participantId, ExampleMusicSetup.CreateCatalog());\n    }\n\n    public string ParticipantId { get; }\n    public DirectorDecisionBatch TickDirector(WorldSnapshot world) =\u003E _director.Tick(world);\n    public void ApplySpawnResult(SpawnRequestResult result) =\u003E _director.ApplyResult(result);\n    public void ReportPressure(PressureEvent pressure, double time) =\u003E _director.ApplyPressure(pressure, time);\n    public MusicRuntimeBatch TickMusic(MusicWorldSnapshot world)\n    {\n        var decisions = _music.Tick(world); return _runtime.Apply(decisions);\n    }\n\n    public ExampleSaveDocument Save() =\u003E new(\n        DirectorStateCodec.ToJson(_director.CaptureState()),\n        MusicStateCodec.DirectorToJson(_music.CaptureState()),\n        MusicStateCodec.RuntimeToJson(_runtime.CaptureState()));\n\n    public void Load(ExampleSaveDocument save)\n    {\n        var directorState = DirectorStateCodec.FromJson(save.DirectorJson);\n        var musicState = MusicStateCodec.DirectorFromJson(save.MusicAuthorityJson);\n        var runtimeState = MusicStateCodec.RuntimeFromJson(save.MusicRuntimeJson);\n        _director = new DirectorCore(_directorConfiguration, directorState);\n        _music = new MusicDirectorCore(_musicConfiguration, musicState);\n        _runtime = new MusicRuntime(ParticipantId, ExampleMusicSetup.CreateCatalog(), runtimeState);\n    }\n}\n"},{"Ident":"notpointless.chomnr_adaptive_director","Path":"SboxDirector/ConcurrentSchedules.cs","FileName":"ConcurrentSchedules.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":337987,"Code":"#nullable enable annotations\nnamespace SboxDirector;\n\npublic sealed class ScheduleLane\n{\n    public string Id { get; init; } = \u0022lane\u0022;\n    public int Priority { get; init; }\n    public IEncounterSchedule Schedule { get; init; } = null!;\n    public Func\u003CWorldSnapshot, bool\u003E? Enabled { get; init; }\n}\n\n/// \u003Csummary\u003E\n/// Runs independent encounter lanes during one authoritative tick. This mirrors the proven separation\n/// of survival lull, horde, special, and boss schedules without copying their unrecovered formulas.\n/// \u003C/summary\u003E\npublic sealed class ConcurrentEncounterSchedule : IEncounterSchedule, IPersistentEncounterSchedule, IEncounterScheduleFeedback\n{\n    readonly IReadOnlyList\u003CScheduleLane\u003E _lanes;\n    readonly HashSet\u003Cstring\u003E _served = new(StringComparer.Ordinal);\n    readonly string _signature;\n    ScheduleLane? _selected;\n    double _servedTime = double.NaN;\n\n    public ConcurrentEncounterSchedule(IEnumerable\u003CScheduleLane\u003E lanes)\n    {\n        _lanes = lanes.OrderByDescending(x =\u003E x.Priority).ThenBy(x =\u003E x.Id, StringComparer.Ordinal).ToArray();\n        if (_lanes.Count == 0 || _lanes.Any(x =\u003E x.Schedule is null || string.IsNullOrWhiteSpace(x.Id) || x.Id.Contains(\u0027:\u0027))) throw new ArgumentException(\u0022Concurrent schedule lanes require unique non-empty ids without colons and a schedule.\u0022);\n        if (_lanes.Select(x =\u003E x.Id).Distinct(StringComparer.Ordinal).Count() != _lanes.Count) throw new ArgumentException(\u0022Concurrent schedule lane ids must be unique.\u0022);\n        _signature = string.Join(\u0022|\u0022, _lanes.Select(x =\u003E $\u0022{x.Id.Length}:{x.Id}:{x.Priority}\u0022));\n    }\n\n    public EncounterKind? SelectEncounter(WorldSnapshot world, TempoPhase tempo, float teamPressure, PopulationLedger population, DeterministicRandom random)\n    {\n        _selected = null;\n        if (_servedTime != world.Time) { _servedTime = world.Time; _served.Clear(); }\n        foreach (var lane in _lanes)\n        {\n            if (_served.Contains(lane.Id) || (lane.Enabled is not null \u0026\u0026 !lane.Enabled(world))) continue;\n            var kind = lane.Schedule.SelectEncounter(world, tempo, teamPressure, population, random);\n            if (kind is null) continue;\n            _selected = lane; _served.Add(lane.Id); return kind;\n        }\n        return null;\n    }\n\n    public int RequestedCount(EncounterKind kind, WorldSnapshot world) =\u003E _selected?.Schedule.RequestedCount(kind, world) ?? 1;\n\n    public void SelectionFinalized(bool requestCreated)\n    {\n        if (_selected is null) return;\n        if (_selected.Schedule is IEncounterScheduleFeedback feedback) feedback.SelectionFinalized(requestCreated);\n        if (!requestCreated) _served.Remove(_selected.Id);\n        _selected = null;\n    }\n\n    public IReadOnlyDictionary\u003Cstring, string\u003E CaptureScheduleState()\n    {\n        var state = new Dictionary\u003Cstring, string\u003E { [\u0022coordinator.signature\u0022] = _signature, [\u0022coordinator.time\u0022] = _servedTime.ToString(\u0022R\u0022, CultureInfo.InvariantCulture), [\u0022coordinator.served\u0022] = string.Join(\u0022|\u0022, _served.OrderBy(x =\u003E x, StringComparer.Ordinal)) };\n        foreach (var lane in _lanes)\n            if (lane.Schedule is IPersistentEncounterSchedule persistent)\n                foreach (var pair in persistent.CaptureScheduleState()) state[$\u0022lane:{lane.Id}:{pair.Key}\u0022] = pair.Value;\n        return state;\n    }\n\n    public void RestoreScheduleState(IReadOnlyDictionary\u003Cstring, string\u003E state)\n    {\n        _served.Clear();\n        if (!state.TryGetValue(\u0022coordinator.signature\u0022, out var signature) || !string.Equals(signature, _signature, StringComparison.Ordinal)) throw new InvalidOperationException(\u0022Concurrent schedule state does not match its configured lanes.\u0022);\n        if (state.TryGetValue(\u0022coordinator.time\u0022, out var time) \u0026\u0026 double.TryParse(time, NumberStyles.Float, CultureInfo.InvariantCulture, out var parsed)) _servedTime = parsed;\n        if (state.TryGetValue(\u0022coordinator.served\u0022, out var served)) foreach (var id in served.Split(\u0027|\u0027, StringSplitOptions.RemoveEmptyEntries)) _served.Add(id);\n        foreach (var lane in _lanes)\n        {\n            if (lane.Schedule is not IPersistentEncounterSchedule persistent) continue;\n            var prefix = $\u0022lane:{lane.Id}:\u0022;\n            persistent.RestoreScheduleState(state.Where(x =\u003E x.Key.StartsWith(prefix, StringComparison.Ordinal)).ToDictionary(x =\u003E x.Key[prefix.Length..], x =\u003E x.Value));\n        }\n    }\n}\n"},{"Ident":"notpointless.chomnr_adaptive_director","Path":"SboxDirector/PopulationLedger.cs","FileName":"PopulationLedger.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":337987,"Code":"namespace SboxDirector;\n\npublic sealed class PopulationLedger\n{\n    readonly PopulationConfiguration _configuration;\n    readonly Dictionary\u003CEncounterKind, int\u003E _reserved = new();\n    readonly Dictionary\u003Clong, PendingReservation\u003E _pending = new();\n    public PopulationLedger(PopulationConfiguration configuration) { _configuration = configuration; }\n\n    public int Available(EncounterKind kind, PopulationSnapshot population)\n        =\u003E Available(kind, population, ConfiguredLimit(kind));\n    public int ConfiguredLimit(EncounterKind kind) =\u003E _configuration.Limits.TryGetValue(kind, out var configured) ? configured : 0;\n    public int Available(EncounterKind kind, PopulationSnapshot population, int limit)\n    {\n        return Math.Max(0, limit - population[kind] - (_reserved.TryGetValue(kind, out var value) ? value : 0));\n    }\n    public bool TryReserve(long requestId, EncounterKind kind, int count, PopulationSnapshot population, double expiresAt = double.PositiveInfinity)\n        =\u003E TryReserveWithLimit(requestId, kind, count, population, ConfiguredLimit(kind), expiresAt);\n    public bool TryReserveWithLimit(long requestId, EncounterKind kind, int count, PopulationSnapshot population, int limit, double expiresAt = double.PositiveInfinity)\n    {\n        if (count \u003C= 0 || limit \u003C 0 || _pending.ContainsKey(requestId) || Available(kind, population, limit) \u003C count) return false;\n        _pending[requestId] = new(requestId, kind, count, expiresAt); _reserved[kind] = (_reserved.TryGetValue(kind, out var value) ? value : 0) \u002B count; return true;\n    }\n    public void Resolve(SpawnRequestResult result)\n    {\n        if (result.Result == RequestResultKind.Deferred) return;\n        if (!_pending.Remove(result.RequestId, out var pending)) return;\n        _reserved[pending.Kind] = Math.Max(0, _reserved[pending.Kind] - pending.Count);\n    }\n    public IReadOnlyList\u003Clong\u003E CancelAll()\n    {\n        var ids = _pending.Keys.OrderBy(x =\u003E x).ToArray(); _pending.Clear(); _reserved.Clear(); return ids;\n    }\n    public IReadOnlyList\u003Clong\u003E Expire(double time)\n    {\n        var expired = _pending.Values.Where(x =\u003E x.ExpiresAt \u003C= time).Select(x =\u003E x.RequestId).ToArray();\n        foreach (var id in expired) Resolve(new SpawnRequestResult(id, RequestResultKind.Failed, 0, \u0022request timeout\u0022));\n        return expired;\n    }\n    public PopulationLedgerState Capture() =\u003E new(_pending.Values.OrderBy(x =\u003E x.RequestId).ToArray());\n    public void Restore(PopulationLedgerState state)\n    {\n        _pending.Clear(); _reserved.Clear();\n        foreach (var item in state.Pending) { _pending[item.RequestId] = item; _reserved[item.Kind] = (_reserved.TryGetValue(item.Kind, out var count) ? count : 0) \u002B item.Count; }\n    }\n    public IReadOnlyDictionary\u003CEncounterKind, int\u003E Reservations =\u003E _reserved;\n    public IReadOnlyList\u003CPendingReservation\u003E Pending =\u003E _pending.Values.OrderBy(x =\u003E x.RequestId).ToArray();\n}\n\npublic readonly record struct PendingReservation(long RequestId, EncounterKind Kind, int Count, double ExpiresAt);\npublic readonly record struct PopulationLedgerState(IReadOnlyList\u003CPendingReservation\u003E Pending);\n"}]}