feat: port retail magic lifecycle and retained spell UI

Complete the retail cast-intent, target, component, enchantment, and busy-state paths; mount the DAT-authored spell bar, spellbook, component book, effects panels, and shared panel lifecycle; and add scoped input plus conformance coverage.

Co-Authored-By: Codex <noreply@openai.com>
This commit is contained in:
Erik 2026-07-15 10:55:22 +02:00
parent 7b7ffcd278
commit 07be994d97
84 changed files with 17822 additions and 1051 deletions

View file

@ -0,0 +1,91 @@
using AcDream.Core.Spells;
namespace AcDream.Core.Tests.Spells;
public sealed class EnchantmentRegistryProjectionTests
{
[Fact]
public void GetEnchantmentsInEffect_ExcludesVitaeAndCooldown()
{
IReadOnlyList<ActiveEnchantmentRecord> result =
EnchantmentRegistryProjection.GetEnchantmentsInEffect(
[
Enchantment(1u, 1u, category: 1u),
Enchantment(2u, 4u, category: 2u),
Enchantment(3u, 8u, category: 3u),
Enchantment(4u, 2u, category: 4u),
]);
Assert.Equal([1u, 4u], result.Select(record => record.SpellId));
}
[Fact]
public void GetEnchantmentsInEffect_CategoryDuel_PrefersHigherPower()
{
IReadOnlyList<ActiveEnchantmentRecord> result =
EnchantmentRegistryProjection.GetEnchantmentsInEffect(
[
Enchantment(1u, 1u, category: 12u, power: 1u),
Enchantment(2u, 1u, category: 12u, power: 7u),
]);
Assert.Equal(2u, Assert.Single(result).SpellId);
}
[Fact]
public void GetEnchantmentsInEffect_PowerComparisonUsesRetailSignedInt()
{
IReadOnlyList<ActiveEnchantmentRecord> result =
EnchantmentRegistryProjection.GetEnchantmentsInEffect(
[
Enchantment(1u, 1u, category: 12u, power: 1u),
Enchantment(2u, 1u, category: 12u, power: uint.MaxValue),
]);
Assert.Equal(1u, Assert.Single(result).SpellId);
}
[Fact]
public void GetEnchantmentsInEffect_EqualPower_PrefersLaterStartAndKeepsExactTie()
{
IReadOnlyList<ActiveEnchantmentRecord> result =
EnchantmentRegistryProjection.GetEnchantmentsInEffect(
[
Enchantment(1u, 1u, category: 12u, power: 7u, start: 10d),
Enchantment(2u, 1u, category: 12u, power: 7u, start: 20d),
Enchantment(3u, 2u, category: 12u, power: 7u, start: 20d),
]);
Assert.Equal(2u, Assert.Single(result).SpellId);
}
[Fact]
public void GetEnchantmentsInEffect_ReplacingWinner_AppendsInRetailOrder()
{
IReadOnlyList<ActiveEnchantmentRecord> result =
EnchantmentRegistryProjection.GetEnchantmentsInEffect(
[
Enchantment(1u, 1u, category: 10u, power: 1u),
Enchantment(2u, 1u, category: 20u, power: 1u),
Enchantment(3u, 2u, category: 10u, power: 2u),
]);
Assert.Equal([2u, 3u], result.Select(record => record.SpellId));
}
private static ActiveEnchantmentRecord Enchantment(
uint spellId,
uint bucket,
uint category,
uint power = 1u,
double start = 0d)
=> new(
SpellId: spellId,
LayerId: spellId,
Duration: 60d,
CasterGuid: 1u,
Bucket: bucket,
StartTime: start,
SpellCategory: category,
PowerLevel: power);
}