feat(items): port retail shared cooldown overlays

Preserve public shared-cooldown metadata, resolve the authoritative cooldown enchantment with retail expiry semantics, and project the exact ten DAT-authored radial steps through the shared retained item-slot architecture.

Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-23 10:41:16 +02:00
parent 19e8863f4e
commit 6b1ae4fb76
27 changed files with 875 additions and 20 deletions

View file

@ -160,6 +160,87 @@ public sealed class SpellbookTests
Assert.Equal(0, book.ActiveCount);
}
[Fact]
public void OnCooldown_removes_first_expired_match_then_reveals_next_record()
{
var book = new Spellbook();
var expired = new ActiveEnchantmentRecord(
SpellId: 0x802Au,
LayerId: 1u,
Duration: 5d,
CasterGuid: 0u,
Bucket: Spellbook.CooldownBucket,
StartTime: 0d);
var active = expired with
{
LayerId = 2u,
Duration = 30d,
StartTime = 10d,
};
book.ReplaceManifest(
new Dictionary<uint, float>(),
[expired, active],
Array.Empty<IReadOnlyList<uint>>(),
Array.Empty<(uint Id, uint Amount)>(),
0u);
ActiveEnchantmentRecord? removed = null;
book.EnchantmentRemoved += record => removed = record;
Assert.False(book.OnCooldown(
0x2Au,
currentTime: 20d,
out double expiredRemaining));
Assert.Equal(-15d, expiredRemaining);
Assert.Equal(expired, removed);
Assert.Equal(1, book.ActiveCount);
Assert.True(book.OnCooldown(
0x2Au,
currentTime: 20d,
out double activeRemaining));
Assert.Equal(20d, activeRemaining);
}
[Fact]
public void OnCooldown_ignores_same_spell_outside_cooldown_registry()
{
var book = new Spellbook();
book.OnEnchantmentAdded(new ActiveEnchantmentRecord(
SpellId: 0x802Au,
LayerId: 1u,
Duration: 30d,
CasterGuid: 0u,
Bucket: 1u,
StartTime: 10d));
Assert.False(book.OnCooldown(
0x2Au,
currentTime: 20d,
out double remaining));
Assert.Equal(0d, remaining);
Assert.Equal(1, book.ActiveCount);
}
[Fact]
public void OnCooldown_zero_group_is_inactive_and_does_not_remove()
{
var book = new Spellbook();
book.OnEnchantmentAdded(new ActiveEnchantmentRecord(
SpellId: 0x8000u,
LayerId: 1u,
Duration: 30d,
CasterGuid: 0u,
Bucket: Spellbook.CooldownBucket,
StartTime: 10d));
Assert.False(book.OnCooldown(
cooldownId: 0u,
currentTime: 20d,
out double remaining));
Assert.Equal(0d, remaining);
Assert.Equal(1, book.ActiveCount);
}
[Fact]
public void OnPurgeAll_RemovesOnlyFiniteMultAndAddEnchantments()
{